refactor(ffi-linkage): Phase 9.3-src — purge 'foreign' from src/ comments + a user-facing diagnostic

Reword every 'foreign' comment to the extern/runtime-class vocabulary matching the
renamed identifiers (foreign call→extern call, foreign class→runtime class, foreign
path→runtime path, the #foreign-literal comment mentions → extern, etc.). Also fixes
two USER-FACING issues: the 'expected … #foreign … after type annotation' parse error
no longer advertises the removed keyword, and the Android 'no #jni_main' help
diagnostic now shows '#jni_class(…) extern' instead of the rejected '#foreign
#jni_class'. Removed the now-dead prefix-#foreign-vs-postfix conflict branch in
parseRuntimeClassDecl (the caller rejects #foreign before it runs).

src/ now contains 'foreign' ONLY in the hash_foreign token machinery + its 4
rejection messages — the deprecation mechanism (kept per the 9.0 recommendation; the
message MUST name #foreign to guide migration). Snapshot-neutral; suite green
(646 corpus / 444 unit, 0 failed).
This commit is contained in:
agra
2026-06-15 09:35:00 +03:00
parent e99383fcb4
commit dc51c4b5bf
35 changed files with 172 additions and 180 deletions

View File

@@ -250,19 +250,20 @@ pub const Parser = struct {
return self.parseProtocolDecl(name, start_pos, name_is_raw);
}
// Foreign-type binding with optional prefix modifiers:
// [#foreign | #jni_main]* (#jni_class / #jni_interface / #objc_class /
// Runtime-class binding with an optional `#jni_main` prefix modifier:
// [#jni_main]* (#jni_class / #jni_interface / #objc_class /
// #objc_protocol / #swift_class / #swift_struct / #swift_protocol) ("path") { body }
//
// Define-by-default: bare `#jni_class("...")` declares a new class (sx-defined).
// `#foreign` flips that to "reference an existing class on the foreign side."
// `#jni_main` flags the class as the launchable entry (Android Activity).
// Postfix `extern` flips that to "reference an existing class on the runtime
// side". `#jni_main` flags the class as the launchable entry (Android Activity).
const prefix_loc = self.current.loc;
if (self.tryParseRuntimeClassPrefix()) |prefix| {
// Phase 8 cutover: the prefix `#foreign` on a runtime-class directive is
// removed — reference an existing class via the postfix `extern` modifier
// (`X :: #objc_class("…") extern { … }`) instead. `prefix_loc` pins the
// diagnostic to the `#foreign` token (already consumed by the lookahead).
// Phase 8 cutover: the removed prefix linkage directive on a
// runtime-class directive — reference an existing class via the
// postfix `extern` modifier (`X :: #objc_class("…") extern { … }`)
// instead. `prefix_loc` pins the diagnostic to the removed-directive
// token (already consumed by the lookahead).
if (prefix.is_extern) {
return self.failAt(prefix_loc, "`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
}
@@ -320,9 +321,9 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi, .name_span = name_span, .is_raw = name_is_raw } });
}
// Phase 8 cutover: the prefix `#foreign` linkage directive has been
// removed — reject it with a migration message (was: `name :: type
// #foreign [lib] ["c_name"];`, now `name :: type extern [lib] ["c_name"];`).
// Phase 8 cutover: the removed prefix linkage directive on a
// const-with-type decl — reject it with a migration message (the
// postfix form is `name :: type extern [lib] ["c_name"];`).
if (self.current.tag == .hash_foreign) {
return self.fail("`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
}
@@ -411,16 +412,16 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = null, .is_raw = name_is_raw } });
}
// Phase 8 cutover: prefix `#foreign` on a data global is removed —
// reject it (was `name : type #foreign [lib] ["c_name"];`, now
// `name : type extern [lib] ["c_name"];`, handled by the `kw_extern` arm).
// Phase 8 cutover: the removed prefix linkage directive on a data
// global — reject it (the postfix form `name : type extern [lib]
// ["c_name"];` is handled by the `kw_extern` arm below).
if (self.current.tag == .hash_foreign) {
return self.fail("`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
}
if (self.current.tag == .kw_extern) {
// name : type extern [LIB] ["csym"]; (extern data global — the
// extern-named counterpart of `#foreign`; resolved at link time)
// extern-named counterpart of `extern`; resolved at link time)
self.advance();
var ext_lib: ?[]const u8 = null;
if (self.current.tag == .identifier) {
@@ -446,7 +447,7 @@ pub const Parser = struct {
} });
}
return self.fail("expected ':', '=', ';', '#foreign', or 'extern' after type annotation");
return self.fail("expected ':', '=', ';', or 'extern' after type annotation");
}
fn parseTypeExpr(self: *Parser) anyerror!*Node {
@@ -1299,9 +1300,9 @@ pub const Parser = struct {
is_main: bool,
};
/// Recognise an optional sequence of `#foreign` / `#jni_main` modifiers
/// Recognise an optional sequence of `extern` / `#jni_main` modifiers
/// followed by a type-introducer directive (`#jni_class`, `#objc_class`,
/// ...). Returns null if the current position isn't a foreign-class
/// ...). Returns null if the current position isn't a runtime-class
/// directive (possibly after modifiers). Consumes the modifier tokens
/// only when a runtime directive follows; otherwise leaves the parser
/// state untouched.
@@ -1368,31 +1369,22 @@ pub const Parser = struct {
try self.expect(.l_paren);
if (self.current.tag != .string_literal) {
return self.fail("expected string literal foreign-type path after directive");
return self.fail("expected string literal runtime-class type path after directive");
}
const raw = self.tokenSlice(self.current);
const runtime_path = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.r_paren);
// Phase 3 (FFI-linkage): optional postfix `extern` / `export` after the
// `#objc_class("X")` directive — the new spelling that replaces the prefix
// `#foreign` modifier (mirrors `struct #compiler` postfix placement).
// `… extern { … }` ⇒ reference an existing runtime class (== `#foreign`).
// `… export { … }` ⇒ define + register a new sx class (== no `#foreign`).
// Maps straight onto the existing `is_extern` decision so lowering is
// unchanged. The legacy prefix `#foreign` form still works via the
// `is_extern` argument; interplay/diagnostics for combining them is Phase 4.
// The postfix `extern` / `export` modifier after the `#objc_class("X")`
// directive (mirrors `struct #compiler` postfix placement):
// `… extern { … }` ⇒ reference an existing runtime class.
// `… export { … }` ⇒ define + register a new sx class (the default).
// Maps onto `is_extern`, threaded into the runtime_class_decl node. (The
// passed `is_extern` is always false here — the removed prefix linkage
// form is rejected by the caller before this point.)
var is_extern_eff = is_extern;
if (self.current.tag == .kw_extern or self.current.tag == .kw_export) {
// Prefix `#foreign` and the postfix `extern`/`export` keyword are two
// spellings of the same linkage axis — combining them is contradictory
// (`#foreign`=import vs `export`=define) or redundant (`#foreign … extern`).
// Reject at the postfix keyword rather than let it silently override.
if (is_extern) {
const kw = if (self.current.tag == .kw_export) "export" else "extern";
return self.failFmt("conflicting linkage: prefix '#foreign' cannot be combined with postfix '{s}'; use either '#foreign' or postfix 'extern'/'export', not both", .{kw});
}
is_extern_eff = self.current.tag == .kw_extern;
self.advance();
}
@@ -1586,7 +1578,7 @@ pub const Parser = struct {
try self.expect(.r_paren);
}
// Method body is optional: `;` → declaration (foreign or inherited
// Method body is optional: `;` → declaration (extern or inherited
// method we just want to call); `{ ... }` → sx-side block body
// for sx-defined classes; `=> expr;` → expression-body form
// (M1.0), lowered as a single-statement block holding `expr`.
@@ -1981,7 +1973,7 @@ pub const Parser = struct {
// Optional `[LIB] ["csym"]` tail after extern/export — a library-alias
// ident then a C symbol-name string, both optional (mirrors
// `#foreign LIB "csym"`). Stored on extern_lib/extern_name; the rename
// `extern LIB "csym"`). Stored on extern_lib/extern_name; the rename
// is consumed in `declareFunction`, the lib reference in Part B.
var extern_lib: ?[]const u8 = null;
var extern_name: ?[]const u8 = null;
@@ -1997,7 +1989,7 @@ pub const Parser = struct {
}
}
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, #compiler, or #foreign marker.
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, or #compiler marker.
// An `extern` import has NO body — just `;`. The extern_export modifier
// carries the linkage; we synthesize an empty block as the (non-optional)
// body placeholder, and lowering routes on the modifier rather than this
@@ -2026,7 +2018,7 @@ pub const Parser = struct {
self.advance();
break :blk try self.createNode(ci_start, .{ .compiler_expr = {} });
} else if (self.current.tag == .hash_foreign) {
// Phase 8 cutover: the fn-body `#foreign` marker is removed — reject it.
// Phase 8 cutover: the removed fn-body linkage marker — reject it.
// The import form is now the postfix `extern` keyword handled above
// (`f :: (…) -> R extern [LIB] ["csym"];`).
return self.fail("`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
@@ -3650,7 +3642,7 @@ pub const Parser = struct {
// ends with `;` directly after the param list — recognise it as a
// function def (not a constant) so it goes through parseFnDecl.
if (self.struct_default_compiler and tag == .semicolon) return true;
// `(T1, T2) -> R` without a trailing body (`{`, `=>`, or a foreign/
// `(T1, T2) -> R` without a trailing body (`{`, `=>`, or an extern/
// builtin marker) is a function-type literal, not a function def.
if (tag == .arrow) return self.hasFnBodyAfterArrow();
// `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;`
@@ -4216,7 +4208,7 @@ test "parse void function with builtin body" {
test "parse void function with extern import" {
// A postfix `extern LIB` fn import builds an empty-block body +
// extern_export = .extern_ + extern_lib. (Phase 8 removed the legacy
// prefix `#foreign` spelling that used to produce this same shape.)
// prefix `extern` spelling that used to produce this same shape.)
const source = "InitWindow :: (width: i32, height: i32, title: *u8) -> void extern rl;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();