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:
agra
2026-06-15 08:06:05 +03:00
parent 8180faf839
commit 3811311e12
12 changed files with 69 additions and 340 deletions

View File

@@ -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