refactor(ffi-linkage): Phase 9.2b-fix — use is_extern (not new is_reference) for the runtime-class ref flag

Per user feedback: don't introduce new terminology. The RuntimeClassDecl
reference-vs-define flag (set by the postfix 'extern' modifier, == old prefix
'#foreign #objc_class') is named is_extern, matching the keyword that drives it
and the existing is_extern on VarDecl/IR. Renamed is_reference→is_extern,
is_reference_eff→is_extern_eff; updated the field comment. Snapshot-neutral; green.
This commit is contained in:
agra
2026-06-15 09:06:19 +03:00
parent d27be42a93
commit a15a868391
9 changed files with 34 additions and 34 deletions

View File

@@ -888,7 +888,7 @@ pub const RuntimeClassDecl = struct {
foreign_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL" foreign_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL"
runtime: RuntimeKind, runtime: RuntimeKind,
members: []const RuntimeClassMember = &.{}, members: []const RuntimeClassMember = &.{},
is_reference: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it is_extern: bool = false, // `#objc_class(…) extern` — class is provided by the runtime; we only reference it (vs `export`, which defines + registers a new sx class)
is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...) is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...)
/// True when the sx-side alias NAME was a backtick raw identifier — exempt /// True when the sx-side alias NAME was a backtick raw identifier — exempt
/// from the reserved-type-name decl check. /// from the reserved-type-name decl check.

View File

@@ -388,7 +388,7 @@ pub const Compilation = struct {
while (it.next()) |entry| { while (it.next()) |entry| {
const fcd = entry.value_ptr.*; const fcd = entry.value_ptr.*;
if (!fcd.is_main) continue; if (!fcd.is_main) continue;
if (fcd.is_reference) continue; if (fcd.is_extern) continue;
if (fcd.runtime != .jni_class) continue; if (fcd.runtime != .jni_class) continue;
if (seen.contains(fcd.foreign_path)) continue; if (seen.contains(fcd.foreign_path)) continue;
try seen.put(fcd.foreign_path, {}); try seen.put(fcd.foreign_path, {});

View File

@@ -389,7 +389,7 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
.foreign_path = "SxFoo", .foreign_path = "SxFoo",
.runtime = .objc_class, .runtime = .objc_class,
.members = &members, .members = &members,
.is_reference = false, .is_extern = false,
.is_main = false, .is_main = false,
}; };
@@ -421,7 +421,7 @@ test "lower: objcDefinedStateStructType handles empty field set" {
.foreign_path = "SxEmpty", .foreign_path = "SxEmpty",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = false, .is_extern = false,
.is_main = false, .is_main = false,
}; };
@@ -453,7 +453,7 @@ test "lower: objcDefinedStateStructType skips non-field members" {
.foreign_path = "SxMixed", .foreign_path = "SxMixed",
.runtime = .objc_class, .runtime = .objc_class,
.members = &members, .members = &members,
.is_reference = false, .is_extern = false,
.is_main = false, .is_main = false,
}; };
@@ -481,7 +481,7 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
.foreign_path = "NSString", .foreign_path = "NSString",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd); try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
@@ -514,7 +514,7 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
.foreign_path = "NSString", .foreign_path = "NSString",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd); try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
@@ -683,7 +683,7 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
.foreign_path = "NSString", .foreign_path = "NSString",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd); try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
@@ -699,7 +699,7 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
.foreign_path = "NSCopying", .foreign_path = "NSCopying",
.runtime = .objc_protocol, .runtime = .objc_protocol,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSCopying", &proto_fcd); try lowering.program_index.runtime_class_map.put("NSCopying", &proto_fcd);
@@ -731,7 +731,7 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
.foreign_path = "NSString", .foreign_path = "NSString",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd); try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
@@ -756,7 +756,7 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
.foreign_path = "NSCoding", .foreign_path = "NSCoding",
.runtime = .objc_protocol, .runtime = .objc_protocol,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try lowering.program_index.runtime_class_map.put("NSCoding", &proto_fcd); try lowering.program_index.runtime_class_map.put("NSCoding", &proto_fcd);

View File

