feat(ffi-linkage): fn-path accepts postfix extern/export + lib/name fields (Phase 1.0a)

parseFnDecl now calls parseOptionalExternExport() after the callconv
slot and stores the modifier on FnDecl.extern_export. For 'extern' the
body is ';' (an empty-block placeholder — the modifier carries the
linkage, no *_expr node, per the naming constraint). Both fn-decl
lookahead predicates (isFunctionDef, hasFnBodyAfterArrow) now treat
kw_extern/kw_export as fn-body markers beside kw_callconv, so
'(...) -> R extern;' is recognized as a fn def rather than a fn-type
const.

Per user feedback, decision 4 ("library separate") is REVISED: extern
carries an optional LIB + "csym" axis mirroring '#foreign LIB "csym"',
so it is a true #foreign superset (Gate A->B requirement — the Part B
migration of 466 #foreign uses across 6 libs must preserve each
symbol's library). Added FnDecl.extern_lib/extern_name and
VarDecl.extern_lib (beside is_extern/extern_name).

All unconsumed by lowering: extern parses, but a fn still errors at
sema (body produces no value). Suite green (443 unit / 633 corpus).
lock commit.
This commit is contained in:
agra
2026-06-14 13:02:42 +03:00
parent 62a3b46f6e
commit df6b675e67
4 changed files with 85 additions and 38 deletions

View File

@@ -143,6 +143,15 @@ pub const FnDecl = struct {
/// `callconv(...)` slot. `.none` for an ordinary sx-internal function.
/// Parsed in Phase 0.1; not consumed by the fn-decl path until Phase 1.
extern_export: ExternExportModifier = .none,
/// Optional library reference + symbol-name override for an `extern`/`export`
/// function, mirroring `#foreign LIB "csym"` (foreign_lib/foreign_name). Both
/// optional: `extern` alone resolves the sx name against the default-linked
/// libs; `extern LIB` names the source library; `extern "csym"` renames the
/// symbol. Required for `extern` to be a behavior-equivalent superset of
/// `#foreign` (Gate A→B) — the migration of 466 `#foreign` uses across 6 libs
/// must preserve each symbol's library. Parsed/consumed in Phase 1.2.
extern_lib: ?[]const u8 = null,
extern_name: ?[]const u8 = null,
/// Span of the function's name token, for the reserved-type-name decl
/// diagnostic. Synthesized decls (e.g. `#import c` foreign
/// functions, lowering-time objc/protocol method synthesis) leave it zero.
@@ -355,12 +364,15 @@ pub const VarDecl = struct {
is_foreign: bool = false,
foreign_lib: ?[]const u8 = null,
foreign_name: ?[]const u8 = null,
/// `extern`-global form `g : T extern ["csym"];` — a reference to a global
/// defined elsewhere (external linkage, resolved at link time). The new
/// extern-named surface; distinct from the legacy `#foreign` path above.
/// `extern_name` is the optional symbol-name override. Parsed in Phase 0.1;
/// not consumed by the var-decl path until Phase 1.2.
/// `extern`-global form `g : T extern [LIB] ["csym"];` — a reference to a
/// global defined elsewhere (external linkage, resolved at link time). The
/// new extern-named surface; distinct from the legacy `#foreign` path above.
/// `extern_lib` is the optional source-library reference and `extern_name`
/// the optional symbol-name override (mirroring foreign_lib/foreign_name, so
/// `extern` fully supersedes `#foreign`). Parsed in Phase 0.1; not consumed
/// by the var-decl path until Phase 1.2.
is_extern: bool = false,
extern_lib: ?[]const u8 = null,
extern_name: ?[]const u8 = null,
/// True when the binding name was written as a backtick raw identifier
/// (`` `i2 := … ``). A raw name is exempt from the reserved-type-name