diff --git a/examples/138-foreign-class-chained-dispatch.sx b/examples/138-foreign-class-chained-dispatch.sx new file mode 100644 index 0000000..f940188 --- /dev/null +++ b/examples/138-foreign-class-chained-dispatch.sx @@ -0,0 +1,41 @@ +// Chained foreign-class method dispatch: `Cls.static().instance(...)` +// resolves the inner call's return type so the outer dispatch's +// receiver type is known. Pre-fix this collapsed to s64 in +// `inferExprType`, the foreign_class_map lookup missed, and lowering +// emitted `error: unresolved 'init'` (or 'initWithWindowScene' etc.) +// — see issues/0043 for the chess uikit.sx C4 migration that hit it. +// +// Two return-type shapes covered: explicit `*ClassName` (alloc here) +// and `*Self` (init). Both must propagate through the chain so the +// next `.method(...)` finds the foreign-class declaration. + +#import "modules/std.sx"; +#import "modules/compiler.sx"; + +NSObject :: #foreign #objc_class("NSObject") { + alloc :: () -> *NSObject; + init :: (self: *Self) -> *Self; +} + +NSObjectSelfReturn :: #foreign #objc_class("NSObject") { + alloc :: () -> *Self; + init :: (self: *Self) -> *Self; +} + +main :: () -> s32 { + inline if OS == .macos { + a := NSObject.alloc().init(); + if a != null { + print("explicit-then-self ok\n"); + } + b := NSObjectSelfReturn.alloc().init(); + if b != null { + print("self-then-self ok\n"); + } + } + inline if OS != .macos { + print("explicit-then-self ok\n"); + print("self-then-self ok\n"); + } + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index b8fc904..77e85b4 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -4528,6 +4528,42 @@ pub const Lowering = struct { return .{ .sel = out, .keyword_count = pieces, .is_override = false }; } + /// Resolve a foreign-class member type, substituting `Self` (and `*Self`) + /// 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. + fn resolveForeignClassMemberType( + self: *Lowering, + fcd: *const ast.ForeignClassDecl, + 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); + } + 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.resolveType(type_node); + } + + fn resolveForeignMethodReturnType( + self: *Lowering, + fcd: *const ast.ForeignClassDecl, + method: ast.ForeignMethodDecl, + ) TypeId { + const rt = method.return_type orelse return .void; + return self.resolveForeignClassMemberType(fcd, rt); + } + + fn foreignClassStructType(self: *Lowering, fcd: *const ast.ForeignClassDecl) 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 = &.{} } }); + } + /// Lower `inst.method(args)` on an `#objc_class` / `#objc_protocol` /// receiver. The selector is derived by `deriveObjcSelector`; arity /// is validated against the keyword count produced by the mangling @@ -4573,7 +4609,7 @@ pub const Lowering = struct { } } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else .void; + const ret_ty = self.resolveForeignMethodReturnType(fcd, method); // Cache the SEL slot per (selector-string, module) like // `#objc_call` does. The mangling produces the literal selector @@ -4628,7 +4664,7 @@ pub const Lowering = struct { } } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else .void; + const ret_ty = self.resolveForeignMethodReturnType(fcd, method); const vptr_ty = self.module.types.ptrTo(.void); @@ -10075,6 +10111,30 @@ pub const Lowering = struct { } } } + // Foreign-class instance method: look up the method's + // declared return type so chained calls (e.g. + // `UIWindow.alloc().initWithWindowScene(scene)`) resolve. + { + var recv_inner = recv_ty; + if (!recv_inner.isBuiltin()) { + const ri = self.module.types.get(recv_inner); + if (ri == .pointer) recv_inner = ri.pointer.pointee; + } + if (!recv_inner.isBuiltin()) { + const inner_info = self.module.types.get(recv_inner); + if (inner_info == .@"struct") { + const sn = self.module.types.getString(inner_info.@"struct".name); + if (self.foreign_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 self.resolveForeignMethodReturnType(fcd, md); + }, + else => {}, + }; + } + } + } + } // Instance method call: obj.method(args) → look up StructName.method { var obj_ty = recv_ty; @@ -10107,6 +10167,15 @@ pub const Lowering = struct { else => null, }; if (type_name) |tn| { + // Foreign-class static method: `Alias.static_method(args)`. + if (self.foreign_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 self.resolveForeignMethodReturnType(fcd, md); + }, + else => {}, + }; + } const type_name_id = self.module.types.internString(tn); if (self.module.types.findByName(type_name_id)) |ty| { const ti = self.module.types.get(ty); diff --git a/tests/expected/138-foreign-class-chained-dispatch.exit b/tests/expected/138-foreign-class-chained-dispatch.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/138-foreign-class-chained-dispatch.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/138-foreign-class-chained-dispatch.txt b/tests/expected/138-foreign-class-chained-dispatch.txt new file mode 100644 index 0000000..1c2c09e --- /dev/null +++ b/tests/expected/138-foreign-class-chained-dispatch.txt @@ -0,0 +1,2 @@ +explicit-then-self ok +self-then-self ok