refactor(ffi-linkage): Phase 9.2b — rename runtime-class fns + state → runtime_* / is_reference

The runtime-class object-model identifiers (Decision 5): parse/lower/find/resolve/
register/stamp fns Foreign→Runtime (parseRuntimeClassDecl, lowerRuntimeMethodCall,
findRuntimeMethodInChain, resolveRuntimeMethodReturnType, registerRuntimeClassDecl,
runtimeClassStructType, runtimeKindForOffset, …); state foreign_class_map→
runtime_class_map, current_foreign_class/_method→current_runtime_*, the
foreign_class_decl union variant→runtime_class_decl, foreign_method/static/instance/
class→runtime_*; and the reference-vs-define flag is_foreign→is_reference (+
is_foreign_eff→is_reference_eff) now that it only lives on RuntimeClassDecl.
Snapshot-neutral; suite green (646/444).

Remaining 9.2: the foreign_path family (coupled .sx hooks: jni_main_foreign_path_at
spans build.sx/bundle.sx/compiler_hooks.zig/specs.md) + the extern-ref validators
(checkForeignRefs etc. → Extern, linkage not runtime) + bare 'foreign' comments.
This commit is contained in:
agra
2026-06-15 09:01:04 +03:00
parent 3354446412
commit 5c8af6eb73
22 changed files with 205 additions and 205 deletions

View File

