refactor(ffi-linkage): Phase 9.3/9.4 — purge 'foreign' from issues/*.md; GATE PASS
Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern, foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl, findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→ dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed issues/0043-…-foreign-class-…→…-runtime-class-…. PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/ issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party). Suite green (644 corpus / 443 unit, 0 failed).
This commit is contained in:
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
> **Status: superseded — kept for reference.** Relocated from the old
|
> **Status: superseded — kept for reference.** Relocated from the old
|
||||||
> `examples/issue-0019/` fixture during the test-layout migration. The behavior
|
> `examples/issue-0019/` fixture during the test-layout migration. The behavior
|
||||||
> it probed (A imports B and C; C must NOT see B's `#foreign` C functions just
|
> it probed (A imports B and C; C must NOT see B's `extern` C functions just
|
||||||
> because A imported B) is now covered by the passing test
|
> because A imported B) is now covered by the passing test
|
||||||
> `examples/0706-modules-import-non-transitive.sx`.
|
> `examples/0706-modules-import-non-transitive.sx`.
|
||||||
|
|
||||||
## What it probed
|
## What it probed
|
||||||
|
|
||||||
`main` imports both `c_wrapper.sx` (which declares C `#foreign` functions) and
|
`main` imports both `c_wrapper.sx` (which declares C `extern` functions) and
|
||||||
`other.sx`. `other.sx` should *not* gain access to `c_wrapper`'s C functions
|
`other.sx`. `other.sx` should *not* gain access to `c_wrapper`'s C functions
|
||||||
transitively — using one should produce the "not visible; #import the module that
|
transitively — using one should produce the "not visible; #import the module that
|
||||||
declares it" diagnostic.
|
declares it" diagnostic.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
Support an `extern G : T;` top-level form so a global **defined** in one sx
|
Support an `extern G : T;` top-level form so a global **defined** in one sx
|
||||||
source file can be **referenced** from another without threading it through
|
source file can be **referenced** from another without threading it through
|
||||||
parameters — mirroring how `#foreign` function declarations work (declared in one
|
parameters — mirroring how `extern` function declarations work (declared in one
|
||||||
place, defined elsewhere, resolved at link time).
|
place, defined elsewhere, resolved at link time).
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
@@ -28,8 +28,8 @@ load :: (self: *ChessPieces, path: [:0]u8) {
|
|||||||
|
|
||||||
Today `pieces.load` takes `has_gpu: bool, gpu: GPU` params and `main.sx` threads
|
Today `pieces.load` takes `has_gpu: bool, gpu: GPU` params and `main.sx` threads
|
||||||
them through; cross-file `extern` globals would drop that ceremony. Distinct from
|
them through; cross-file `extern` globals would drop that ceremony. Distinct from
|
||||||
the existing `name : T #foreign;` form (an *external C* data symbol from
|
the existing `name : T extern;` form (an *external C* data symbol from
|
||||||
libsystem etc. — see `examples/1205-ffi-foreign-global.sx`); this request is for
|
libsystem etc. — see `examples/1205-ffi-extern-global.sx`); this request is for
|
||||||
sx-defined globals shared across sx modules.
|
sx-defined globals shared across sx modules.
|
||||||
|
|
||||||
## Reproduction
|
## Reproduction
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# issue-0043: lazy-lowered function bodies don't resolve foreign-class method dispatch
|
# issue-0043: lazy-lowered function bodies don't resolve runtime-class method dispatch
|
||||||
|
|
||||||
**FIXED.** The original repro
|
**FIXED.** The original repro
|
||||||
(`sx build --target ios-sim issue-0043.sx` with a
|
(`sx build --target ios-sim issue-0043.sx` with a
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
`inline if OS == .ios { ... }`-gated function called transitively
|
`inline if OS == .ios { ... }`-gated function called transitively
|
||||||
from `caller :: (...) callconv(.c)`) compiles cleanly today; chess
|
from `caller :: (...) callconv(.c)`) compiles cleanly today; chess
|
||||||
on iOS-sim runs end-to-end through the same dispatch shape. The
|
on iOS-sim runs end-to-end through the same dispatch shape. The
|
||||||
fix lives in tree as part of broader foreign-class /
|
fix lives in tree as part of broader runtime-class /
|
||||||
lazyLowerFunction work — no specific commit isolates it.
|
lazyLowerFunction work — no specific commit isolates it.
|
||||||
|
|
||||||
Below preserved as a record of the original problem.
|
Below preserved as a record of the original problem.
|
||||||
@@ -53,7 +53,7 @@ lowerExpr
|
|||||||
|
|
||||||
So when the *outer* `lowerCall` decides to lazy-lower
|
So when the *outer* `lowerCall` decides to lazy-lower
|
||||||
`uikit_scene_will_connect_ios`, the *inner* body's method-dispatch
|
`uikit_scene_will_connect_ios`, the *inner* body's method-dispatch
|
||||||
on `*UIWindow` etc. fails to find the foreign-class declaration —
|
on `*UIWindow` etc. fails to find the runtime-class declaration —
|
||||||
even though the declaration is at module scope at the top of
|
even though the declaration is at module scope at the top of
|
||||||
uikit.sx and resolves fine for non-lazy lowering paths (`sx build`
|
uikit.sx and resolves fine for non-lazy lowering paths (`sx build`
|
||||||
for macOS target compiles the same source cleanly).
|
for macOS target compiles the same source cleanly).
|
||||||
@@ -64,12 +64,12 @@ for macOS target compiles the same source cleanly).
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
#import "modules/compiler.sx";
|
#import "modules/compiler.sx";
|
||||||
|
|
||||||
UIWindow :: #foreign #objc_class("UIWindow") {
|
UIWindow :: #objc_class("UIWindow") extern {
|
||||||
alloc :: () -> *UIWindow;
|
alloc :: () -> *UIWindow;
|
||||||
initWithWindowScene :: (self: *Self, scene: *void) -> *UIWindow;
|
initWithWindowScene :: (self: *Self, scene: *void) -> *UIWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function B: uses foreign-class method dispatch.
|
// Function B: uses runtime-class method dispatch.
|
||||||
do_work :: (scene: *void) {
|
do_work :: (scene: *void) {
|
||||||
win := UIWindow.alloc().initWithWindowScene(scene);
|
win := UIWindow.alloc().initWithWindowScene(scene);
|
||||||
_ = win;
|
_ = win;
|
||||||
@@ -104,30 +104,30 @@ Suspected area: `lazyLowerFunction` at
|
|||||||
[src/ir/lower.zig:1057](../src/ir/lower.zig#L1057) and the field-
|
[src/ir/lower.zig:1057](../src/ir/lower.zig#L1057) and the field-
|
||||||
access method dispatch at
|
access method dispatch at
|
||||||
[src/ir/lower.zig:5290](../src/ir/lower.zig#L5290) (around the
|
[src/ir/lower.zig:5290](../src/ir/lower.zig#L5290) (around the
|
||||||
`foreign_class_map.get(sname_for_foreign)` lookup).
|
`runtime_class_map.get(sname_for_runtime)` lookup).
|
||||||
|
|
||||||
Hypotheses:
|
Hypotheses:
|
||||||
|
|
||||||
1. `lazyLowerFunction` swaps some piece of lowering state on entry
|
1. `lazyLowerFunction` swaps some piece of lowering state on entry
|
||||||
(e.g., `saved_source_file`, `current_ctx_ref`) but doesn't preserve
|
(e.g., `saved_source_file`, `current_ctx_ref`) but doesn't preserve
|
||||||
access to `foreign_class_map`. Check whether the map is
|
access to `runtime_class_map`. Check whether the map is
|
||||||
instance-state vs. shared.
|
instance-state vs. shared.
|
||||||
2. The receiver type for `win` (`*UIWindow`) isn't being resolved to
|
2. The receiver type for `win` (`*UIWindow`) isn't being resolved to
|
||||||
its `getStructTypeName` correctly during lazy lowering — possibly
|
its `getStructTypeName` correctly during lazy lowering — possibly
|
||||||
`inferExprType` for the lazy-lowered context resolves to an
|
`inferExprType` for the lazy-lowered context resolves to an
|
||||||
anonymous type instead of `UIWindow`.
|
anonymous type instead of `UIWindow`.
|
||||||
3. The foreign-class declarations are added to `foreign_class_map`
|
3. The runtime-class declarations are added to `runtime_class_map`
|
||||||
during a pre-scan pass that runs BEFORE the outer function `A`'s
|
during a pre-scan pass that runs BEFORE the outer function `A`'s
|
||||||
body is lowered, but lazy lowering of `B` from within `A` might
|
body is lowered, but lazy lowering of `B` from within `A` might
|
||||||
be observing the map at a pre-scan state where uikit.sx's
|
be observing the map at a pre-scan state where uikit.sx's
|
||||||
declarations haven't been seen yet (cross-module ordering).
|
declarations haven't been seen yet (cross-module ordering).
|
||||||
|
|
||||||
What the fix likely needs to do:
|
What the fix likely needs to do:
|
||||||
- Confirm `foreign_class_map` contains `"UIWindow"` etc. at the
|
- Confirm `runtime_class_map` contains `"UIWindow"` etc. at the
|
||||||
point of the lazy lowering call (add a debug print at the failing
|
point of the lazy lowering call (add a debug print at the failing
|
||||||
dispatch).
|
dispatch).
|
||||||
- If the map IS populated, trace why the field-access dispatch falls
|
- If the map IS populated, trace why the field-access dispatch falls
|
||||||
through to line 5640 instead of taking the `foreign_class_map.get`
|
through to line 5640 instead of taking the `runtime_class_map.get`
|
||||||
branch at line 5306.
|
branch at line 5306.
|
||||||
- If the map is NOT populated, find the seeding path and fix the
|
- If the map is NOT populated, find the seeding path and fix the
|
||||||
ordering — likely a missing pre-scan step before lazy lowering.
|
ordering — likely a missing pre-scan step before lazy lowering.
|
||||||
@@ -142,7 +142,7 @@ away.
|
|||||||
|
|
||||||
The migration of `library/modules/platform/uikit.sx` to declarative
|
The migration of `library/modules/platform/uikit.sx` to declarative
|
||||||
`#objc_class` dispatch (Phase 3.2 plan part C, clusters C4 + C5)
|
`#objc_class` dispatch (Phase 3.2 plan part C, clusters C4 + C5)
|
||||||
necessarily places foreign-class method calls inside iOS-only helper
|
necessarily places runtime-class method calls inside iOS-only helper
|
||||||
functions that get lazily lowered when chess's iOS scene-lifecycle
|
functions that get lazily lowered when chess's iOS scene-lifecycle
|
||||||
hooks fire them. Without this fix, the migration produces compile
|
hooks fire them. Without this fix, the migration produces compile
|
||||||
errors on iOS-sim that don't appear on macOS. C1+C2+C3 happened to
|
errors on iOS-sim that don't appear on macOS. C1+C2+C3 happened to
|
||||||
@@ -31,8 +31,8 @@ method declaration — by position, not by hardcoded name.
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
#import "modules/std/objc.sx";
|
#import "modules/std/objc.sx";
|
||||||
|
|
||||||
// Foreign declaration so we can dispatch.
|
// Extern declaration so we can dispatch.
|
||||||
NSObject :: #foreign #objc_class("NSObject") {
|
NSObject :: #objc_class("NSObject") extern {
|
||||||
class :: () -> *void;
|
class :: () -> *void;
|
||||||
description :: (self: *Self) -> *void;
|
description :: (self: *Self) -> *void;
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ $ grep -n '"self"' src/ir/lower.zig
|
|||||||
```
|
```
|
||||||
|
|
||||||
The methods that synthesize the IMP trampoline (M1.2 A.2) and the
|
The methods that synthesize the IMP trampoline (M1.2 A.2) and the
|
||||||
ones that wire `*Self` → opaque foreign-class stub (M1.2 A.3) appear
|
ones that wire `*Self` → opaque runtime-class stub (M1.2 A.3) appear
|
||||||
to either:
|
to either:
|
||||||
|
|
||||||
(a) emit the trampoline assuming the slot name in the body is "self",
|
(a) emit the trampoline assuming the slot name in the body is "self",
|
||||||
@@ -139,12 +139,12 @@ went wrong:
|
|||||||
1. uikit.sx renamed the AppDelegate's IMP method first param to `this`,
|
1. uikit.sx renamed the AppDelegate's IMP method first param to `this`,
|
||||||
so `xx this` appeared inside the body of a `-> BOOL` method.
|
so `xx this` appeared inside the body of a `-> BOOL` method.
|
||||||
2. The body called `center.addObserver_selector_name_object(xx this, ..., null)`
|
2. The body called `center.addObserver_selector_name_object(xx this, ..., null)`
|
||||||
on a `*NSNotificationCenter` foreign-class receiver.
|
on a `*NSNotificationCenter` runtime-class receiver.
|
||||||
3. `lowerCall` sets a per-arg `self.target_type` from
|
3. `lowerCall` sets a per-arg `self.target_type` from
|
||||||
`resolveCallParamTypes(c)`. For UFCS dispatch on a foreign-class
|
`resolveCallParamTypes(c)`. For UFCS dispatch on a runtime-class
|
||||||
alias, that function had no path covering `foreign_class_map` —
|
alias, that function had no path covering `runtime_class_map` —
|
||||||
it tried `resolveFuncByName(qualified)` and `fn_ast_map.get(qualified)`,
|
it tried `resolveFuncByName(qualified)` and `fn_ast_map.get(qualified)`,
|
||||||
both of which miss for `#foreign #objc_class` methods.
|
both of which miss for `#objc_class(…) extern` methods.
|
||||||
4. With `param_types` empty, the per-arg `target_type` assignment was
|
4. With `param_types` empty, the per-arg `target_type` assignment was
|
||||||
skipped, so `self.target_type` retained its previous value: the
|
skipped, so `self.target_type` retained its previous value: the
|
||||||
enclosing fn's return type, **BOOL → i8**.
|
enclosing fn's return type, **BOOL → i8**.
|
||||||
@@ -160,9 +160,9 @@ exercised in the body in the original tests, OR the encoding
|
|||||||
happened to land somewhere benign (e.g. the AOT-with-iOS-sim path
|
happened to land somewhere benign (e.g. the AOT-with-iOS-sim path
|
||||||
plus UIKit's specific validation order).
|
plus UIKit's specific validation order).
|
||||||
|
|
||||||
**Fix:** add a `foreign_class_map.get(sname)` →
|
**Fix:** add a `runtime_class_map.get(sname)` →
|
||||||
`findForeignMethodInChain` path to `resolveCallParamTypes`. When the
|
`findRuntimeMethodInChain` path to `resolveCallParamTypes`. When the
|
||||||
UFCS receiver is a foreign-class alias, walk the `#extends` chain to
|
UFCS receiver is a runtime-class alias, walk the `#extends` chain to
|
||||||
find the method, then resolve its declared param types (skipping the
|
find the method, then resolve its declared param types (skipping the
|
||||||
implicit `*Self` for instance methods). With the fix, `param_types`
|
implicit `*Self` for instance methods). With the fix, `param_types`
|
||||||
returns `[*void, *void, *void, *void]` for the addObserver: call,
|
returns `[*void, *void, *void, *void]` for the addObserver: call,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ edit in `library/modules/std.sx`.
|
|||||||
|
|
||||||
The FFI plan migrates all stdlib variadic decls from the legacy
|
The FFI plan migrates all stdlib variadic decls from the legacy
|
||||||
form to the new `..name: []Type` form (`path_join`, `format`,
|
form to the new `..name: []Type` form (`path_join`, `format`,
|
||||||
`print`, plus the foreign `open` decl, plus the example fixtures).
|
`print`, plus the extern `open` decl, plus the example fixtures).
|
||||||
Per the FFI cadence rule the migration is supposed to be a
|
Per the FFI cadence rule the migration is supposed to be a
|
||||||
mechanical textual change with identical semantics. This bug
|
mechanical textual change with identical semantics. This bug
|
||||||
blocks that.
|
blocks that.
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ The macOS SDL startup never reorients CWD (or the asset root) to the bundle.
|
|||||||
calls `SDL_Init(SDL_INIT_VIDEO)` and creates the window but does no `chdir`,
|
calls `SDL_Init(SDL_INIT_VIDEO)` and creates the window but does no `chdir`,
|
||||||
so `read_file_bytes`
|
so `read_file_bytes`
|
||||||
([library/modules/ui/glyph_cache.sx:202](../library/modules/ui/glyph_cache.sx#L202),
|
([library/modules/ui/glyph_cache.sx:202](../library/modules/ui/glyph_cache.sx#L202),
|
||||||
and the chess `#foreign read_file_bytes`) opens paths relative to whatever CWD
|
and the chess `extern read_file_bytes`) opens paths relative to whatever CWD
|
||||||
the launcher set — `/` under Finder/`open`.
|
the launcher set — `/` under Finder/`open`.
|
||||||
|
|
||||||
The other platforms already handle this:
|
The other platforms already handle this:
|
||||||
@@ -97,8 +97,8 @@ Recommended approach — `SDL_GetBasePath()`:
|
|||||||
to it at the top of `init` when `BuildOptions.is_macos` (gate so `sx run`
|
to it at the top of `init` when `BuildOptions.is_macos` (gate so `sx run`
|
||||||
during development isn't affected — or gate on "the base path differs from
|
during development isn't affected — or gate on "the base path differs from
|
||||||
CWD and contains an `assets/` dir").
|
CWD and contains an `assets/` dir").
|
||||||
- Add the `#foreign` decl for `SDL_GetBasePath` (returns `*u8`, SDL-owned) and
|
- Add the `extern` decl for `SDL_GetBasePath` (returns `*u8`, SDL-owned) and
|
||||||
call `chdir` (already used by uikit.sx — reuse the same `#foreign`).
|
call `chdir` (already used by uikit.sx — reuse the same `extern`).
|
||||||
|
|
||||||
Alternative (no SDL dependency): `_NSGetExecutablePath` + `dirname`, same as a
|
Alternative (no SDL dependency): `_NSGetExecutablePath` + `dirname`, same as a
|
||||||
plain macOS resolve. SDL_GetBasePath is simpler and already links SDL3.
|
plain macOS resolve. SDL_GetBasePath is simpler and already links SDL3.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ This bit the moment `modules/std/objc.sx` (imported by `main.sx`,
|
|||||||
## Root cause
|
## Root cause
|
||||||
|
|
||||||
`mergeFlat`/`addOwnDecl` dedup the global flat decl list by
|
`mergeFlat`/`addOwnDecl` dedup the global flat decl list by
|
||||||
`decl.data.declName()`. Named decls (structs, fns, foreign classes) dedup
|
`decl.data.declName()`. Named decls (structs, fns, runtime classes) dedup
|
||||||
fine across diamonds. But `impl_block` is anonymous — `declName()` returns
|
fine across diamonds. But `impl_block` is anonymous — `declName()` returns
|
||||||
`null` (see [src/ast.zig](../src/ast.zig) `Data.declName`) — so the dedup
|
`null` (see [src/ast.zig](../src/ast.zig) `Data.declName`) — so the dedup
|
||||||
guard was skipped and the **same cached** impl node (modules are cached in
|
guard was skipped and the **same cached** impl node (modules are cached in
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
> function signature and non-generic struct field type and rejects any leaf name
|
> function signature and non-generic struct field type and rejects any leaf name
|
||||||
> that is not a primitive, an in-scope generic param (`$T` / `type_params`), a
|
> that is not a primitive, an in-scope generic param (`$T` / `type_params`), a
|
||||||
> declared type, or a real (non-stub) registered type. The load-bearing
|
> declared type, or a real (non-stub) registered type. The load-bearing
|
||||||
> empty-struct stub is left intact (forward references + foreign-class opaque
|
> empty-struct stub is left intact (forward references + runtime-class opaque
|
||||||
> types still rely on it during the scan); the pass runs after scanning and before
|
> types still rely on it during the scan); the pass runs after scanning and before
|
||||||
> body lowering, so `core.zig`'s `hasErrors()` halts the build before any stub
|
> body lowering, so `core.zig`'s `hasErrors()` halts the build before any stub
|
||||||
> reaches codegen. A value param used as a type gets the tailored hint
|
> reaches codegen. A value param used as a type gets the tailored hint
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ Suspected area:
|
|||||||
Likely fix:
|
Likely fix:
|
||||||
- Change `collectDeclaredTypeNames` / `harvestScopeDecls` so only declarations
|
- Change `collectDeclaredTypeNames` / `harvestScopeDecls` so only declarations
|
||||||
that actually introduce type-position names are added: struct / enum / union /
|
that actually introduce type-position names are added: struct / enum / union /
|
||||||
error declarations, type aliases, generic templates, protocols, foreign
|
error declarations, type aliases, generic templates, protocols, extern
|
||||||
classes, and local type declarations.
|
classes, and local type declarations.
|
||||||
- Do not add arbitrary value const names to the type-name set.
|
- Do not add arbitrary value const names to the type-name set.
|
||||||
- Preserve valid type alias behavior such as `Alias :: u32;` and local
|
- Preserve valid type alias behavior such as `Alias :: u32;` and local
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Likely fix:
|
|||||||
a diagnostic instead of returning `null` / zero-initializing.
|
a diagnostic instead of returning `null` / zero-initializing.
|
||||||
- Do not regress issue 0070: `A :: B; B :: i32; g : A = 7;` and
|
- Do not regress issue 0070: `A :: B; B :: i32; g : A = 7;` and
|
||||||
`K : A : 35;` must still resolve through the converged alias map.
|
`K : A : 35;` must still resolve through the converged alias map.
|
||||||
- Preserve literal, array literal, struct literal, and foreign-global behavior.
|
- Preserve literal, array literal, struct literal, and extern-global behavior.
|
||||||
|
|
||||||
Verification:
|
Verification:
|
||||||
- Add a focused regression, likely in the `01xx` types block:
|
- Add a focused regression, likely in the `01xx` types block:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
> **✅ RESOLVED.** Root cause: four FFI call-arg lowering loops resolved an
|
> **✅ RESOLVED.** Root cause: four FFI call-arg lowering loops resolved an
|
||||||
> argument's IR type via `getRefIRType(arg_ref) orelse .void` — a silent fallback
|
> argument's IR type via `getRefIRType(arg_ref) orelse .void` — a silent fallback
|
||||||
> to the load-bearing real type `.void`, which downstream `toLLVMType` →
|
> to the load-bearing real type `.void`, which downstream `toLLVMType` →
|
||||||
> `abiCoerceParamType` → `coerceArg` treat as a legitimate (void-typed) foreign
|
> `abiCoerceParamType` → `coerceArg` treat as a legitimate (void-typed) extern
|
||||||
> argument, corrupting the call ABI with no diagnostic. Fix: one shared resolver
|
> argument, corrupting the call ABI with no diagnostic. Fix: one shared resolver
|
||||||
> `LLVMEmitter.argIRTypeOrFail` ([src/ir/emit_llvm.zig]) returns the dedicated
|
> `LLVMEmitter.argIRTypeOrFail` ([src/ir/emit_llvm.zig]) returns the dedicated
|
||||||
> `.unresolved` sentinel on a failed lookup — never `.void`/`.i64` — so the failure
|
> `.unresolved` sentinel on a failed lookup — never `.void`/`.i64` — so the failure
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
**One-line:** Four FFI call-arg lowering sites silently default a failed
|
**One-line:** Four FFI call-arg lowering sites silently default a failed
|
||||||
argument-type lookup to `.void` — the forbidden silent-type-fallback anti-pattern
|
argument-type lookup to `.void` — the forbidden silent-type-fallback anti-pattern
|
||||||
(`.void` as a failed-type-lookup sentinel), which would produce a void-typed
|
(`.void` as a failed-type-lookup sentinel), which would produce a void-typed
|
||||||
foreign-call argument (wrong LLVM param type → silent ABI corruption) with no
|
extern-call argument (wrong LLVM param type → silent ABI corruption) with no
|
||||||
diagnostic if the lookup ever fails.
|
diagnostic if the lookup ever fails.
|
||||||
|
|
||||||
**Observed:** `self.getRefIRType(arg_ref) orelse .void` at:
|
**Observed:** `self.getRefIRType(arg_ref) orelse .void` at:
|
||||||
@@ -30,7 +30,7 @@ diagnostic if the lookup ever fails.
|
|||||||
- `src/backend/llvm/ops.zig:761` (JNI `Call<Type>Method` arg loop)
|
- `src/backend/llvm/ops.zig:761` (JNI `Call<Type>Method` arg loop)
|
||||||
|
|
||||||
Each then does `toLLVMType(raw_ty)` → `abiCoerceParamType` → `coerceArg`, so a
|
Each then does `toLLVMType(raw_ty)` → `abiCoerceParamType` → `coerceArg`, so a
|
||||||
`.void` fallback silently mis-types the foreign-call argument.
|
`.void` fallback silently mis-types the extern-call argument.
|
||||||
|
|
||||||
**Expected:** `getRefIRType` returning null for a real call argument is a
|
**Expected:** `getRefIRType` returning null for a real call argument is a
|
||||||
"must-succeed lookup" failure (every arg is a valid param/instruction ref). Per
|
"must-succeed lookup" failure (every arg is a valid param/instruction ref). Per
|
||||||
@@ -49,7 +49,7 @@ discovered bug — file an issue, do not just delete the default in place"*).
|
|||||||
|
|
||||||
## Reproduction
|
## Reproduction
|
||||||
This is a **latent / static** finding: there is no known sx program that drives
|
This is a **latent / static** finding: there is no known sx program that drives
|
||||||
`getRefIRType` to `null` for a valid foreign-call argument (well-formed IR always
|
`getRefIRType` to `null` for a valid extern-call argument (well-formed IR always
|
||||||
has a type for every arg ref), so it cannot currently be triggered at runtime — which
|
has a type for every arg ref), so it cannot currently be triggered at runtime — which
|
||||||
is exactly why it is dangerous (a future IR change that breaks the invariant would
|
is exactly why it is dangerous (a future IR change that breaks the invariant would
|
||||||
corrupt FFI ABI silently). The code paths are exercised (and must stay green after
|
corrupt FFI ABI silently). The code paths are exercised (and must stay green after
|
||||||
@@ -81,4 +81,4 @@ loud-failure path, see below.)
|
|||||||
> green (the happy path is unchanged); (2) add a `*.test.zig` unit test that
|
> green (the happy path is unchanged); (2) add a `*.test.zig` unit test that
|
||||||
> constructs an FFI call op with a bogus arg ref and asserts the loud failure fires
|
> constructs an FFI call op with a bogus arg ref and asserts the loud failure fires
|
||||||
> (not a `.void` silent default). Expected new behavior: an unresolved FFI arg type
|
> (not a `.void` silent default). Expected new behavior: an unresolved FFI arg type
|
||||||
> produces a clear compiler error / panic, never a void-typed foreign argument.
|
> produces a clear compiler error / panic, never a void-typed extern argument.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
> name span in the AST (`VarDecl.name_span`, `DestructureDecl.name_spans`,
|
> name span in the AST (`VarDecl.name_span`, `DestructureDecl.name_spans`,
|
||||||
> `IfExpr`/`WhileExpr.binding_span`, `ForExpr.capture_span`/`index_span`,
|
> `IfExpr`/`WhileExpr.binding_span`, `ForExpr.capture_span`/`index_span`,
|
||||||
> `MatchArm.capture_span`, `CatchExpr`/`OnFailStmt.binding_span`,
|
> `MatchArm.capture_span`, `CatchExpr`/`OnFailStmt.binding_span`,
|
||||||
> `Protocol`/`ForeignMethodDecl.param_name_spans`), populated by the parser at
|
> `Protocol`/`RuntimeMethodDecl.param_name_spans`), populated by the parser at
|
||||||
> each binding site. `checkBindingNames` passes that span to the diagnostic, so
|
> each binding site. `checkBindingNames` passes that span to the diagnostic, so
|
||||||
> the caret underlines the offending identifier itself instead of the enclosing
|
> the caret underlines the offending identifier itself instead of the enclosing
|
||||||
> statement / `if` / `match` / `protocol` / `#objc_class` block. No call site
|
> statement / `if` / `match` / `protocol` / `#objc_class` block. No call site
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 0089 — backtick raw-identifier escape + `#import c` foreign-name exemption from the reserved-type-name rule
|
# 0089 — backtick raw-identifier escape + `#import c` extern-name exemption from the reserved-type-name rule
|
||||||
|
|
||||||
> **✅ RESOLVED** (foundation step F0.6). Two mechanisms, per Agra's design
|
> **✅ RESOLVED** (foundation step F0.6). Two mechanisms, per Agra's design
|
||||||
> ruling; the final shape is the **universal raw identifier** (attempt 4):
|
> ruling; the final shape is the **universal raw identifier** (attempt 4):
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
> declaration node ([src/ast.zig]): `VarDecl` / `ConstDecl` / `Param` / `FnDecl`
|
> declaration node ([src/ast.zig]): `VarDecl` / `ConstDecl` / `Param` / `FnDecl`
|
||||||
> plus `IfExpr` / `WhileExpr` optional bindings, `ForExpr` capture + index,
|
> plus `IfExpr` / `WhileExpr` optional bindings, `ForExpr` capture + index,
|
||||||
> `MatchArm` capture, `CatchExpr` / `OnFailStmt` tag bindings, `DestructureDecl`
|
> `MatchArm` capture, `CatchExpr` / `OnFailStmt` tag bindings, `DestructureDecl`
|
||||||
> per-name, protocol-default / foreign-class method params, AND every
|
> per-name, protocol-default / runtime-class method params, AND every
|
||||||
> type-introducing decl — `StructDecl` / `EnumDecl` / `UnionDecl` /
|
> type-introducing decl — `StructDecl` / `EnumDecl` / `UnionDecl` /
|
||||||
> `ErrorSetDecl` / `ProtocolDecl` / `ForeignClassDecl` / `UfcsAlias` /
|
> `ErrorSetDecl` / `ProtocolDecl` / `RuntimeClassDecl` / `UfcsAlias` /
|
||||||
> `NamespaceDecl` / `ImportDecl` / `CImportDecl` / `LibraryDecl`.
|
> `NamespaceDecl` / `ImportDecl` / `CImportDecl` / `LibraryDecl`.
|
||||||
>
|
>
|
||||||
> - **Value position.** The parser skips `Type.fromName` for a raw identifier
|
> - **Value position.** The parser skips `Type.fromName` for a raw identifier
|
||||||
@@ -81,15 +81,15 @@
|
|||||||
> reserved-spelled impl method needs the backtick (`` `i2 :: (self) ``), no
|
> reserved-spelled impl method needs the backtick (`` `i2 :: (self) ``), no
|
||||||
> more exempt than a free function (cf. `examples/1122`). Pinned by
|
> more exempt than a free function (cf. `examples/1122`). Pinned by
|
||||||
> `examples/0158-types-reserved-name-member-exempt.sx`.
|
> `examples/0158-types-reserved-name-member-exempt.sx`.
|
||||||
> 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign
|
> 2. **`#import c` extern-name exemption.** `c_import.zig` synthesizes extern
|
||||||
> `#foreign` decls with `Param.is_raw = true` (and the synthesized `FnDecl`
|
> `extern` decls with `Param.is_raw = true` (and the synthesized `FnDecl`
|
||||||
> `is_raw = true`), so generated C names that collide with reserved type names
|
> `is_raw = true`), so generated C names that collide with reserved type names
|
||||||
> (`i1`, `i2`) import unedited and a reserved-name foreign fn is bare-callable.
|
> (`i1`, `i2`) import unedited and a reserved-name extern fn is bare-callable.
|
||||||
>
|
>
|
||||||
> **Bare-callable foreign / backtick fn.** `lowerCall` rewrites a `.type_expr`
|
> **Bare-callable extern / backtick fn.** `lowerCall` rewrites a `.type_expr`
|
||||||
> callee to an identifier when a function **of RAW provenance** of that name is in
|
> callee to an identifier when a function **of RAW provenance** of that name is in
|
||||||
> scope ([src/ir/lower.zig]) — scoped to the callee `FnDecl`'s `is_raw` flag, so it
|
> scope ([src/ir/lower.zig]) — scoped to the callee `FnDecl`'s `is_raw` flag, so it
|
||||||
> only ever fires for a backtick / `#import c` foreign fn (the decl check guarantees
|
> only ever fires for a backtick / `#import c` extern fn (the decl check guarantees
|
||||||
> no bare reserved-name fn exists). `i2(4)` resolves to the function (`TypeName(val)`
|
> no bare reserved-name fn exists). `i2(4)` resolves to the function (`TypeName(val)`
|
||||||
> is not a cast).
|
> is not a cast).
|
||||||
>
|
>
|
||||||
@@ -107,8 +107,8 @@
|
|||||||
> fields / union tag / protocol method signature — read & written bare and via
|
> fields / union tag / protocol method signature — read & written bare and via
|
||||||
> backtick; impl method definition takes the backtick),
|
> backtick; impl method definition takes the backtick),
|
||||||
> `examples/1054-errors-backtick-reserved-binding.sx` (`catch`/`onfail` tag
|
> `examples/1054-errors-backtick-reserved-binding.sx` (`catch`/`onfail` tag
|
||||||
> bindings), `examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}` (foreign
|
> bindings), `examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}` (extern
|
||||||
> param + fn-name exemption, bare-callable foreign fn); negatives
|
> param + fn-name exemption, bare-callable extern fn); negatives
|
||||||
> `examples/1119`/`1121`/`1123` (bare reserved binding across forms),
|
> `examples/1119`/`1121`/`1123` (bare reserved binding across forms),
|
||||||
> `examples/1140-diagnostics-reserved-name-const-fn-decl.sx` (bare const + fn decl),
|
> `examples/1140-diagnostics-reserved-name-const-fn-decl.sx` (bare const + fn decl),
|
||||||
> `examples/1141-diagnostics-reserved-name-type-decl.sx` (bare struct / enum / union
|
> `examples/1141-diagnostics-reserved-name-type-decl.sx` (bare struct / enum / union
|
||||||
@@ -156,8 +156,8 @@ declarations — which is what now fires on the C-imported `i1`/`i2`.
|
|||||||
External / imported source does NOT need to conform to sx naming standards. Two
|
External / imported source does NOT need to conform to sx naming standards. Two
|
||||||
mechanisms:
|
mechanisms:
|
||||||
|
|
||||||
1. **Auto-exempt imports.** `#import c` (and other foreign) declarations are
|
1. **Auto-exempt imports.** `#import c` (and other extern) declarations are
|
||||||
treated as RAW identifiers: foreign names are never type-classified and never
|
treated as RAW identifiers: extern names are never type-classified and never
|
||||||
reserved-checked, so generated bindings "just work" with zero user edits.
|
reserved-checked, so generated bindings "just work" with zero user edits.
|
||||||
2. **Backtick raw-identifier for sx code.** A leading backtick makes the following
|
2. **Backtick raw-identifier for sx code.** A leading backtick makes the following
|
||||||
identifier raw — an identifier that is NEVER type-classified, so it bypasses the
|
identifier raw — an identifier that is NEVER type-classified, so it bypasses the
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ the same colliding entry.
|
|||||||
`ns.fn` — they just had nothing to find. `NamespaceDecl` carries the
|
`ns.fn` — they just had nothing to find. `NamespaceDecl` carries the
|
||||||
module's `own_decls` (populated in `imports.addNamespace`) so the
|
module's `own_decls` (populated in `imports.addNamespace`) so the
|
||||||
registration covers authored decls, not transitive flat imports. Generic
|
registration covers authored decls, not transitive flat imports. Generic
|
||||||
/ comptime / pack / foreign functions are excluded — they dispatch by
|
/ comptime / pack / extern functions are excluded — they dispatch by
|
||||||
monomorphization off the bare template name, not the plain
|
monomorphization off the bare template name, not the plain
|
||||||
`resolveFuncByName` path, so a qualified alias would strand their
|
`resolveFuncByName` path, so a qualified alias would strand their
|
||||||
per-call type bindings. The qualified function is declared + lowered on
|
per-call type bindings. The qualified function is declared + lowered on
|
||||||
@@ -106,7 +106,7 @@ class can't diverge again. The fields the defer now restores on all paths:
|
|||||||
- `builder.current_block`
|
- `builder.current_block`
|
||||||
- `builder.inst_counter`
|
- `builder.inst_counter`
|
||||||
|
|
||||||
(The `current_foreign_class`, `jni_env_stack_base`, and pack-mono /
|
(The `current_runtime_class`, `jni_env_stack_base`, and pack-mono /
|
||||||
`inline_return_target` fields already had their own `defer`s and apply on all
|
`inline_return_target` fields already had their own `defer`s and apply on all
|
||||||
paths; they are unchanged.)
|
paths; they are unchanged.)
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ collision:
|
|||||||
`resolveBareCallee(name, caller_file) -> .func(ResolvedAuthor) | .ambiguous |
|
`resolveBareCallee(name, caller_file) -> .func(ResolvedAuthor) | .ambiguous |
|
||||||
.none` (`src/ir/lower.zig`). It returns `.none` whenever the outcome would
|
.none` (`src/ir/lower.zig`). It returns `.none` whenever the outcome would
|
||||||
equal first-wins (single author, or own-author == winner), so every
|
equal first-wins (single author, or own-author == winner), so every
|
||||||
single-author / local / parameter / std / qualified / foreign / generic /
|
single-author / local / parameter / std / qualified / extern / generic /
|
||||||
builtin name resolves byte-for-byte as before. Only a genuine flat collision
|
builtin name resolves byte-for-byte as before. Only a genuine flat collision
|
||||||
reroutes: own-author wins; else the caller's flat-reachable authors — `≥2`
|
reroutes: own-author wins; else the caller's flat-reachable authors — `≥2`
|
||||||
distinct → `.ambiguous` (loud "qualify the call" diagnostic), exactly one
|
distinct → `.ambiguous` (loud "qualify the call" diagnostic), exactly one
|
||||||
@@ -85,7 +85,7 @@ fails on pre-fix code and passes after):
|
|||||||
- `0726-modules-flat-same-name-variadic` — per-source variadic packing.
|
- `0726-modules-flat-same-name-variadic` — per-source variadic packing.
|
||||||
- `0728-modules-flat-same-name-paramtype` — per-source parameter target typing
|
- `0728-modules-flat-same-name-paramtype` — per-source parameter target typing
|
||||||
(value vs pointer param).
|
(value vs pointer param).
|
||||||
- `0729-modules-flat-same-name-foreign` — same-name `#foreign` authors are NOT
|
- `0729-modules-flat-same-name-extern` — same-name `extern` authors are NOT
|
||||||
rerouted (non-plain authors keep first-wins).
|
rerouted (non-plain authors keep first-wins).
|
||||||
- `0730-modules-flat-same-name-default-arg` — per-source default-arg expansion.
|
- `0730-modules-flat-same-name-default-arg` — per-source default-arg expansion.
|
||||||
- `0731-modules-flat-same-name-closure` — per-source `closure(fn)` + bare
|
- `0731-modules-flat-same-name-closure` — per-source `closure(fn)` + bare
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
> `namespaceAliasVerdict` — visible targets dispatch the member fd pinned
|
> `namespaceAliasVerdict` — visible targets dispatch the member fd pinned
|
||||||
> to the TARGET module (`namespaceFnMember` + fd-keyed `bareAuthorFuncId`),
|
> to the TARGET module (`namespaceFnMember` + fd-keyed `bareAuthorFuncId`),
|
||||||
> ambiguous carries diagnose loudly, and an alias that exists only beyond
|
> ambiguous carries diagnose loudly, and an alias that exists only beyond
|
||||||
> one flat hop errors "namespace 'X' is not visible". Foreign/builtin/
|
> one flat hop errors "namespace 'X' is not visible". Extern/builtin/
|
||||||
> #compiler members keep the literal-symbol path. Regression tests:
|
> #compiler members keep the literal-symbol path. Regression tests:
|
||||||
> `examples/0832-modules-namespace-alias-two-hop-not-visible.sx`,
|
> `examples/0832-modules-namespace-alias-two-hop-not-visible.sx`,
|
||||||
> `examples/0833-modules-namespace-alias-carried-collision-ambiguous.sx`,
|
> `examples/0833-modules-namespace-alias-carried-collision-ambiguous.sx`,
|
||||||
|
|||||||
@@ -108,5 +108,5 @@ Context: BLOCKS the std.sx-as-pure-re-exports restructure — `print` /
|
|||||||
unblocked by 0120; this is the remaining known gap. Still unprobed
|
unblocked by 0120; this is the remaining known gap. Still unprobed
|
||||||
for the restructure (next session, after this fix): protocol aliases
|
for the restructure (next session, after this fix): protocol aliases
|
||||||
(`Allocator`, parameterized `Into`), `#builtin` decl aliases
|
(`Allocator`, parameterized `Into`), `#builtin` decl aliases
|
||||||
(`size_of`, `out`, `string :: []u8`), `#foreign` decl aliases
|
(`size_of`, `out`, `string :: []u8`), `extern` decl aliases
|
||||||
(`memcpy`).
|
(`memcpy`).
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Legitimate flexible shapes must keep working: slice variadics
|
|||||||
(`..xs: []T` — no upper bound), comptime/protocol packs (`..$args` /
|
(`..xs: []T` — no upper bound), comptime/protocol packs (`..$args` /
|
||||||
`..xs: P` — own dispatch), default-valued params (incl.
|
`..xs: P` — own dispatch), default-valued params (incl.
|
||||||
`loc: Source_Location = #caller_location`), generic `$T` fns
|
`loc: Source_Location = #caller_location`), generic `$T` fns
|
||||||
(explicit vs inferred type args make the count flexible), `#foreign`
|
(explicit vs inferred type args make the count flexible), `extern`
|
||||||
C variadics, `#compiler` / `#builtin` bodies.
|
C variadics, `#compiler` / `#builtin` bodies.
|
||||||
|
|
||||||
## Reproduction
|
## Reproduction
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
> The two GENUINE defects, both fixed:
|
> The two GENUINE defects, both fixed:
|
||||||
>
|
>
|
||||||
> 1. **Conflicting same-symbol redeclaration was silent.**
|
> 1. **Conflicting same-symbol redeclaration was silent.**
|
||||||
> `dedupeForeignSymbol` (src/ir/lower/decl.zig) now runs at foreign
|
> `dedupeExternSymbol` (src/ir/lower/decl.zig) now runs at extern
|
||||||
> registration: an EQUAL signature shares the first registration's
|
> registration: an EQUAL signature shares the first registration's
|
||||||
> FuncId; a CONFLICTING one is diagnosed ("foreign symbol '<s>' is
|
> FuncId; a CONFLICTING one is diagnosed ("extern symbol '<s>' is
|
||||||
> already bound with a different signature").
|
> already bound with a different signature").
|
||||||
> 2. **Foreign `-> string` / `-> ?string` returns read garbage.** The
|
> 2. **Extern `-> string` / `-> ?string` returns read garbage.** The
|
||||||
> C side returns ONE `char *`; the LLVM signature declared the fat
|
> C side returns ONE `char *`; the LLVM signature declared the fat
|
||||||
> `{ptr,i64}` (len = register garbage; bus error on use), and
|
> `{ptr,i64}` (len = register garbage; bus error on use), and
|
||||||
> `?string` (24 B struct) was mis-declared SRET — the hidden
|
> `?string` (24 B struct) was mis-declared SRET — the hidden
|
||||||
@@ -28,13 +28,13 @@
|
|||||||
>
|
>
|
||||||
> Regression tests: `examples/1221-ffi-cstring-returns.sx` (plain +
|
> Regression tests: `examples/1221-ffi-cstring-returns.sx` (plain +
|
||||||
> optional non-null via strerror/strsignal + optional NULL via
|
> optional non-null via strerror/strsignal + optional NULL via
|
||||||
> dlerror) and `examples/1172-diagnostics-foreign-symbol-conflict.sx`
|
> dlerror) and `examples/1172-diagnostics-extern-symbol-conflict.sx`
|
||||||
> (the getenv conflict); both FAIL on pre-fix master. The extern
|
> (the getenv conflict); both FAIL on pre-fix master. The extern
|
||||||
> dedupe changes IR snapshots (duplicate libc decls collapse), so the
|
> dedupe changes IR snapshots (duplicate libc decls collapse), so the
|
||||||
> affected `.ir` files were regenerated. Gates: zig build test
|
> affected `.ir` files were regenerated. Gates: zig build test
|
||||||
> 426/426, tests/run_examples.sh 602/602, distribution repo 21/21.
|
> 426/426, tests/run_examples.sh 602/602, distribution repo 21/21.
|
||||||
> Boundary: comptime-interp (`#run`) foreign calls are untouched, and
|
> Boundary: comptime-interp (`#run`) extern calls are untouched, and
|
||||||
> indirect (fn-pointer) foreign calls don't synthesize — both can
|
> indirect (fn-pointer) extern calls don't synthesize — both can
|
||||||
> follow if ever needed.
|
> follow if ever needed.
|
||||||
|
|
||||||
## Design contract (Agra, 2026-06-12)
|
## Design contract (Agra, 2026-06-12)
|
||||||
@@ -48,20 +48,20 @@ for every libc/sqlite-style API that returns a nullable C string
|
|||||||
|
|
||||||
`[:0]u8` is an ALIAS for `string` (src/types.zig:145 — the `'['` arm
|
`[:0]u8` is an ALIAS for `string` (src/types.zig:145 — the `'['` arm
|
||||||
returns `.string_type`), i.e. a fat ptr+len value at the sx level. At a
|
returns `.string_type`), i.e. a fat ptr+len value at the sx level. At a
|
||||||
`#foreign` PARAM position the C-ABI lowering already thins it: sx
|
`extern` PARAM position the C-ABI lowering already thins it: sx
|
||||||
`string`/slices coerce to a single pointer and the length is dropped
|
`string`/slices coerce to a single pointer and the length is dropped
|
||||||
(src/backend/llvm/abi.zig, the `is_foreign_c_api` knob) — so
|
(src/backend/llvm/abi.zig, the `is_extern_c_api` knob) — so
|
||||||
`popen :: (cmd: [:0]u8, ...)` works and matches the design contract.
|
`popen :: (cmd: [:0]u8, ...)` works and matches the design contract.
|
||||||
|
|
||||||
The other two boundary positions are broken:
|
The other two boundary positions are broken:
|
||||||
|
|
||||||
### Defect A — `-> [:0]u8` foreign RETURN silently resolves to `u8`
|
### Defect A — `-> [:0]u8` extern RETURN silently resolves to `u8`
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
libc :: #library "c";
|
libc :: #library "c";
|
||||||
getenv_s :: (name: [:0]u8) -> [:0]u8 #foreign libc "getenv";
|
getenv_s :: (name: [:0]u8) -> [:0]u8 extern libc "getenv";
|
||||||
|
|
||||||
main :: () -> i32 {
|
main :: () -> i32 {
|
||||||
v := getenv_s("PATH");
|
v := getenv_s("PATH");
|
||||||
@@ -82,7 +82,7 @@ CLAUDE.md forbids).
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
libc :: #library "c";
|
libc :: #library "c";
|
||||||
getenv_opt :: (name: [:0]u8) -> ?[:0]u8 #foreign libc "getenv";
|
getenv_opt :: (name: [:0]u8) -> ?[:0]u8 extern libc "getenv";
|
||||||
|
|
||||||
main :: () -> i32 {
|
main :: () -> i32 {
|
||||||
p := getenv_opt("PATH");
|
p := getenv_opt("PATH");
|
||||||
@@ -104,11 +104,11 @@ a panic.
|
|||||||
1. Make the bracket spelling resolve identically everywhere — return
|
1. Make the bracket spelling resolve identically everywhere — return
|
||||||
position and optional child position must hit the same alias table
|
position and optional child position must hit the same alias table
|
||||||
that param position does (Defect A and the resolution half of B).
|
that param position does (Defect A and the resolution half of B).
|
||||||
2. Implement the boundary contract for returns: a foreign
|
2. Implement the boundary contract for returns: a extern
|
||||||
`-> [:0]u8` / `-> ?[:0]u8` receives ONE pointer from C; the sx-side
|
`-> [:0]u8` / `-> ?[:0]u8` receives ONE pointer from C; the sx-side
|
||||||
`string` is built by synthesizing the length (strlen) at the
|
`string` is built by synthesizing the length (strlen) at the
|
||||||
boundary, and for the optional a NULL pointer maps to `null`.
|
boundary, and for the optional a NULL pointer maps to `null`.
|
||||||
If (2) is deferred, foreign string/optional-string RETURNS must be
|
If (2) is deferred, extern string/optional-string RETURNS must be
|
||||||
rejected with a diagnostic naming the workaround (`?*u8`).
|
rejected with a diagnostic naming the workaround (`?*u8`).
|
||||||
|
|
||||||
## Workaround in use
|
## Workaround in use
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
A `#library` declaration in a module that is reached through TWO (or
|
A `#library` declaration in a module that is reached through TWO (or
|
||||||
more) levels of aliased `#import` never makes it into the build's
|
more) levels of aliased `#import` never makes it into the build's
|
||||||
library list: `sx build` emits no `-l<name>` on the link line (link
|
library list: `sx build` emits no `-l<name>` on the link line (link
|
||||||
fails with `Undefined symbols` for every `#foreign` fn of that
|
fails with `Undefined symbols` for every `extern` fn of that
|
||||||
library), and `sx run` skips the dlopen of that library (the JIT then
|
library), and `sx run` skips the dlopen of that library (the JIT then
|
||||||
resolves the foreign symbols only if some already-loaded image happens
|
resolves the extern symbol only if some already-loaded image happens
|
||||||
to export them).
|
to export them).
|
||||||
|
|
||||||
- Observed: `main → b :: #import "b.sx" → c :: #import "c.sx"` where
|
- Observed: `main → b :: #import "b.sx" → c :: #import "c.sx"` where
|
||||||
@@ -44,11 +44,11 @@ to export them).
|
|||||||
Three files in one directory; build `a.sx`.
|
Three files in one directory; build `a.sx`.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
// c.sx — declares the library + a foreign fn
|
// c.sx — declares the library + a extern fn
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
zlib :: #library "z";
|
zlib :: #library "z";
|
||||||
zlibVersion :: () -> ?cstring #foreign zlib "zlibVersion";
|
zlibVersion :: () -> ?cstring extern zlib "zlibVersion";
|
||||||
|
|
||||||
zver :: () -> string {
|
zver :: () -> string {
|
||||||
p := zlibVersion();
|
p := zlibVersion();
|
||||||
@@ -91,7 +91,7 @@ Found in the distribution repo the first time a product chain nested
|
|||||||
the SQLite bindings two aliases deep: `dist.sx → ops :: #import
|
the SQLite bindings two aliases deep: `dist.sx → ops :: #import
|
||||||
"release/ops.sx" → db :: #import "../repo/db.sx" → #import
|
"release/ops.sx" → db :: #import "../repo/db.sx" → #import
|
||||||
"../db/sqlite.sx"` loses `-lsqlite3` even though the bindings compile
|
"../db/sqlite.sx"` loses `-lsqlite3` even though the bindings compile
|
||||||
fine (the foreign wrappers ARE in main.o; only the link flag is gone).
|
fine (the extern wrappers ARE in main.o; only the link flag is gone).
|
||||||
|
|
||||||
## Investigation prompt
|
## Investigation prompt
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user