feat(ffi-linkage)!: Phase 8.1 — parser hard-rejects #foreign (cutover)
The prefix #foreign linkage directive is removed. All four parse sites
(const-with-type, data global, fn body, runtime-class prefix) now reject it with
a migration message ('#foreign has been removed; use the postfix extern (import) /
export (define) linkage keyword instead'); added a span-aware failAt for the
runtime-class case (the lookahead consumes the token before the reject decision).
Greens the Phase 8.0 xfail 1176.
- Deleted obsolete tests: 1174 (#foreign+postfix conflict — unreachable now that
#foreign alone is rejected) and 1620 (#foreign nosuchunit lib-ref — superseded by
the extern twin 1231). Their assertions tested #foreign-specific behavior.
- Removed the GATE A→B unit test + lowerSrcToIr helper (lower.test.zig): it locked
#foreign ≡ extern through the migration; with #foreign gone there is nothing to
compare. Converted the in-source 'parse void function with foreign body' parser
test to the surviving postfix 'extern' spelling (identical resulting AST).
- specs.md + readme.md drop #foreign; document extern/export as the sole C-linkage
surface.
extern_export in parseFnDecl is now const (the fn-body arm that mutated it is gone).
Suite green (646 corpus / 444 unit, 0 failed). NOTE: comment-only #foreign in
examples + issues/*.md prose + internal foreign_* identifiers remain for Phase 9
(now unblocked: Decision 6 = purge everything).
This commit is contained in:
59
specs.md
59
specs.md
@@ -1055,7 +1055,7 @@ the chain leaves the checked zone.
|
||||
|
||||
`cstring` is the C-boundary string: ONE pointer to a null-terminated u8
|
||||
buffer — exactly C's `char *`. It is thin (8 bytes, no length field;
|
||||
`cstring_len` walks to the terminator, O(n)) and crosses `#foreign`
|
||||
`cstring_len` walks to the terminator, O(n)) and crosses `extern`
|
||||
boundaries verbatim in BOTH directions. `?cstring` is the nullable case
|
||||
and lowers to the same bare pointer (null = absent) — the natural type
|
||||
for `getenv`-style returns and optional `char *` parameters.
|
||||
@@ -1190,58 +1190,45 @@ Optionals work in `#run` blocks — `??`, `!`, `if val :=`, null checks all supp
|
||||
|
||||
### Foreign Function Interface (C Interop)
|
||||
|
||||
To call C functions, declare a library constant with `#library` and bind functions with `#foreign`:
|
||||
C linkage is expressed with the postfix `extern` (import) and `export` (define +
|
||||
expose) keywords. `extern` declares a symbol defined elsewhere — a C function or
|
||||
data global resolved at link time; `export` is its dual — **define** a symbol in
|
||||
sx and expose it under the C ABI so C (or asm, or another language) can call it.
|
||||
Both imply `callconv(.c)`, carry external linkage, and suppress the implicit sx
|
||||
context parameter. They are postfix modifiers, written where `callconv` would go.
|
||||
|
||||
```sx
|
||||
// Declare a named library constant
|
||||
libc :: #library "c";
|
||||
sdl :: #library "SDL3";
|
||||
|
||||
// Bind foreign functions — library ref is required
|
||||
socket :: (domain: i32, type: i32, protocol: i32) -> i32 #foreign libc;
|
||||
SDL_Init :: (flags: u32) -> bool #foreign sdl;
|
||||
|
||||
// Symbol renaming — optional second argument gives the C symbol name
|
||||
write_fd :: (fd: i32, buf: [*]u8, count: u64) -> i64 #foreign libc "write";
|
||||
```
|
||||
|
||||
- `#library "name"` must be assigned to a named constant. The library is passed to the linker (`-lname` on Unix, `name.lib` on Windows).
|
||||
- `#foreign lib_ref` declares a function as external C. The library reference is optional: when present it is passed to the linker (`-lname` on Unix); when omitted (`name :: (…) -> T #foreign;`), the symbol must resolve at link time from a framework or an already-linked / auto-detected library.
|
||||
- `#foreign lib_ref "c_symbol"` renames the binding: the sx function name differs from the C symbol. This avoids name collisions (e.g. POSIX `write` vs an sx builtin).
|
||||
|
||||
#### `extern` / `export` linkage keywords
|
||||
|
||||
`extern` and `export` are the keyword surface for C linkage. `extern` is the
|
||||
modern spelling of `#foreign` (import a symbol defined elsewhere); `export` is
|
||||
its dual — **define** a symbol in sx and expose it under the C ABI so C (or asm,
|
||||
or another language) can call it. Both imply `callconv(.c)`, carry external
|
||||
linkage, and suppress the implicit sx context parameter. They are postfix
|
||||
modifiers, written where `callconv` would go.
|
||||
|
||||
```sx
|
||||
// Functions — `extern` imports, `export` defines + exposes
|
||||
abs :: (x: i32) -> i32 extern; // import (== `#foreign`)
|
||||
write_fd :: (fd: i32, buf: [*]u8, n: u64) -> i64 extern libc "write"; // [LIB] ["csym"]
|
||||
socket :: (domain: i32, type: i32, protocol: i32) -> i32 extern libc;
|
||||
SDL_Init :: (flags: u32) -> bool extern sdl;
|
||||
abs :: (x: i32) -> i32 extern; // no LIB: resolves from a framework / auto-linked lib
|
||||
write_fd :: (fd: i32, buf: [*]u8, n: u64) -> i64 extern libc "write"; // [LIB] ["csym"] rename
|
||||
sx_square :: (x: i32) -> i32 export { x * x } // define; C can call `sx_square`
|
||||
triple_c :: (x: i32) -> i32 export "triple_c" { x * 3 } // export under a C name
|
||||
|
||||
// Data globals — `extern` imports an external global
|
||||
__stdinp : *void extern; // (== `<name> : <type> #foreign;`)
|
||||
__stdinp : *void extern;
|
||||
|
||||
// Aggregates (Obj-C / JNI runtime classes) — postfix after the directive
|
||||
NSObject :: #objc_class("NSObject") extern { alloc :: () -> *NSObject; } // reference
|
||||
SxFoo :: #objc_class("SxFoo") export { counter: i32; bump :: (self: *Self) { … } } // define
|
||||
```
|
||||
|
||||
- `extern` takes the same optional `[LIB] ["csym"]` tail as `#foreign`
|
||||
(`extern libc "write"`): a `#library` alias reference then a C symbol rename.
|
||||
The `#library` declaration + build-flag linking mechanism is a separate axis —
|
||||
`extern` *references* a library, it does not declare one.
|
||||
- `export "csym"` renames the exported symbol the same way (the C-visible name
|
||||
differs from the sx name).
|
||||
- On an aggregate, the prefix `#foreign` modifier and a postfix `extern`/`export`
|
||||
keyword are the same axis and cannot be combined: `#objc_class("X") extern` is
|
||||
exactly `#foreign #objc_class("X")`; writing both is a compile error.
|
||||
- `#library "name"` must be assigned to a named constant. The library is passed
|
||||
to the linker (`-lname` on Unix, `name.lib` on Windows).
|
||||
- `extern lib_ref` declares a function (or `<name> : <type> extern;` a data
|
||||
global) as an external C symbol. The library reference is optional: when present
|
||||
it is passed to the linker (`-lname` on Unix); when omitted, the symbol must
|
||||
resolve at link time from a framework or an already-linked / auto-detected
|
||||
library. The `#library` declaration + build-flag linking mechanism is a separate
|
||||
axis — `extern` *references* a library, it does not declare one.
|
||||
- `extern lib_ref "c_symbol"` (and `export "c_symbol"`) renames the binding: the
|
||||
sx name differs from the C symbol. This avoids name collisions (e.g. POSIX
|
||||
`write` vs an sx builtin) and gives an export a stable C-visible name.
|
||||
|
||||
### C Interop Type Mapping
|
||||
|
||||
|
||||
Reference in New Issue
Block a user