@@ -93,7 +93,7 @@ pub const Node = struct {
protocol_decl: ProtocolDecl,
impl_block: ImplBlock,
ffi_intrinsic_call: FfiIntrinsicCall,
foreign_class_decl: RuntimeClassDecl,
runtime_class_decl: RuntimeClassDecl,
jni_env_block: JniEnvBlock,
pub fn declName(self: Data) ?[]const u8 {
@@ -109,7 +109,7 @@ pub const Node = struct {
.ufcs_alias => |d| d.name,
.c_import_decl => |d| d.name,
.protocol_decl => |d| d.name,
.foreign_class_decl => |d| d.name,
.runtime_class_decl => |d| d.name,
else => null,
};
}
@@ -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_foreign: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it
is_reference: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it
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.

View File

@@ -295,7 +295,7 @@ pub const FfiCtors = struct {
// Parent class — pre-resolved Obj-C runtime name from
// lower.zig (M2.3 resolveObjcParentName). Stored on the
// cache entry so emit_llvm doesn't re-walk
// foreign_class_map here.
// runtime_class_map here.
const parent_name = entry_kv.parent_objc_name;
const parent_str_global = self.e.emitPrivateCString(parent_name, "OBJC_CLASS_NAME_");

View File

@@ -359,12 +359,12 @@ pub const Compilation = struct {
return module;
}
/// Walk `lowering.program_index.foreign_class_map` and render Java sources for every
/// Walk `lowering.program_index.runtime_class_map` and render Java sources for every
/// `#jni_main #jni_class("...")` declaration. Renders happen here so the
/// AST + class-registry snapshot stay confined to the lowering pass; the
/// downstream APK pipeline only needs `{foreign_path, java_source}` pairs.
fn collectJniMainEmissions(self: *Compilation, lowering: *ir.Lowering) !void {
// `foreign_class_map` registers each decl under bare + qualified names —
// `runtime_class_map` registers each decl under bare + qualified names —
// dedupe by foreign_path so a single decl emits one .java.
var seen = std.StringHashMap(void).init(self.allocator);
defer seen.deinit();
@@ -373,7 +373,7 @@ pub const Compilation = struct {
// and `#extends Alias` resolution.
var registry = std.StringHashMap([]const u8).init(self.allocator);
defer registry.deinit();
var it_reg = lowering.program_index.foreign_class_map.iterator();
var it_reg = lowering.program_index.runtime_class_map.iterator();
while (it_reg.next()) |entry| {
try registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path);
}
@@ -384,11 +384,11 @@ pub const Compilation = struct {
// .so loading via another class.
const lib_name = libNameFromOutputPath(self.target_config.output_path);
var it = lowering.program_index.foreign_class_map.iterator();
var it = lowering.program_index.runtime_class_map.iterator();
while (it.next()) |entry| {
const fcd = entry.value_ptr.*;
if (!fcd.is_main) continue;
if (fcd.is_foreign) continue;
if (fcd.is_reference) continue;
if (fcd.runtime != .jni_class) continue;
if (seen.contains(fcd.foreign_path)) continue;
try seen.put(fcd.foreign_path, {});

View File

@@ -371,7 +371,7 @@ pub const ResolvedModule = struct {
/// symbol, not split into a duplicate `__stdinp.1`.
fn isPerSourceDecl(decl: *const Node) bool {
return switch (decl.data) {
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .runtime_class_decl => true,
.const_decl => |cd| cd.value.data != .fn_decl,
else => false,
};
@@ -463,7 +463,7 @@ pub const RawDeclRef = union(enum) {
union_decl: *const ast.UnionDecl,
error_set_decl: *const ast.ErrorSetDecl,
protocol_decl: *const ast.ProtocolDecl,
foreign_class_decl: *const ast.RuntimeClassDecl,
runtime_class_decl: *const ast.RuntimeClassDecl,
namespace_decl: *const ast.NamespaceDecl,
};
@@ -513,7 +513,7 @@ pub fn rawDeclRefOf(decl: *const Node) ?RawDeclRef {
.union_decl => |*d| .{ .union_decl = d },
.error_set_decl => |*d| .{ .error_set_decl = d },
.protocol_decl => |*d| .{ .protocol_decl = d },
.foreign_class_decl => |*d| .{ .foreign_class_decl = d },
.runtime_class_decl => |*d| .{ .runtime_class_decl = d },
.namespace_decl => |*d| .{ .namespace_decl = d },
else => null,
};
@@ -592,7 +592,7 @@ pub const DeclKind = enum {
@"union",
error_set,
protocol,
foreign_class,
runtime_class,
namespace,
};
@@ -606,7 +606,7 @@ fn declKindOf(ref: RawDeclRef) DeclKind {
.union_decl => .@"union",
.error_set_decl => .error_set,
.protocol_decl => .protocol,
.foreign_class_decl => .foreign_class,
.runtime_class_decl => .runtime_class,
.namespace_decl => .namespace,
};
}
@@ -815,9 +815,9 @@ fn stampFnBodySource(decl: *Node, file_path: []const u8) void {
// An sx-defined `#objc_class` / `#jni_class`: its IMP trampolines are
// emitted at lowering time (possibly from another module's context), so
// record the defining path AND stamp each method body (E4).
.foreign_class_decl => {
decl.data.foreign_class_decl.source_file = file_path;
stampForeignClassMethodSources(decl.data.foreign_class_decl, file_path);
.runtime_class_decl => {
decl.data.runtime_class_decl.source_file = file_path;
stampRuntimeClassMethodSources(decl.data.runtime_class_decl, file_path);
},
.const_decl => |cd| switch (cd.value.data) {
.fn_decl => |fd| fd.body.source_file = file_path,
@@ -827,9 +827,9 @@ fn stampFnBodySource(decl: *Node, file_path: []const u8) void {
// bodies need the defining path stamped just like a top-level fn.
.struct_decl => |sd| stampStructMethodSources(sd, file_path),
.protocol_decl => cd.value.data.protocol_decl.source_file = file_path,
.foreign_class_decl => {
cd.value.data.foreign_class_decl.source_file = file_path;
stampForeignClassMethodSources(cd.value.data.foreign_class_decl, file_path);
.runtime_class_decl => {
cd.value.data.runtime_class_decl.source_file = file_path;
stampRuntimeClassMethodSources(cd.value.data.runtime_class_decl, file_path);
},
else => {},
},
@@ -853,7 +853,7 @@ fn stampStructMethodSources(sd: ast.StructDecl, file_path: []const u8) void {
/// Stamp the defining module path onto every bodied method of an sx-defined
/// foreign class, so the method's sx body lowers in the class's own module.
fn stampForeignClassMethodSources(fcd: ast.RuntimeClassDecl, file_path: []const u8) void {
fn stampRuntimeClassMethodSources(fcd: ast.RuntimeClassDecl, file_path: []const u8) void {
for (fcd.members) |m| {
if (m == .method) {
if (m.method.body) |b| b.source_file = file_path;

View File

@@ -360,7 +360,7 @@ test "plan: foreign-class instance vs static dispatch" {
.{ .method = .{ .name = "stringWithUTF8String", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "i64"), .is_static = true } },
};
var fcd = ast.RuntimeClassDecl{ .name = "NSString", .foreign_path = "NSString", .runtime = .objc_class, .members = &members };
l.program_index.foreign_class_map.put("NSString", &fcd) catch unreachable;
l.program_index.runtime_class_map.put("NSString", &fcd) catch unreachable;
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("NSString"), .fields = &.{} } });
// Instance: `cast(NSString, _).length` — receiver prepended.
@@ -368,9 +368,9 @@ test "plan: foreign-class instance vs static dispatch" {
const recv = callNode(alloc, ident(alloc, "cast"), &[_]*Node{ typeExpr(alloc, "NSString"), intLit(alloc, 0) });
const call = callNode(alloc, fieldAccess(alloc, recv, "length"), &.{});
const p = cr.plan(&call.data.call);
try std.testing.expectEqual(CallPlan.Kind.foreign_instance, p.kind);
try std.testing.expectEqualStrings("length", p.target.foreign_method.name);
try std.testing.expect(!p.target.foreign_method.is_static);
try std.testing.expectEqual(CallPlan.Kind.runtime_instance, p.kind);
try std.testing.expectEqualStrings("length", p.target.runtime_method.name);
try std.testing.expect(!p.target.runtime_method.is_static);
try std.testing.expectEqual(TypeId.i64, p.return_type);
try std.testing.expect(p.prepends_receiver);
}
@@ -378,9 +378,9 @@ test "plan: foreign-class instance vs static dispatch" {
{
const call = callNode(alloc, fieldAccess(alloc, ident(alloc, "NSString"), "stringWithUTF8String"), &.{});
const p = cr.plan(&call.data.call);
try std.testing.expectEqual(CallPlan.Kind.foreign_static, p.kind);
try std.testing.expectEqualStrings("stringWithUTF8String", p.target.foreign_method.name);
try std.testing.expect(p.target.foreign_method.is_static);
try std.testing.expectEqual(CallPlan.Kind.runtime_static, p.kind);
try std.testing.expectEqualStrings("stringWithUTF8String", p.target.runtime_method.name);
try std.testing.expect(p.target.runtime_method.is_static);
try std.testing.expect(!p.prepends_receiver);
}
}

View File

@@ -51,8 +51,8 @@ pub const CallPlan = struct {
/// type prefix). Distinct from `namespace_fn` precisely because the
/// receiver IS prepended (`prepends_receiver`).
free_fn_ufcs,
foreign_instance,
foreign_static,
runtime_instance,
runtime_static,
/// `pkg.fn(args)` — the receiver is a namespace / module prefix, NOT a
/// value, so nothing is prepended.
namespace_fn,
@@ -82,7 +82,7 @@ pub const CallPlan = struct {
/// Protocol method, by index in the protocol's method table.
protocol_method: u32,
/// Foreign-class method (Obj-C / JNI), with its static-ness.
foreign_method: struct { name: []const u8, is_static: bool },
runtime_method: struct { name: []const u8, is_static: bool },
/// Enum / tagged-union type under construction.
constructed: TypeId,
};
@@ -254,13 +254,13 @@ pub const CallResolver = struct {
const inner_info = self.l.module.types.get(recv_inner);
if (inner_info == .@"struct") {
const sn = self.l.module.types.getString(inner_info.@"struct".name);
if (self.l.program_index.foreign_class_map.get(sn)) |fcd| {
if (self.l.program_index.runtime_class_map.get(sn)) |fcd| {
for (fcd.members) |m| switch (m) {
.method => |md| if (!md.is_static and std.mem.eql(u8, md.name, cfa.field)) {
return .{
.kind = .foreign_instance,
.return_type = self.l.resolveForeignMethodReturnType(fcd, md),
.target = .{ .foreign_method = .{ .name = md.name, .is_static = false } },
.kind = .runtime_instance,
.return_type = self.l.resolveRuntimeMethodReturnType(fcd, md),
.target = .{ .runtime_method = .{ .name = md.name, .is_static = false } },
.prepends_receiver = true,
};
},
@@ -396,13 +396,13 @@ pub const CallResolver = struct {
};
if (type_name) |tn| {
// Foreign-class static method: `Alias.static_method(args)`.
if (self.l.program_index.foreign_class_map.get(tn)) |fcd| {
if (self.l.program_index.runtime_class_map.get(tn)) |fcd| {
for (fcd.members) |m| switch (m) {
.method => |md| if (md.is_static and std.mem.eql(u8, md.name, cfa.field)) {
return .{
.kind = .foreign_static,
.return_type = self.l.resolveForeignMethodReturnType(fcd, md),
.target = .{ .foreign_method = .{ .name = md.name, .is_static = true } },
.kind = .runtime_static,
.return_type = self.l.resolveRuntimeMethodReturnType(fcd, md),
.target = .{ .runtime_method = .{ .name = md.name, .is_static = true } },
};
},
else => {},

View File

@@ -179,7 +179,7 @@ pub const ObjcLowering = struct {
const is_objc_obj = blk: {
if (pointee_info != .@"struct") break :blk false;
const name = self.l.module.types.getString(pointee_info.@"struct".name);
break :blk self.l.program_index.foreign_class_map.get(name) != null;
break :blk self.l.program_index.runtime_class_map.get(name) != null;
};
if (is_objc_obj) {
try out.append(self.l.alloc, '@');
@@ -319,7 +319,7 @@ pub const ObjcLowering = struct {
const pointee_info = self.l.module.types.get(ptr_info.pointer.pointee);
if (pointee_info != .@"struct") return false;
const struct_name = self.l.module.types.getString(pointee_info.@"struct".name);
const fcd = self.l.program_index.foreign_class_map.get(struct_name) orelse return false;
const fcd = self.l.program_index.runtime_class_map.get(struct_name) orelse return false;
return fcd.runtime == .objc_class or fcd.runtime == .objc_protocol;
}
@@ -383,7 +383,7 @@ pub const ObjcLowering = struct {
const pointee_info = self.l.module.types.get(pointee);
if (pointee_info != .@"struct") break :blk false;
const struct_name = self.l.module.types.getString(pointee_info.@"struct".name);
const fcd = self.l.program_index.foreign_class_map.get(struct_name) orelse break :blk false;
const fcd = self.l.program_index.runtime_class_map.get(struct_name) orelse break :blk false;
break :blk fcd.runtime == .objc_class or fcd.runtime == .objc_protocol;
};

View File

@@ -1,7 +1,7 @@
// Tests for jni_java_emit.zig — #jni_main pipeline slice 1.
// Locks in the Java source emitted from `RuntimeClassDecl` AST nodes:
// package split, class header, @Override delegate pattern, primitive
// type mapping, cross-class refs through the foreign_class registry.
// type mapping, cross-class refs through the runtime_class registry.
const std = @import("std");
const ast = @import("../ast.zig");

View File

@@ -389,7 +389,7 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
.foreign_path = "SxFoo",
.runtime = .objc_class,
.members = &members,
.is_foreign = false,
.is_reference = false,
.is_main = false,
};
@@ -421,7 +421,7 @@ test "lower: objcDefinedStateStructType handles empty field set" {
.foreign_path = "SxEmpty",
.runtime = .objc_class,
.members = &.{},
.is_foreign = false,
.is_reference = false,
.is_main = false,
};
@@ -453,7 +453,7 @@ test "lower: objcDefinedStateStructType skips non-field members" {
.foreign_path = "SxMixed",
.runtime = .objc_class,
.members = &members,
.is_foreign = false,
.is_reference = false,
.is_main = false,
};
@@ -481,10 +481,10 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
// Return *NSString, no args: "@@:"
const e1 = try lowering.objc().objcTypeEncodingFromSignature(ns_ptr, &.{}, null);
@@ -514,10 +514,10 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
// `?i64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary.
const opt_i64 = module.types.optionalOf(.i64);
@@ -683,10 +683,10 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
try std.testing.expect(lowering.objc().isObjcClassPointer(ns_ptr));
// *NSCopying where NSCopying is a registered Obj-C *protocol* → also true
@@ -699,10 +699,10 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
.foreign_path = "NSCopying",
.runtime = .objc_protocol,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSCopying", &proto_fcd);
try lowering.program_index.runtime_class_map.put("NSCopying", &proto_fcd);
try std.testing.expect(lowering.objc().isObjcClassPointer(proto_ptr));
// *Plain where Plain is a non-foreign struct → false.
@@ -731,10 +731,10 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.runtime_class_map.put("NSString", &ns_fcd);
// Primitive field, no modifiers → assign (the non-object default).
const prim = ast.RuntimeFieldDecl{ .name = "count", .field_type = typeKeyword(alloc, "i32"), .is_property = true };
@@ -756,10 +756,10 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
.foreign_path = "NSCoding",
.runtime = .objc_protocol,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try lowering.program_index.foreign_class_map.put("NSCoding", &proto_fcd);
try lowering.program_index.runtime_class_map.put("NSCoding", &proto_fcd);
const proto_ty = typeKeyword(alloc, "*NSCoding");
defer alloc.destroy(proto_ty);
const proto_default = ast.RuntimeFieldDecl{ .name = "coder", .field_type = proto_ty, .is_property = true };

View File

@@ -275,8 +275,8 @@ pub const Lowering = struct {
trace_clear_fid: ?FuncId = null, // extern `sx_trace_clear`
needs_trace_runtime: bool = false, // set when lowering emits a trace push/clear; signals Compilation to auto-link sx_trace.c
chain_fail_target: ?ChainFailTarget = null, // ERR E2.4: when set, a failable `or` chain routes its TOTAL failure here (an absorbing consumer like `catch`) instead of propagating to the function
current_foreign_class: ?*const ast.RuntimeClassDecl = null, // set while lowering a `#jni_main` (or any sx-defined `#jni_class`) bodied method — `super.method(args)` dispatch resolves the parent class against this fcd's `#extends`
current_foreign_method: ?ast.RuntimeMethodDecl = null, // the specific method whose body is being lowered; `super.<same_name>(...)` reuses its signature
current_runtime_class: ?*const ast.RuntimeClassDecl = null, // set while lowering a `#jni_main` (or any sx-defined `#jni_class`) bodied method — `super.method(args)` dispatch resolves the parent class against this fcd's `#extends`
current_runtime_method: ?ast.RuntimeMethodDecl = null, // the specific method whose body is being lowered; `super.<same_name>(...)` reuses its signature
type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId)
current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch)
force_block_value: bool = false, // set by lowerBlockValue to extract if-else values
@@ -844,9 +844,9 @@ pub const Lowering = struct {
// lowerFieldAccess to go through the `__sx_state` ivar
// (object_getIvar + struct_gep) when needed — see M1.2 A.3.
if (node.data == .type_expr and std.mem.eql(u8, node.data.type_expr.name, "Self")) {
if (self.current_foreign_class) |fcd| {
if (self.current_runtime_class) |fcd| {
if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) {
return self.foreignClassStructType(fcd);
return self.runtimeClassStructType(fcd);
}
}
}
@@ -1770,15 +1770,15 @@ pub const Lowering = struct {
pub const getSelRegisterNameFid = lower_ffi.getSelRegisterNameFid;
pub const lowerFfiIntrinsicCall = lower_ffi.lowerFfiIntrinsicCall;
pub const lowerJniCall = lower_ffi.lowerJniCall;
pub const lowerForeignMethodCall = lower_ffi.lowerForeignMethodCall;
pub const resolveForeignClassMemberType = lower_ffi.resolveForeignClassMemberType;
pub const resolveForeignMethodReturnType = lower_ffi.resolveForeignMethodReturnType;
pub const foreignClassStructType = lower_ffi.foreignClassStructType;
pub const lowerRuntimeMethodCall = lower_ffi.lowerRuntimeMethodCall;
pub const resolveRuntimeClassMemberType = lower_ffi.resolveRuntimeClassMemberType;
pub const resolveRuntimeMethodReturnType = lower_ffi.resolveRuntimeMethodReturnType;
pub const runtimeClassStructType = lower_ffi.runtimeClassStructType;
pub const lowerObjcMethodCall = lower_ffi.lowerObjcMethodCall;
pub const lowerObjcStaticCall = lower_ffi.lowerObjcStaticCall;
pub const lowerForeignStaticCall = lower_ffi.lowerForeignStaticCall;
pub const lowerRuntimeStaticCall = lower_ffi.lowerRuntimeStaticCall;
pub const lowerSuperCall = lower_ffi.lowerSuperCall;
pub const registerForeignClassDecl = lower_ffi.registerForeignClassDecl;
pub const registerRuntimeClassDecl = lower_ffi.registerRuntimeClassDecl;
pub const resolveObjcParentName = lower_ffi.resolveObjcParentName;
pub const declareObjcDefinedStateIvarGlobal = lower_ffi.declareObjcDefinedStateIvarGlobal;
pub const declareObjcDefinedClassGlobal = lower_ffi.declareObjcDefinedClassGlobal;
@@ -1786,15 +1786,15 @@ pub const Lowering = struct {
pub const synthesizeFnDeclFromObjcMethod = lower_ffi.synthesizeFnDeclFromObjcMethod;
pub const lookupObjcDefinedClassForMethod = lower_ffi.lookupObjcDefinedClassForMethod;
pub const getJniEnvTlFids = lower_ffi.getJniEnvTlFids;
pub const registerNamespacedForeignClasses = lower_ffi.registerNamespacedForeignClasses;
pub const registerNamespacedRuntimeClasses = lower_ffi.registerNamespacedRuntimeClasses;
pub const synthesizeJniMainStubs = lower_ffi.synthesizeJniMainStubs;
pub const synthesizeJniMainStub = lower_ffi.synthesizeJniMainStub;
// --- moved to lower/objc_class.zig (lower_objc_class) ---
pub const lowerObjcDefinedClassMethods = lower_objc_class.lowerObjcDefinedClassMethods;
pub const lookupObjcPropertyOnPointer = lower_objc_class.lookupObjcPropertyOnPointer;
pub const findForeignMethodInChain = lower_objc_class.findForeignMethodInChain;
pub const findForeignPropertyInChain = lower_objc_class.findForeignPropertyInChain;
pub const findRuntimeMethodInChain = lower_objc_class.findRuntimeMethodInChain;
pub const findRuntimePropertyInChain = lower_objc_class.findRuntimePropertyInChain;
pub const lookupObjcDefinedStateFieldOnPointer = lower_objc_class.lookupObjcDefinedStateFieldOnPointer;
pub const lowerObjcDefinedStateFieldRead = lower_objc_class.lowerObjcDefinedStateFieldRead;
pub const lowerObjcDefinedStateForObj = lower_objc_class.lowerObjcDefinedStateForObj;

View File

@@ -610,10 +610,10 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
// NewObject. Falls through to existing paths when no match.
if (fa.object.data == .identifier) {
const alias = fa.object.data.identifier.name;
if (self.program_index.foreign_class_map.get(alias)) |fcd| {
if (self.program_index.runtime_class_map.get(alias)) |fcd| {
for (fcd.members) |m| switch (m) {
.method => |md| if (md.is_static and std.mem.eql(u8, md.name, fa.field)) {
return self.lowerForeignStaticCall(fcd, md, args.items, c.callee.span);
return self.lowerRuntimeStaticCall(fcd, md, args.items, c.callee.span);
},
else => {},
};
@@ -896,9 +896,9 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
// (or its parallel forms). Routes to the JNI dispatch
// shape, descriptor derived from the sx signature.
const struct_name = self.getStructTypeName(obj_ty);
if (struct_name) |sname_for_foreign| {
if (self.program_index.foreign_class_map.get(sname_for_foreign)) |fcd| {
return self.lowerForeignMethodCall(fcd, fa.field, obj, args.items, c.callee.span);
if (struct_name) |sname_for_runtime| {
if (self.program_index.runtime_class_map.get(sname_for_runtime)) |fcd| {
return self.lowerRuntimeMethodCall(fcd, fa.field, obj, args.items, c.callee.span);
}
}
@@ -2237,17 +2237,17 @@ pub fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call, sel_author: ?*
}
if (self.getStructTypeName(obj_ty)) |sname| {
// Foreign-class receiver (`#objc_class` / `#jni_class` / etc.):
// resolve the method from `foreign_class_map` walking `#extends`.
// resolve the method from `runtime_class_map` walking `#extends`.
// Without this path, `target_type` for each arg falls back to
// whatever `self.target_type` was on entry — typically the
// enclosing fn's return type — which silently truncates `xx ptr`
// casts inside e.g. a `BOOL`-returning method body.
if (self.program_index.foreign_class_map.get(sname)) |fcd| {
if (self.findForeignMethodInChain(fcd, fa.field)) |found| {
if (self.program_index.runtime_class_map.get(sname)) |fcd| {
if (self.findRuntimeMethodInChain(fcd, fa.field)) |found| {
const md = found.method;
const saved_fc = self.current_foreign_class;
defer self.current_foreign_class = saved_fc;
self.current_foreign_class = found.fcd;
const saved_fc = self.current_runtime_class;
defer self.current_runtime_class = saved_fc;
self.current_runtime_class = found.fcd;
const user_param_start: usize = if (md.is_static) 0 else 1;
if (md.params.len > user_param_start) {
var types_list = std.ArrayList(TypeId).empty;

View File

@@ -204,10 +204,10 @@ pub fn checkRequiredEntryPoints(self: *Lowering) void {
const tc = self.target_config orelse return;
if (!tc.isAndroid()) return;
var it = self.program_index.foreign_class_map.iterator();
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_foreign and fcd.runtime == .jni_class) return;
if (fcd.is_main and !fcd.is_reference and fcd.runtime == .jni_class) return;
}
if (self.diagnostics) |diags| {
@@ -341,11 +341,11 @@ pub fn lowerDecls(self: *Lowering, decls: []const *const Node) void {
.impl_block => {
self.protocolResolver().registerImplBlock(&decl.data.impl_block, is_imported, decl);
},
.foreign_class_decl => {
self.registerForeignClassDecl(&decl.data.foreign_class_decl);
.runtime_class_decl => {
self.registerRuntimeClassDecl(&decl.data.runtime_class_decl);
},
.namespace_decl => |ns| {
self.registerNamespacedForeignClasses(ns);
self.registerNamespacedRuntimeClasses(ns);
if (self.main_file != null) {
self.registerNamespaceQualifiedFns(ns.name, ns.own_decls);
self.lowerDecls(ns.decls);
@@ -769,11 +769,11 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
.impl_block => {
self.protocolResolver().registerImplBlock(&decl.data.impl_block, is_imported, decl);
},
.foreign_class_decl => {
self.registerForeignClassDecl(&decl.data.foreign_class_decl);
.runtime_class_decl => {
self.registerRuntimeClassDecl(&decl.data.runtime_class_decl);
},
.namespace_decl => |ns| {
self.registerNamespacedForeignClasses(ns);
self.registerNamespacedRuntimeClasses(ns);
if (self.main_file != null) {
self.scanDecls(ns.decls);
self.registerNamespaceQualifiedFns(ns.name, ns.own_decls);
@@ -1746,7 +1746,7 @@ pub fn selectNominalLeaf(self: *Lowering, name: []const u8, from: []const u8, ra
/// it is recognised via `type_aliases_by_source` separately from named types.
pub fn isNamedTypeKind(raw: resolver_mod.RawDeclRef) bool {
return switch (raw) {
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .runtime_class_decl => true,
.fn_decl, .const_decl, .var_decl, .namespace_decl => false,
};
}
@@ -1774,7 +1774,7 @@ pub fn namedRefTid(self: *Lowering, ref: resolver_mod.RawDeclRef, name: []const
.struct_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
.enum_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
.union_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
.error_set_decl, .protocol_decl, .foreign_class_decl => table.findByName(table.internString(name)),
.error_set_decl, .protocol_decl, .runtime_class_decl => table.findByName(table.internString(name)),
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
};
}
@@ -2288,15 +2288,15 @@ pub fn lazyLowerFunction(self: *Lowering, name: []const u8) void {
// Already lowered?
if (self.lowered_functions.contains(name)) return;
// For sx-defined `#objc_class` methods, pin current_foreign_class
// For sx-defined `#objc_class` methods, pin current_runtime_class
// so `*Self` substitutions in resolveTypeWithBindings find the
// state-struct type (M1.2 A.2b). The inline body-lowering path
// below re-resolves param types, so the context must be set
// BEFORE any resolveReturnType / resolveParamType call.
const saved_fc_lazy = self.current_foreign_class;
defer self.current_foreign_class = saved_fc_lazy;
const saved_fc_lazy = self.current_runtime_class;
defer self.current_runtime_class = saved_fc_lazy;
if (self.lookupObjcDefinedClassForMethod(name)) |fcd| {
self.current_foreign_class = fcd;
self.current_runtime_class = fcd;
}
// No AST? (builtins, foreign functions, or imported functions not in this file)
const fd = self.program_index.fn_ast_map.get(name) orelse return;
@@ -2381,10 +2381,10 @@ pub fn lazyLowerFunction(self: *Lowering, name: []const u8) void {
pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId, name: []const u8) void {
// objc-defined-class method context for `*Self` substitution (M1.2 A.2b);
// the resolveReturnType / resolveParamType calls below consult it.
const saved_fc = self.current_foreign_class;
defer self.current_foreign_class = saved_fc;
const saved_fc = self.current_runtime_class;
defer self.current_runtime_class = saved_fc;
if (self.lookupObjcDefinedClassForMethod(name)) |fcd| {
self.current_foreign_class = fcd;
self.current_runtime_class = fcd;
}
var reentry = FnBodyReentry.enter(self);
@@ -2477,13 +2477,13 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
/// Lower a single function declaration.
pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, is_imported: bool) void {
// For sx-defined `#objc_class` methods (qualified `<Class>.<method>`),
// set `current_foreign_class` so `*Self` substitutions through
// set `current_runtime_class` so `*Self` substitutions through
// `resolveTypeWithBindings` find the state-struct type (M1.2 A.2b).
// Save+restore — function lowering can re-enter.
const saved_fc = self.current_foreign_class;
defer self.current_foreign_class = saved_fc;
const saved_fc = self.current_runtime_class;
defer self.current_runtime_class = saved_fc;
if (self.lookupObjcDefinedClassForMethod(name)) |fcd| {
self.current_foreign_class = fcd;
self.current_runtime_class = fcd;
}
const name_id = self.module.types.internString(name);

View File

@@ -233,7 +233,7 @@ pub fn lowerJniCall(self: *Lowering, fic: *const ast.FfiIntrinsicCall) Ref {
/// JNI runtimes lower directly to `jni_msg_send` with a descriptor derived
/// from the method's sx signature; Obj-C / Swift runtimes are deferred to
/// Phase 3/4 and currently surface a clear diagnostic.
pub fn lowerForeignMethodCall(
pub fn lowerRuntimeMethodCall(
self: *Lowering,
fcd: *const ast.RuntimeClassDecl,
method_name: []const u8,
@@ -250,7 +250,7 @@ pub fn lowerForeignMethodCall(
// still used for `*Self` substitution at the dispatch site
// — the inherited method's *Self should resolve to the
// child receiver, not the parent.
const found = self.findForeignMethodInChain(fcd, method_name) orelse {
const found = self.findRuntimeMethodInChain(fcd, method_name) orelse {
if (self.diagnostics) |d| {
d.addFmt(.err, span, "no method '{s}' on foreign class '{s}' (or any `#extends` ancestor)", .{ method_name, fcd.name });
}
@@ -270,7 +270,7 @@ pub fn lowerForeignMethodCall(
if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) {
return self.lowerObjcMethodCall(fcd, method, target, method_args, span);
}
if (!fcd.is_foreign) {
if (!fcd.is_reference) {
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) });
}
@@ -295,7 +295,7 @@ pub fn lowerForeignMethodCall(
// resolve `*Foo` cross-class refs to their foreign paths.
var registry = jni_descriptor.ClassRegistry.init(self.alloc);
defer registry.deinit();
var it = self.program_index.foreign_class_map.iterator();
var it = self.program_index.runtime_class_map.iterator();
while (it.next()) |entry| {
registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {};
}
@@ -357,33 +357,33 @@ pub fn lowerForeignMethodCall(
/// with the foreign class's own struct type. Without this substitution
/// chained calls like `Cls.alloc().init()` see the inner result as a
/// fictitious `Self` struct and the next dispatch lookup fails.
pub fn resolveForeignClassMemberType(
pub fn resolveRuntimeClassMemberType(
self: *Lowering,
fcd: *const ast.RuntimeClassDecl,
type_node: *const ast.Node,
) TypeId {
if (type_node.data == .type_expr and std.mem.eql(u8, type_node.data.type_expr.name, "Self")) {
return self.foreignClassStructType(fcd);
return self.runtimeClassStructType(fcd);
}
if (type_node.data == .pointer_type_expr) {
const pt = type_node.data.pointer_type_expr;
if (pt.pointee_type.data == .type_expr and std.mem.eql(u8, pt.pointee_type.data.type_expr.name, "Self")) {
return self.module.types.ptrTo(self.foreignClassStructType(fcd));
return self.module.types.ptrTo(self.runtimeClassStructType(fcd));
}
}
return self.resolveType(type_node);
}
pub fn resolveForeignMethodReturnType(
pub fn resolveRuntimeMethodReturnType(
self: *Lowering,
fcd: *const ast.RuntimeClassDecl,
method: ast.RuntimeMethodDecl,
) TypeId {
const rt = method.return_type orelse return .void;
return self.resolveForeignClassMemberType(fcd, rt);
return self.resolveRuntimeClassMemberType(fcd, rt);
}
pub fn foreignClassStructType(self: *Lowering, fcd: *const ast.RuntimeClassDecl) TypeId {
pub fn runtimeClassStructType(self: *Lowering, fcd: *const ast.RuntimeClassDecl) TypeId {
const name_id = self.module.types.internString(fcd.name);
if (self.module.types.findByName(name_id)) |existing| return existing;
return self.module.types.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
@@ -434,7 +434,7 @@ pub fn lowerObjcMethodCall(
}
}
const ret_ty = self.resolveForeignMethodReturnType(fcd, method);
const ret_ty = self.resolveRuntimeMethodReturnType(fcd, method);
// Cache the SEL slot per (selector-string, module) like
// `#objc_call` does. The mangling produces the literal selector
@@ -489,7 +489,7 @@ pub fn lowerObjcStaticCall(
}
}
const ret_ty = self.resolveForeignMethodReturnType(fcd, method);
const ret_ty = self.resolveRuntimeMethodReturnType(fcd, method);
const vptr_ty = self.module.types.ptrTo(.void);
@@ -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_foreign and
if (!fcd.is_reference and
fcd.runtime == .objc_class and
method_args.len == 0 and
std.mem.eql(u8, method.name, "alloc"))
@@ -568,7 +568,7 @@ pub fn lowerObjcStaticCall(
/// user can use `#jni_static_call(T)(class, "name", sig, args...)`
/// for those. Constructor is the common case for #jni_main bodies
/// that need to instantiate Android classes (SurfaceView, etc.).
pub fn lowerForeignStaticCall(
pub fn lowerRuntimeStaticCall(
self: *Lowering,
fcd: *const ast.RuntimeClassDecl,
method: ast.RuntimeMethodDecl,
@@ -600,7 +600,7 @@ pub fn lowerForeignStaticCall(
// Build class registry snapshot for `*Foo` cross-class refs.
var registry = jni_descriptor.ClassRegistry.init(self.alloc);
defer registry.deinit();
var it = self.program_index.foreign_class_map.iterator();
var it = self.program_index.runtime_class_map.iterator();
while (it.next()) |entry| {
registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {};
}
@@ -679,7 +679,7 @@ pub fn lowerSuperCall(
method_args: []const Ref,
span: ast.Span,
) Ref {
const fcd = self.current_foreign_class orelse {
const fcd = self.current_runtime_class orelse {
if (self.diagnostics) |d| d.addFmt(.err, span, "'super' is only valid inside a `#jni_class` method body", .{});
return Ref.none;
};
@@ -689,7 +689,7 @@ pub fn lowerSuperCall(
var parent_path: []const u8 = "android/app/Activity";
for (fcd.members) |m| switch (m) {
.extends => |alias| {
if (self.program_index.foreign_class_map.get(alias)) |parent_fcd| {
if (self.program_index.runtime_class_map.get(alias)) |parent_fcd| {
parent_path = parent_fcd.foreign_path;
} else {
parent_path = alias;
@@ -704,14 +704,14 @@ pub fn lowerSuperCall(
// the parent class to be declared via `#foreign #jni_class`.
var descriptor: []const u8 = "";
var resolved_method: ?ast.RuntimeMethodDecl = null;
if (self.current_foreign_method) |em| {
if (self.current_runtime_method) |em| {
if (std.mem.eql(u8, em.name, method_name)) {
resolved_method = em;
}
}
if (resolved_method == null) {
const parent_fcd = blk: for (fcd.members) |m| switch (m) {
.extends => |alias| if (self.program_index.foreign_class_map.get(alias)) |pf| break :blk pf else continue,
.extends => |alias| if (self.program_index.runtime_class_map.get(alias)) |pf| break :blk pf else continue,
else => {},
} else null;
if (parent_fcd) |pf| {
@@ -733,7 +733,7 @@ pub fn lowerSuperCall(
// for `*Self` resolution).
var registry = jni_descriptor.ClassRegistry.init(self.alloc);
defer registry.deinit();
var it = self.program_index.foreign_class_map.iterator();
var it = self.program_index.runtime_class_map.iterator();
while (it.next()) |entry| {
registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {};
}
@@ -781,7 +781,7 @@ pub fn lowerSuperCall(
// ── Foreign-class registration ──────────────────────────────────
/// Register a foreign-class declaration. The alias goes into
/// `foreign_class_map` for method-dispatch lookup. The underlying
/// `runtime_class_map` for method-dispatch lookup. The underlying
/// type (e.g. `*Activity`) is resolved via the existing struct
/// fallback in `type_bridge.resolveTypeName` (which interns unknown
/// named types as 0-field structs).
@@ -792,9 +792,9 @@ pub fn lowerSuperCall(
/// under qualified names `<ClassName>.<methodName>`. Lazy lowering
/// then handles the body via the standard path; `*Self` is
/// substituted to `*<ClassName>State` during body lowering (M1.2 A.2b).
pub fn registerForeignClassDecl(self: *Lowering, fcd: *const ast.RuntimeClassDecl) void {
self.program_index.foreign_class_map.put(fcd.name, fcd) catch {};
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
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 (self.module.lookupObjcDefinedClass(fcd.name) == null) {
self.module.appendObjcDefinedClass(fcd.name, fcd);
// M2.3 — resolve the `#extends` alias to the actual
@@ -828,8 +828,8 @@ pub fn registerForeignClassDecl(self: *Lowering, fcd: *const ast.RuntimeClassDec
pub fn resolveObjcParentName(self: *Lowering, fcd: *const ast.RuntimeClassDecl) []const u8 {
for (fcd.members) |m| switch (m) {
.extends => |alias| {
if (self.program_index.foreign_class_map.get(alias)) |parent_fcd| {
if (parent_fcd.is_foreign) return parent_fcd.foreign_path;
if (self.program_index.runtime_class_map.get(alias)) |parent_fcd| {
if (parent_fcd.is_reference) return parent_fcd.foreign_path;
// Sx-defined parent — its alias IS its Obj-C name.
return parent_fcd.name;
}
@@ -883,11 +883,11 @@ pub fn declareObjcDefinedClassGlobal(self: *Lowering, class_name: []const u8) vo
/// (M1.2 A.4b.iii). Bodyless declarations are skipped — they
/// reference inherited / external methods, not sx-side bodies.
pub fn registerObjcDefinedClassMethods(self: *Lowering, fcd: *const ast.RuntimeClassDecl) void {
// Set current_foreign_class so `*Self` substitutions in
// Set current_runtime_class so `*Self` substitutions in
// declareFunction's type resolution find the state struct.
const saved = self.current_foreign_class;
self.current_foreign_class = fcd;
defer self.current_foreign_class = saved;
const saved = self.current_runtime_class;
self.current_runtime_class = fcd;
defer self.current_runtime_class = saved;
var method_infos = std.ArrayList(Module.ObjcDefinedMethodEntry).empty;
@@ -969,7 +969,7 @@ pub fn synthesizeFnDeclFromObjcMethod(self: *Lowering, method: ast.RuntimeMethod
/// If `name` matches an sx-defined `#objc_class`'s qualified-method
/// pattern (`<ClassName>.<methodName>`), return the class's
/// RuntimeClassDecl. Used by `lowerFunction` to set
/// `current_foreign_class` so `*Self` resolves to the state struct
/// `current_runtime_class` so `*Self` resolves to the state struct
/// during body lowering.
pub fn lookupObjcDefinedClassForMethod(self: *Lowering, name: []const u8) ?*const ast.RuntimeClassDecl {
const dot = std.mem.indexOf(u8, name, ".") orelse return null;
@@ -1013,15 +1013,15 @@ pub fn getJniEnvTlFids(self: *Lowering) struct { get: FuncId, set: FuncId } {
/// scan/lower already handles bare-name registration; this only adds the
/// qualified-name entry, so cross-class refs in method signatures
/// (`*View` → bare lookup) still work.
pub fn registerNamespacedForeignClasses(self: *Lowering, ns: ast.NamespaceDecl) void {
pub fn registerNamespacedRuntimeClasses(self: *Lowering, ns: ast.NamespaceDecl) void {
for (ns.decls) |inner| {
if (inner.data == .foreign_class_decl) {
const fcd = &inner.data.foreign_class_decl;
if (inner.data == .runtime_class_decl) {
const fcd = &inner.data.runtime_class_decl;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ns.name, fcd.name }) catch fcd.name;
self.program_index.foreign_class_map.put(qualified, fcd) catch {};
self.program_index.runtime_class_map.put(qualified, fcd) catch {};
} else if (inner.data == .namespace_decl) {
// Nested namespaces — qualify with both prefixes.
self.registerNamespacedForeignClasses(inner.data.namespace_decl);
self.registerNamespacedRuntimeClasses(inner.data.namespace_decl);
}
}
}
@@ -1033,11 +1033,11 @@ pub fn synthesizeJniMainStubs(self: *Lowering) void {
var seen = std.StringHashMap(void).init(self.alloc);
defer seen.deinit();
var it = self.program_index.foreign_class_map.iterator();
var it = self.program_index.runtime_class_map.iterator();
while (it.next()) |entry| {
const fcd = entry.value_ptr.*;
if (!fcd.is_main) continue;
if (fcd.is_foreign) continue;
if (fcd.is_reference) continue;
if (fcd.runtime != .jni_class) continue;
if (seen.contains(fcd.foreign_path)) continue;
seen.put(fcd.foreign_path, {}) catch continue;
@@ -1121,13 +1121,13 @@ pub fn synthesizeJniMainStub(self: *Lowering, fcd: *const ast.RuntimeClassDecl,
// Record method context so `super.method(args)` inside the body
// can find the parent class (via `#extends`) and the method's
// signature.
const saved_fcd = self.current_foreign_class;
const saved_method = self.current_foreign_method;
self.current_foreign_class = fcd;
self.current_foreign_method = md;
const saved_fcd = self.current_runtime_class;
const saved_method = self.current_runtime_method;
self.current_runtime_class = fcd;
self.current_runtime_method = md;
defer {
self.current_foreign_class = saved_fcd;
self.current_foreign_method = saved_method;
self.current_runtime_class = saved_fcd;
self.current_runtime_method = saved_method;
}
// JNI native methods are C-callable entry points — install the

View File

@@ -296,7 +296,7 @@ pub fn rawNamedTypePtr(ref: resolver_mod.RawDeclRef) ?*const anyopaque {
.union_decl => |d| @ptrCast(d),
.error_set_decl => |d| @ptrCast(d),
.protocol_decl => |d| @ptrCast(d),
.foreign_class_decl => |d| @ptrCast(d),
.runtime_class_decl => |d| @ptrCast(d),
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
};
}

View File

@@ -30,7 +30,7 @@ const Lowering = lower.Lowering;
/// `#objc_class`. The Obj-C runtime invokes these via the IMP
/// pointers wired up in M1.2 A.4 — no sx-side call path triggers
/// lazy lowering, so we walk the cache and force-lower here.
/// `lowerFunction` sets `current_foreign_class` automatically based
/// `lowerFunction` sets `current_runtime_class` automatically based
/// on the qualified name, so `*Self` substitutions in the body
/// resolve correctly (M1.2 A.2b). After the bodies are lowered,
/// `emitObjcDefinedClassImps` wraps each with a C-ABI trampoline
@@ -65,16 +65,16 @@ pub fn lookupObjcPropertyOnPointer(self: *Lowering, obj_expr: *const ast.Node, f
const pointee_info = self.module.types.get(ptr_info.pointer.pointee);
if (pointee_info != .@"struct") return null;
const struct_name = self.module.types.getString(pointee_info.@"struct".name);
const fcd = self.program_index.foreign_class_map.get(struct_name) orelse return null;
const fcd = self.program_index.runtime_class_map.get(struct_name) orelse return null;
if (fcd.runtime != .objc_class and fcd.runtime != .objc_protocol) return null;
return self.findForeignPropertyInChain(fcd, field_name);
return self.findRuntimePropertyInChain(fcd, field_name);
}
/// Walk the `#extends` chain looking for a method by name. M2.3.
/// Returns the owning fcd + the method decl, or null if no ancestor
/// declares it. Depth-capped at 16 to break accidental cycles
/// (real Obj-C class chains rarely exceed 6 levels).
pub fn findForeignMethodInChain(self: *Lowering, fcd: *const ast.RuntimeClassDecl, method_name: []const u8) ?struct { fcd: *const ast.RuntimeClassDecl, method: ast.RuntimeMethodDecl } {
pub fn findRuntimeMethodInChain(self: *Lowering, fcd: *const ast.RuntimeClassDecl, method_name: []const u8) ?struct { fcd: *const ast.RuntimeClassDecl, method: ast.RuntimeMethodDecl } {
var current: *const ast.RuntimeClassDecl = fcd;
var depth: u32 = 0;
while (depth < 16) : (depth += 1) {
@@ -90,14 +90,14 @@ pub fn findForeignMethodInChain(self: *Lowering, fcd: *const ast.RuntimeClassDec
};
break :blk null;
} orelse return null;
current = self.program_index.foreign_class_map.get(parent) orelse return null;
current = self.program_index.runtime_class_map.get(parent) orelse return null;
}
return null;
}
/// Walk the `#extends` chain looking for a `#property` field by
/// name. M2.3 companion to findForeignMethodInChain.
pub fn findForeignPropertyInChain(self: *Lowering, fcd: *const ast.RuntimeClassDecl, field_name: []const u8) ?ast.RuntimeFieldDecl {
/// name. M2.3 companion to findRuntimeMethodInChain.
pub fn findRuntimePropertyInChain(self: *Lowering, fcd: *const ast.RuntimeClassDecl, field_name: []const u8) ?ast.RuntimeFieldDecl {
var current: *const ast.RuntimeClassDecl = fcd;
var depth: u32 = 0;
while (depth < 16) : (depth += 1) {
@@ -112,7 +112,7 @@ pub fn findForeignPropertyInChain(self: *Lowering, fcd: *const ast.RuntimeClassD
};
break :blk null;
} orelse return null;
current = self.program_index.foreign_class_map.get(parent) orelse return null;
current = self.program_index.runtime_class_map.get(parent) orelse return null;
}
return null;
}
@@ -136,10 +136,10 @@ pub fn lookupObjcDefinedStateFieldOnPointer(self: *Lowering, obj_expr: *const as
const pointee_info = self.module.types.get(ptr_info.pointer.pointee);
if (pointee_info != .@"struct") return null;
const struct_name = self.module.types.getString(pointee_info.@"struct".name);
const fcd = self.program_index.foreign_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
// classes' fields are purely declaration metadata (no state).
if (fcd.is_foreign or fcd.runtime != .objc_class) return null;
if (fcd.is_reference 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) {
@@ -694,11 +694,11 @@ pub fn emitObjcDefinedClassImp(self: *Lowering, fcd: *const ast.RuntimeClassDecl
params.append(self.alloc, .{ .name = self.module.types.internString("obj"), .ty = ptr_void }) catch return;
params.append(self.alloc, .{ .name = self.module.types.internString("_cmd"), .ty = ptr_void }) catch return;
// Set current_foreign_class so *Self in user-param resolution
// Set current_runtime_class so *Self in user-param resolution
// resolves to *<Cls>State (M1.2 A.2b). Save+restore.
const saved_fc = self.current_foreign_class;
self.current_foreign_class = fcd;
defer self.current_foreign_class = saved_fc;
const saved_fc = self.current_runtime_class;
self.current_runtime_class = fcd;
defer self.current_runtime_class = saved_fc;
const param_start: usize = 1;
for (md.params[param_start..], 0..) |p_node, i| {
@@ -971,12 +971,12 @@ pub fn emitObjcDefinedClassStaticImp(self: *Lowering, fcd: *const ast.RuntimeCla
params.append(self.alloc, .{ .name = self.module.types.internString("cls"), .ty = ptr_void }) catch return;
params.append(self.alloc, .{ .name = self.module.types.internString("_cmd"), .ty = ptr_void }) catch return;
// current_foreign_class lets `*Self` (if it appears in
// current_runtime_class lets `*Self` (if it appears in
// user-arg types — rare for class methods) resolve to the
// state-struct type. Save+restore.
const saved_fc = self.current_foreign_class;
self.current_foreign_class = fcd;
defer self.current_foreign_class = saved_fc;
const saved_fc = self.current_runtime_class;
self.current_runtime_class = fcd;
defer self.current_runtime_class = saved_fc;
for (md.params, 0..) |p_node, i| {
const pty = self.resolveType(p_node);

View File

@@ -79,7 +79,7 @@ pub const Module = struct {
/// Pre-resolved Obj-C runtime name of the parent class, so
/// emit_llvm can pass it to `objc_getClass(parent)` /
/// `objc_allocateClassPair(super, ...)` without walking the
/// sx-side foreign_class_map (which lives in lower.zig).
/// sx-side runtime_class_map (which lives in lower.zig).
/// Defaults to "NSObject" when no `#extends` member is present.
parent_objc_name: []const u8 = "NSObject",
};

View File

@@ -67,17 +67,17 @@ test "ProgramIndex declaration maps round-trip (A1.1b)" {
try idx.module_const_map.put("AF_INET", .{ .value = &blk, .ty = .i32 });
try std.testing.expect(idx.module_const_map.get("AF_INET").?.value == &blk);
// foreign_class_map: sx alias → RuntimeClassDecl.
// runtime_class_map: sx alias → RuntimeClassDecl.
const fcd = ast.RuntimeClassDecl{
.name = "NSString",
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_reference = true,
.is_main = false,
};
try idx.foreign_class_map.put("NSString", &fcd);
try std.testing.expect(idx.foreign_class_map.get("NSString").? == &fcd);
try idx.runtime_class_map.put("NSString", &fcd);
try std.testing.expect(idx.runtime_class_map.get("NSString").? == &fcd);
// protocol_decl_map: protocol name → ProtocolDeclInfo.
try idx.protocol_decl_map.put("Show", .{ .name = "Show", .is_inline = false, .methods = &.{} });

View File

@@ -665,7 +665,7 @@ pub const ProgramIndex = struct {
/// resolve. Keyed/allocated with the lowering allocator.
qualified_fn_source: std.StringHashMap([]const u8),
/// sx alias → RuntimeClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass).
foreign_class_map: std.StringHashMap(*const ast.RuntimeClassDecl),
runtime_class_map: std.StringHashMap(*const ast.RuntimeClassDecl),
/// `#run` global name → GlobalId.
global_names: std.StringHashMap(GlobalInfo),
/// Type alias name → target TypeId. The single-source alias table; passed
@@ -708,7 +708,7 @@ pub const ProgramIndex = struct {
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc),
.qualified_fn_source = std.StringHashMap([]const u8).init(alloc),
.global_names = std.StringHashMap(GlobalInfo).init(alloc),
.foreign_class_map = std.StringHashMap(*const ast.RuntimeClassDecl).init(alloc),
.runtime_class_map = std.StringHashMap(*const ast.RuntimeClassDecl).init(alloc),
.type_alias_map = std.StringHashMap(TypeId).init(alloc),
.struct_template_map = std.StringHashMap(StructTemplate).init(alloc),
.struct_template_by_decl = std.AutoHashMap(imports.DeclId, StructTemplate).init(alloc),
@@ -728,7 +728,7 @@ pub const ProgramIndex = struct {
self.import_flags.deinit();
self.fn_ast_map.deinit();
self.qualified_fn_source.deinit();
self.foreign_class_map.deinit();
self.runtime_class_map.deinit();
self.global_names.deinit();
self.type_alias_map.deinit();
self.struct_template_map.deinit();

View File

@@ -121,7 +121,7 @@ pub const Domain = enum {
generic_struct_head,
type_fn_head,
protocol_head,
foreign_class,
runtime_class,
struct_const,
namespace_member,
ufcs,
@@ -135,7 +135,7 @@ pub fn eligibleKind(domain: Domain, raw: RawDeclRef, field: ?[]const u8) bool {
return switch (domain) {
.bare_type => switch (raw) {
.struct_decl, .enum_decl, .union_decl, .error_set_decl,
.protocol_decl, .foreign_class_decl => true,
.protocol_decl, .runtime_class_decl => true,
else => false,
},
.value_const => raw == .const_decl,
@@ -143,7 +143,7 @@ pub fn eligibleKind(domain: Domain, raw: RawDeclRef, field: ?[]const u8) bool {
.generic_struct_head => if (structDeclOf(raw)) |sd| sd.type_params.len > 0 else false,
.type_fn_head => if (fnDeclOf(raw)) |fd| fd.type_params.len > 0 else false,
.protocol_head => raw == .protocol_decl,
.foreign_class => raw == .foreign_class_decl,
.runtime_class => raw == .runtime_class_decl,
.struct_const => structHasConstMember(raw, field orelse return false),
.namespace_member => true,
.ufcs => fnDeclOf(raw) != null,
@@ -229,9 +229,9 @@ pub fn classifyHeadKind(raw: RawDeclRef, gs: *bool, tf: *bool, pr: *bool) void {
/// True when the bare-type verdict selected a foreign-class author
/// unambiguously. Used by lowering to route to the foreign-class path.
pub fn foreignClassWinsType(set: AuthorSet, verdict: Verdict) bool {
pub fn runtimeClassWinsType(set: AuthorSet, verdict: Verdict) bool {
return switch (verdict) {
.own_wins => if (set.own) |a| std.meta.activeTag(a.raw) == .foreign_class_decl else false,
.own_wins => if (set.own) |a| std.meta.activeTag(a.raw) == .runtime_class_decl else false,
.single => blk: {
var selected: ?RawAuthor = null;
for (set.flat) |a| {
@@ -240,7 +240,7 @@ pub fn foreignClassWinsType(set: AuthorSet, verdict: Verdict) bool {
selected = a;
}
const a = selected orelse break :blk false;
break :blk std.meta.activeTag(a.raw) == .foreign_class_decl;
break :blk std.meta.activeTag(a.raw) == .runtime_class_decl;
},
.ambiguous, .not_visible, .domain_filtered => false,
};

View File

@@ -211,7 +211,7 @@ pub const UnknownTypeChecker = struct {
}
}
},
.foreign_class_decl => |fcd| {
.runtime_class_decl => |fcd| {
// The sx-side alias (left of `::`) is a user-chosen name, so a
// reserved spelling is rejected like any other type decl (0089).
self.checkDeclName(node, fcd.name, fcd.is_raw);
@@ -394,7 +394,7 @@ pub const UnknownTypeChecker = struct {
else => {},
}
}
var it_fc = self.index.foreign_class_map.keyIterator();
var it_fc = self.index.runtime_class_map.keyIterator();
while (it_fc.next()) |k| out.put(k.*, {}) catch {};
var it_tmpl = self.index.struct_template_map.keyIterator();
while (it_tmpl.next()) |k| out.put(k.*, {}) catch {};

View File

@@ -258,15 +258,15 @@ pub const Parser = struct {
// `#foreign` flips that to "reference an existing class on the foreign side."
// `#jni_main` flags the class as the launchable entry (Android Activity).
const prefix_loc = self.current.loc;
if (self.tryParseForeignClassPrefix()) |prefix| {
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).
if (prefix.is_foreign) {
if (prefix.is_reference) {
return self.failAt(prefix_loc, "`#foreign` has been removed; use the postfix `extern` (import) / `export` (define) linkage keyword instead");
}
return self.parseForeignClassDecl(name, start_pos, prefix.runtime, prefix.is_foreign, prefix.is_main, name_is_raw);
return self.parseRuntimeClassDecl(name, start_pos, prefix.runtime, prefix.is_reference, prefix.is_main, name_is_raw);
}
// C-style union declaration
@@ -1280,7 +1280,7 @@ pub const Parser = struct {
} });
}
fn foreignRuntimeForCurrent(self: *Parser) ?ast.RuntimeKind {
fn runtimeKindForCurrent(self: *Parser) ?ast.RuntimeKind {
return switch (self.current.tag) {
.hash_jni_class => .jni_class,
.hash_jni_interface => .jni_interface,
@@ -1295,7 +1295,7 @@ pub const Parser = struct {
const RuntimeClassPrefix = struct {
runtime: ast.RuntimeKind,
is_foreign: bool,
is_reference: bool,
is_main: bool,
};
@@ -1305,16 +1305,16 @@ pub const Parser = struct {
/// directive (possibly after modifiers). Consumes the modifier tokens
/// only when a runtime directive follows; otherwise leaves the parser
/// state untouched.
fn tryParseForeignClassPrefix(self: *Parser) ?RuntimeClassPrefix {
fn tryParseRuntimeClassPrefix(self: *Parser) ?RuntimeClassPrefix {
// Peek ahead through modifier tokens to confirm a directive follows.
var lookahead_idx: usize = 0;
var is_foreign = false;
var is_reference = false;
var is_main = false;
while (true) {
const tag = self.peekTag(lookahead_idx);
switch (tag) {
.hash_foreign => {
is_foreign = true;
is_reference = true;
lookahead_idx += 1;
},
.hash_jni_main => {
@@ -1324,11 +1324,11 @@ pub const Parser = struct {
else => break,
}
}
const runtime = self.foreignRuntimeForOffset(lookahead_idx) orelse return null;
const runtime = self.runtimeKindForOffset(lookahead_idx) orelse return null;
// Commit: consume modifier tokens.
var i: usize = 0;
while (i < lookahead_idx) : (i += 1) self.advance();
return .{ .runtime = runtime, .is_foreign = is_foreign, .is_main = is_main };
return .{ .runtime = runtime, .is_reference = is_reference, .is_main = is_main };
}
fn peekTag(self: *Parser, offset: usize) Tag {
@@ -1349,7 +1349,7 @@ pub const Parser = struct {
return self.peekTag(1) == .identifier and self.peekTag(2) == .r_paren;
}
fn foreignRuntimeForOffset(self: *Parser, offset: usize) ?ast.RuntimeKind {
fn runtimeKindForOffset(self: *Parser, offset: usize) ?ast.RuntimeKind {
const tag = self.peekTag(offset);
return switch (tag) {
.hash_jni_class => .jni_class,
@@ -1363,7 +1363,7 @@ pub const Parser = struct {
};
}
fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.RuntimeKind, is_foreign: bool, is_main: bool, name_is_raw: bool) anyerror!*Node {
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 {
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_foreign` decision so lowering is
// Maps straight onto the existing `is_reference` decision so lowering is
// unchanged. The legacy prefix `#foreign` form still works via the
// `is_foreign` argument; interplay/diagnostics for combining them is Phase 4.
var is_foreign_eff = is_foreign;
// `is_reference` argument; interplay/diagnostics for combining them is Phase 4.
var is_reference_eff = is_reference;
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_foreign) {
if (is_reference) {
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_foreign_eff = self.current.tag == .kw_extern;
is_reference_eff = self.current.tag == .kw_extern;
self.advance();
}
@@ -1619,12 +1619,12 @@ pub const Parser = struct {
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .foreign_class_decl = .{
return try self.createNode(start_pos, .{ .runtime_class_decl = .{
.name = name,
.foreign_path = foreign_path,
.runtime = runtime,
.members = try members.toOwnedSlice(self.allocator),
.is_foreign = is_foreign_eff,
.is_reference = is_reference_eff,
.is_main = is_main,
.is_raw = name_is_raw,
} });

View File

@@ -1332,16 +1332,16 @@ pub const Analyzer = struct {
}
}
},
.foreign_class_decl => |fd| {
.runtime_class_decl => |fd| {
try self.addSymbol(fd.name, .type_alias, null, node.span);
if (fd.is_foreign and fd.is_main) {
if (fd.is_reference 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_foreign) {
if (fd.is_reference) {
for (fd.members) |m| switch (m) {
.method => |md| if (md.body != null) {
try self.diagnostics.append(self.allocator, .{
@@ -1784,7 +1784,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
.tuple_type_expr,
.ufcs_alias,
.closure_type_expr,
.foreign_class_decl,
.runtime_class_decl,
=> {},
.jni_env_block => |eb| {
if (findNodeAtOffset(eb.env, offset)) |found| return found;