@@ -207,7 +207,7 @@ pub fn checkRequiredEntryPoints(self: *Lowering) void {
var it = self.program_index.runtime_class_map.iterator(); var it = self.program_index.runtime_class_map.iterator();
while (it.next()) |entry| { while (it.next()) |entry| {
const fcd = entry.value_ptr.*; const fcd = entry.value_ptr.*;
if (fcd.is_main and !fcd.is_reference and fcd.runtime == .jni_class) return; if (fcd.is_main and !fcd.is_extern and fcd.runtime == .jni_class) return;
} }
if (self.diagnostics) |diags| { if (self.diagnostics) |diags| {

View File

@@ -270,7 +270,7 @@ pub fn lowerRuntimeMethodCall(
if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) { if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) {
return self.lowerObjcMethodCall(fcd, method, target, method_args, span); return self.lowerObjcMethodCall(fcd, method, target, method_args, span);
} }
if (!fcd.is_reference) { if (!fcd.is_extern) {
if (self.diagnostics) |d| { if (self.diagnostics) |d| {
d.addFmt(.err, span, "sx-defined classes on non-Obj-C runtimes can't yet be dispatched into (class '{s}', runtime '{s}')", .{ fcd.name, @tagName(fcd.runtime) }); d.addFmt(.err, span, "sx-defined classes on non-Obj-C runtimes can't yet be dispatched into (class '{s}', runtime '{s}')", .{ fcd.name, @tagName(fcd.runtime) });
} }
@@ -505,7 +505,7 @@ pub fn lowerObjcStaticCall(
// instead of going through `objc_msgSend` (which would land in the // instead of going through `objc_msgSend` (which would land in the
// +alloc IMP and use `__sx_default_context.allocator`). This honors // +alloc IMP and use `__sx_default_context.allocator`). This honors
// a surrounding `push Context.{ allocator = ... }`. // a surrounding `push Context.{ allocator = ... }`.
if (!fcd.is_reference and if (!fcd.is_extern and
fcd.runtime == .objc_class and fcd.runtime == .objc_class and
method_args.len == 0 and method_args.len == 0 and
std.mem.eql(u8, method.name, "alloc")) std.mem.eql(u8, method.name, "alloc"))
@@ -794,7 +794,7 @@ pub fn lowerSuperCall(
/// substituted to `*<ClassName>State` during body lowering (M1.2 A.2b). /// substituted to `*<ClassName>State` during body lowering (M1.2 A.2b).
pub fn registerRuntimeClassDecl(self: *Lowering, fcd: *const ast.RuntimeClassDecl) void { pub fn registerRuntimeClassDecl(self: *Lowering, fcd: *const ast.RuntimeClassDecl) void {
self.program_index.runtime_class_map.put(fcd.name, fcd) catch {}; self.program_index.runtime_class_map.put(fcd.name, fcd) catch {};
if (!fcd.is_reference and fcd.runtime == .objc_class) { if (!fcd.is_extern and fcd.runtime == .objc_class) {
if (self.module.lookupObjcDefinedClass(fcd.name) == null) { if (self.module.lookupObjcDefinedClass(fcd.name) == null) {
self.module.appendObjcDefinedClass(fcd.name, fcd); self.module.appendObjcDefinedClass(fcd.name, fcd);
// M2.3 — resolve the `#extends` alias to the actual // M2.3 — resolve the `#extends` alias to the actual
@@ -829,7 +829,7 @@ pub fn resolveObjcParentName(self: *Lowering, fcd: *const ast.RuntimeClassDecl)
for (fcd.members) |m| switch (m) { for (fcd.members) |m| switch (m) {
.extends => |alias| { .extends => |alias| {
if (self.program_index.runtime_class_map.get(alias)) |parent_fcd| { if (self.program_index.runtime_class_map.get(alias)) |parent_fcd| {
if (parent_fcd.is_reference) return parent_fcd.foreign_path; if (parent_fcd.is_extern) return parent_fcd.foreign_path;
// Sx-defined parent — its alias IS its Obj-C name. // Sx-defined parent — its alias IS its Obj-C name.
return parent_fcd.name; return parent_fcd.name;
} }
@@ -1037,7 +1037,7 @@ pub fn synthesizeJniMainStubs(self: *Lowering) void {
while (it.next()) |entry| { while (it.next()) |entry| {
const fcd = entry.value_ptr.*; const fcd = entry.value_ptr.*;
if (!fcd.is_main) continue; if (!fcd.is_main) continue;
if (fcd.is_reference) continue; if (fcd.is_extern) continue;
if (fcd.runtime != .jni_class) continue; if (fcd.runtime != .jni_class) continue;
if (seen.contains(fcd.foreign_path)) continue; if (seen.contains(fcd.foreign_path)) continue;
seen.put(fcd.foreign_path, {}) catch continue; seen.put(fcd.foreign_path, {}) catch continue;

View File

@@ -139,7 +139,7 @@ pub fn lookupObjcDefinedStateFieldOnPointer(self: *Lowering, obj_expr: *const as
const fcd = self.program_index.runtime_class_map.get(struct_name) orelse return null; const fcd = self.program_index.runtime_class_map.get(struct_name) orelse return null;
// Only sx-defined Obj-C classes have a state struct. Foreign // Only sx-defined Obj-C classes have a state struct. Foreign
// classes' fields are purely declaration metadata (no state). // classes' fields are purely declaration metadata (no state).
if (fcd.is_reference or fcd.runtime != .objc_class) return null; if (fcd.is_extern or fcd.runtime != .objc_class) return null;
// Skip property fields — those dispatch via the M2.2 getter/setter // Skip property fields — those dispatch via the M2.2 getter/setter
// path. Plain instance fields take the ivar+gep path. // path. Plain instance fields take the ivar+gep path.
for (fcd.members) |m| switch (m) { for (fcd.members) |m| switch (m) {

View File

@@ -73,7 +73,7 @@ test "ProgramIndex declaration maps round-trip (A1.1b)" {
.foreign_path = "NSString", .foreign_path = "NSString",
.runtime = .objc_class, .runtime = .objc_class,
.members = &.{}, .members = &.{},
.is_reference = true, .is_extern = true,
.is_main = false, .is_main = false,
}; };
try idx.runtime_class_map.put("NSString", &fcd); try idx.runtime_class_map.put("NSString", &fcd);

View File

@@ -263,10 +263,10 @@ pub const Parser = struct {
// removed — reference an existing class via the postfix `extern` modifier // removed — reference an existing class via the postfix `extern` modifier
// (`X :: #objc_class("…") extern { … }`) instead. `prefix_loc` pins the // (`X :: #objc_class("…") extern { … }`) instead. `prefix_loc` pins the
// diagnostic to the `#foreign` token (already consumed by the lookahead). // diagnostic to the `#foreign` token (already consumed by the lookahead).
if (prefix.is_reference) { if (prefix.is_extern) {
return self.failAt(prefix_loc, "`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead"); return self.failAt(prefix_loc, "`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
} }
return self.parseRuntimeClassDecl(name, start_pos, prefix.runtime, prefix.is_reference, prefix.is_main, name_is_raw); return self.parseRuntimeClassDecl(name, start_pos, prefix.runtime, prefix.is_extern, prefix.is_main, name_is_raw);
} }
// C-style union declaration // C-style union declaration
@@ -1295,7 +1295,7 @@ pub const Parser = struct {
const RuntimeClassPrefix = struct { const RuntimeClassPrefix = struct {
runtime: ast.RuntimeKind, runtime: ast.RuntimeKind,
is_reference: bool, is_extern: bool,
is_main: bool, is_main: bool,
}; };
@@ -1308,13 +1308,13 @@ pub const Parser = struct {
fn tryParseRuntimeClassPrefix(self: *Parser) ?RuntimeClassPrefix { fn tryParseRuntimeClassPrefix(self: *Parser) ?RuntimeClassPrefix {
// Peek ahead through modifier tokens to confirm a directive follows. // Peek ahead through modifier tokens to confirm a directive follows.
var lookahead_idx: usize = 0; var lookahead_idx: usize = 0;
var is_reference = false; var is_extern = false;
var is_main = false; var is_main = false;
while (true) { while (true) {
const tag = self.peekTag(lookahead_idx); const tag = self.peekTag(lookahead_idx);
switch (tag) { switch (tag) {
.hash_foreign => { .hash_foreign => {
is_reference = true; is_extern = true;
lookahead_idx += 1; lookahead_idx += 1;
}, },
.hash_jni_main => { .hash_jni_main => {
@@ -1328,7 +1328,7 @@ pub const Parser = struct {
// Commit: consume modifier tokens. // Commit: consume modifier tokens.
var i: usize = 0; var i: usize = 0;
while (i < lookahead_idx) : (i += 1) self.advance(); while (i < lookahead_idx) : (i += 1) self.advance();
return .{ .runtime = runtime, .is_reference = is_reference, .is_main = is_main }; return .{ .runtime = runtime, .is_extern = is_extern, .is_main = is_main };
} }
fn peekTag(self: *Parser, offset: usize) Tag { fn peekTag(self: *Parser, offset: usize) Tag {
@@ -1363,7 +1363,7 @@ pub const Parser = struct {
}; };
} }
fn parseRuntimeClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.RuntimeKind, is_reference: bool, is_main: bool, name_is_raw: bool) anyerror!*Node { fn parseRuntimeClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.RuntimeKind, is_extern: bool, is_main: bool, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip directive token self.advance(); // skip directive token
try self.expect(.l_paren); try self.expect(.l_paren);
@@ -1380,20 +1380,20 @@ pub const Parser = struct {
// `#foreign` modifier (mirrors `struct #compiler` postfix placement). // `#foreign` modifier (mirrors `struct #compiler` postfix placement).
// `… extern { … }` ⇒ reference an existing runtime class (== `#foreign`). // `… extern { … }` ⇒ reference an existing runtime class (== `#foreign`).
// `… export { … }` ⇒ define + register a new sx class (== no `#foreign`). // `… export { … }` ⇒ define + register a new sx class (== no `#foreign`).
// Maps straight onto the existing `is_reference` decision so lowering is // Maps straight onto the existing `is_extern` decision so lowering is
// unchanged. The legacy prefix `#foreign` form still works via the // unchanged. The legacy prefix `#foreign` form still works via the
// `is_reference` argument; interplay/diagnostics for combining them is Phase 4. // `is_extern` argument; interplay/diagnostics for combining them is Phase 4.
var is_reference_eff = is_reference; var is_extern_eff = is_extern;
if (self.current.tag == .kw_extern or self.current.tag == .kw_export) { if (self.current.tag == .kw_extern or self.current.tag == .kw_export) {
// Prefix `#foreign` and the postfix `extern`/`export` keyword are two // Prefix `#foreign` and the postfix `extern`/`export` keyword are two
// spellings of the same linkage axis — combining them is contradictory // spellings of the same linkage axis — combining them is contradictory
// (`#foreign`=import vs `export`=define) or redundant (`#foreign … extern`). // (`#foreign`=import vs `export`=define) or redundant (`#foreign … extern`).
// Reject at the postfix keyword rather than let it silently override. // Reject at the postfix keyword rather than let it silently override.
if (is_reference) { if (is_extern) {
const kw = if (self.current.tag == .kw_export) "export" else "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}); return self.failFmt("conflicting linkage: prefix '#foreign' cannot be combined with postfix '{s}'; use either '#foreign' or postfix 'extern'/'export', not both", .{kw});
} }
is_reference_eff = self.current.tag == .kw_extern; is_extern_eff = self.current.tag == .kw_extern;
self.advance(); self.advance();
} }
@@ -1624,7 +1624,7 @@ pub const Parser = struct {
.foreign_path = foreign_path, .foreign_path = foreign_path,
.runtime = runtime, .runtime = runtime,
.members = try members.toOwnedSlice(self.allocator), .members = try members.toOwnedSlice(self.allocator),
.is_reference = is_reference_eff, .is_extern = is_extern_eff,
.is_main = is_main, .is_main = is_main,
.is_raw = name_is_raw, .is_raw = name_is_raw,
} }); } });

View File

@@ -1334,14 +1334,14 @@ pub const Analyzer = struct {
}, },
.runtime_class_decl => |fd| { .runtime_class_decl => |fd| {
try self.addSymbol(fd.name, .type_alias, null, node.span); try self.addSymbol(fd.name, .type_alias, null, node.span);
if (fd.is_reference and fd.is_main) { if (fd.is_extern and fd.is_main) {
try self.diagnostics.append(self.allocator, .{ try self.diagnostics.append(self.allocator, .{
.level = .err, .level = .err,
.message = "'#foreign' and '#jni_main' / '#objc_main' are mutually exclusive — a foreign-referenced class can't be the app's main entry", .message = "'#foreign' and '#jni_main' / '#objc_main' are mutually exclusive — a foreign-referenced class can't be the app's main entry",
.span = node.span, .span = node.span,
}); });
} }
if (fd.is_reference) { if (fd.is_extern) {
for (fd.members) |m| switch (m) { for (fd.members) |m| switch (m) {
.method => |md| if (md.body != null) { .method => |md| if (md.body != null) {
try self.diagnostics.append(self.allocator, .{ try self.diagnostics.append(self.allocator, .{