From a15a868391fa48218458786688011c30613eeeb0 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 15 Jun 2026 09:06:19 +0300 Subject: [PATCH] =?UTF-8?q?refactor(ffi-linkage):=20Phase=209.2b-fix=20?= =?UTF-8?q?=E2=80=94=20use=20is=5Fextern=20(not=20new=20is=5Freference)=20?= =?UTF-8?q?for=20the=20runtime-class=20ref=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/ast.zig | 2 +- src/core.zig | 2 +- src/ir/lower.test.zig | 18 +++++++++--------- src/ir/lower/decl.zig | 2 +- src/ir/lower/ffi.zig | 10 +++++----- src/ir/lower/objc_class.zig | 2 +- src/ir/program_index.test.zig | 2 +- src/parser.zig | 26 +++++++++++++------------- src/sema.zig | 4 ++-- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/ast.zig b/src/ast.zig index b876a02..c62b2ab 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -888,7 +888,7 @@ pub const RuntimeClassDecl = struct { foreign_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL" runtime: RuntimeKind, 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 / ...) /// True when the sx-side alias NAME was a backtick raw identifier — exempt /// from the reserved-type-name decl check. diff --git a/src/core.zig b/src/core.zig index a182704..ece0a80 100644 --- a/src/core.zig +++ b/src/core.zig @@ -388,7 +388,7 @@ pub const Compilation = struct { while (it.next()) |entry| { const fcd = entry.value_ptr.*; if (!fcd.is_main) continue; - if (fcd.is_reference) continue; + if (fcd.is_extern) continue; if (fcd.runtime != .jni_class) continue; if (seen.contains(fcd.foreign_path)) continue; try seen.put(fcd.foreign_path, {}); diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index c2008fc..dbd1de0 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -389,7 +389,7 @@ test "lower: objcDefinedStateStructType collects user-declared fields" { .foreign_path = "SxFoo", .runtime = .objc_class, .members = &members, - .is_reference = false, + .is_extern = false, .is_main = false, }; @@ -421,7 +421,7 @@ test "lower: objcDefinedStateStructType handles empty field set" { .foreign_path = "SxEmpty", .runtime = .objc_class, .members = &.{}, - .is_reference = false, + .is_extern = false, .is_main = false, }; @@ -453,7 +453,7 @@ test "lower: objcDefinedStateStructType skips non-field members" { .foreign_path = "SxMixed", .runtime = .objc_class, .members = &members, - .is_reference = false, + .is_extern = false, .is_main = false, }; @@ -481,7 +481,7 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" { .foreign_path = "NSString", .runtime = .objc_class, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; 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", .runtime = .objc_class, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; 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", .runtime = .objc_class, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; 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", .runtime = .objc_protocol, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; 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", .runtime = .objc_class, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; 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", .runtime = .objc_protocol, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; try lowering.program_index.runtime_class_map.put("NSCoding", &proto_fcd); diff --git a/src/ir/lower/decl.zig b/src/ir/lower/decl.zig index 9ef55c0..444518c 100644 --- a/src/ir/lower/decl.zig +++ b/src/ir/lower/decl.zig @@ -207,7 +207,7 @@ pub fn checkRequiredEntryPoints(self: *Lowering) void { var it = self.program_index.runtime_class_map.iterator(); while (it.next()) |entry| { 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| { diff --git a/src/ir/lower/ffi.zig b/src/ir/lower/ffi.zig index a70dee8..d1f5023 100644 --- a/src/ir/lower/ffi.zig +++ b/src/ir/lower/ffi.zig @@ -270,7 +270,7 @@ pub fn lowerRuntimeMethodCall( if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) { return self.lowerObjcMethodCall(fcd, method, target, method_args, span); } - if (!fcd.is_reference) { + if (!fcd.is_extern) { 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) }); } @@ -505,7 +505,7 @@ pub fn lowerObjcStaticCall( // instead of going through `objc_msgSend` (which would land in the // +alloc IMP and use `__sx_default_context.allocator`). This honors // a surrounding `push Context.{ allocator = ... }`. - if (!fcd.is_reference and + if (!fcd.is_extern and fcd.runtime == .objc_class and method_args.len == 0 and std.mem.eql(u8, method.name, "alloc")) @@ -794,7 +794,7 @@ pub fn lowerSuperCall( /// substituted to `*State` during body lowering (M1.2 A.2b). pub fn registerRuntimeClassDecl(self: *Lowering, fcd: *const ast.RuntimeClassDecl) void { 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) { self.module.appendObjcDefinedClass(fcd.name, fcd); // 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) { .extends => |alias| { 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. return parent_fcd.name; } @@ -1037,7 +1037,7 @@ pub fn synthesizeJniMainStubs(self: *Lowering) void { while (it.next()) |entry| { const fcd = entry.value_ptr.*; if (!fcd.is_main) continue; - if (fcd.is_reference) continue; + if (fcd.is_extern) continue; if (fcd.runtime != .jni_class) continue; if (seen.contains(fcd.foreign_path)) continue; seen.put(fcd.foreign_path, {}) catch continue; diff --git a/src/ir/lower/objc_class.zig b/src/ir/lower/objc_class.zig index bc45b80..2aaaf6b 100644 --- a/src/ir/lower/objc_class.zig +++ b/src/ir/lower/objc_class.zig @@ -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; // Only sx-defined Obj-C classes have a state struct. Foreign // 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 // path. Plain instance fields take the ivar+gep path. for (fcd.members) |m| switch (m) { diff --git a/src/ir/program_index.test.zig b/src/ir/program_index.test.zig index b86d60e..5b115c6 100644 --- a/src/ir/program_index.test.zig +++ b/src/ir/program_index.test.zig @@ -73,7 +73,7 @@ test "ProgramIndex declaration maps round-trip (A1.1b)" { .foreign_path = "NSString", .runtime = .objc_class, .members = &.{}, - .is_reference = true, + .is_extern = true, .is_main = false, }; try idx.runtime_class_map.put("NSString", &fcd); diff --git a/src/parser.zig b/src/parser.zig index b9ae0af..2841236 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -263,10 +263,10 @@ pub const Parser = struct { // 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). - 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.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 @@ -1295,7 +1295,7 @@ pub const Parser = struct { const RuntimeClassPrefix = struct { runtime: ast.RuntimeKind, - is_reference: bool, + is_extern: bool, is_main: bool, }; @@ -1308,13 +1308,13 @@ pub const Parser = struct { fn tryParseRuntimeClassPrefix(self: *Parser) ?RuntimeClassPrefix { // Peek ahead through modifier tokens to confirm a directive follows. var lookahead_idx: usize = 0; - var is_reference = false; + var is_extern = false; var is_main = false; while (true) { const tag = self.peekTag(lookahead_idx); switch (tag) { .hash_foreign => { - is_reference = true; + is_extern = true; lookahead_idx += 1; }, .hash_jni_main => { @@ -1328,7 +1328,7 @@ pub const Parser = struct { // Commit: consume modifier tokens. var i: usize = 0; 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 { @@ -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 try self.expect(.l_paren); @@ -1380,20 +1380,20 @@ pub const Parser = struct { // `#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_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 - // `is_reference` argument; interplay/diagnostics for combining them is Phase 4. - var is_reference_eff = is_reference; + // `is_extern` argument; interplay/diagnostics for combining them is Phase 4. + 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_reference) { + 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_reference_eff = self.current.tag == .kw_extern; + is_extern_eff = self.current.tag == .kw_extern; self.advance(); } @@ -1624,7 +1624,7 @@ pub const Parser = struct { .foreign_path = foreign_path, .runtime = runtime, .members = try members.toOwnedSlice(self.allocator), - .is_reference = is_reference_eff, + .is_extern = is_extern_eff, .is_main = is_main, .is_raw = name_is_raw, } }); diff --git a/src/sema.zig b/src/sema.zig index fa0d6da..d7c699b 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1334,14 +1334,14 @@ pub const Analyzer = struct { }, .runtime_class_decl => |fd| { 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, .{ .level = .err, .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, }); } - if (fd.is_reference) { + if (fd.is_extern) { for (fd.members) |m| switch (m) { .method => |md| if (md.body != null) { try self.diagnostics.append(self.allocator, .{