ffi M1.2 A.2b: register sx-defined #objc_class methods + *Self substitution
Bodied instance methods on a sx-defined '#objc_class("Cls") { ... }'
declaration are now registered in fn_ast_map under '<Cls>.<method>'
and declared in the IR with their *Self params substituted to
the hidden state-struct type (M1.2 A.2a).
registerObjcDefinedClassMethods walks the foreign_class_decl's
members, synthesizes an FnDecl from each ForeignMethodDecl (zipping
params + param_names), and feeds it through declareFunction with
current_foreign_class temporarily pinned so resolveTypeWithBindings
substitutes Self → __SxFooState.
resolveTypeWithBindings now treats type_expr 'Self' as a contextual
alias: when current_foreign_class points to a sx-defined Obj-C
class, the substitution returns objcDefinedStateStructType(fcd).
Other Self contexts (protocols, JNI super, foreign-class member
type resolution) are untouched — the check filters on (!is_foreign
and runtime == .objc_class).
lowerFunction also sets current_foreign_class for the duration of
the body lowering when the name is qualified <Cls>.<method> and
Cls is in objc_defined_class_cache. Save+restore via defer so
nested calls round-trip cleanly.
Verification (manual): 'sx ir' on an sx-defined class shows
'declare void @SxFoo.bump(ptr, ptr)' — two args = implicit
__sx_ctx + the state-struct pointer (correct *Self substitution).
Body emission happens lazily; A.2c will trigger it eagerly so
the IMP trampoline (A.4) can reference it.
170 example tests + zig build test green.
This commit is contained in:
@@ -1075,6 +1075,16 @@ pub const Lowering = struct {
|
||||
|
||||
/// 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
|
||||
// `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;
|
||||
if (self.lookupObjcDefinedClassForMethod(name)) |fcd| {
|
||||
self.current_foreign_class = fcd;
|
||||
}
|
||||
|
||||
const name_id = self.module.types.internString(name);
|
||||
const ret_ty = self.resolveReturnType(fd);
|
||||
|
||||
@@ -8797,6 +8807,20 @@ pub const Lowering = struct {
|
||||
|
||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
||||
// *Self substitution for sx-defined `#objc_class` method bodies
|
||||
// (M1.2 A.2b). `current_foreign_class` is set by lowerFunction
|
||||
// and registerObjcDefinedClassMethods when the surrounding
|
||||
// function is a class method. `Self` inside the body resolves
|
||||
// to the class's hidden state-struct type — `self.field` then
|
||||
// works as a plain struct field access (M1.2 A.3 "free if
|
||||
// types align").
|
||||
if (node.data == .type_expr and std.mem.eql(u8, node.data.type_expr.name, "Self")) {
|
||||
if (self.current_foreign_class) |fcd| {
|
||||
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
|
||||
return self.objcDefinedStateStructType(fcd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.type_bindings) |tb| {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| {
|
||||
@@ -9567,16 +9591,80 @@ pub const Lowering = struct {
|
||||
///
|
||||
/// sx-defined Obj-C classes (no `#foreign`, runtime == .objc_class)
|
||||
/// also land in `module.objc_defined_class_cache` in declaration
|
||||
/// order — that cache drives M1.2 class-synthesis emission (A.4+).
|
||||
/// order AND have their bodied methods registered into `fn_ast_map`
|
||||
/// 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).
|
||||
fn registerForeignClassDecl(self: *Lowering, fcd: *const ast.ForeignClassDecl) void {
|
||||
self.foreign_class_map.put(fcd.name, fcd) catch {};
|
||||
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
|
||||
if (self.module.lookupObjcDefinedClass(fcd.name) == null) {
|
||||
self.module.appendObjcDefinedClass(fcd.name, fcd);
|
||||
}
|
||||
self.registerObjcDefinedClassMethods(fcd);
|
||||
}
|
||||
}
|
||||
|
||||
/// For each bodied instance method on an sx-defined `#objc_class`,
|
||||
/// synthesize an `FnDecl` from the `ForeignMethodDecl`, register it
|
||||
/// in `fn_ast_map` under `<ClassName>.<methodName>`, and declare
|
||||
/// the IR function so callers can resolve the name. Bodyless
|
||||
/// declarations are skipped — they reference inherited / external
|
||||
/// methods, not sx-side bodies.
|
||||
fn registerObjcDefinedClassMethods(self: *Lowering, fcd: *const ast.ForeignClassDecl) void {
|
||||
for (fcd.members) |m| {
|
||||
const method = switch (m) {
|
||||
.method => |md| md,
|
||||
else => continue,
|
||||
};
|
||||
const body = method.body orelse continue;
|
||||
const fd = self.synthesizeFnDeclFromObjcMethod(method, body) orelse continue;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fcd.name, method.name }) catch continue;
|
||||
self.fn_ast_map.put(qualified, fd) catch {};
|
||||
// Set current_foreign_class while declaring so `*Self` in
|
||||
// the signature resolves to the state struct (M1.2 A.2b).
|
||||
const saved = self.current_foreign_class;
|
||||
self.current_foreign_class = fcd;
|
||||
defer self.current_foreign_class = saved;
|
||||
self.declareFunction(fd, qualified);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an `FnDecl` whose params are zipped from the
|
||||
/// `ForeignMethodDecl.params` (type nodes) and `param_names`. Used
|
||||
/// to feed sx-defined class methods through the standard
|
||||
/// fn-lowering pipeline. Allocator-owned; lives for the duration
|
||||
/// of the Lowering pass.
|
||||
fn synthesizeFnDeclFromObjcMethod(self: *Lowering, method: ast.ForeignMethodDecl, body: *ast.Node) ?*ast.FnDecl {
|
||||
if (method.params.len != method.param_names.len) return null;
|
||||
var params = std.ArrayList(ast.Param).empty;
|
||||
for (method.params, method.param_names) |type_node, p_name| {
|
||||
params.append(self.alloc, .{
|
||||
.name = p_name,
|
||||
.name_span = .{ .start = 0, .end = 0 },
|
||||
.type_expr = type_node,
|
||||
}) catch unreachable;
|
||||
}
|
||||
const fd = self.alloc.create(ast.FnDecl) catch return null;
|
||||
fd.* = .{
|
||||
.name = method.name,
|
||||
.params = params.toOwnedSlice(self.alloc) catch unreachable,
|
||||
.return_type = method.return_type,
|
||||
.body = body,
|
||||
};
|
||||
return fd;
|
||||
}
|
||||
|
||||
/// If `name` matches an sx-defined `#objc_class`'s qualified-method
|
||||
/// pattern (`<ClassName>.<methodName>`), return the class's
|
||||
/// ForeignClassDecl. Used by `lowerFunction` to set
|
||||
/// `current_foreign_class` so `*Self` resolves to the state struct
|
||||
/// during body lowering.
|
||||
fn lookupObjcDefinedClassForMethod(self: *Lowering, name: []const u8) ?*const ast.ForeignClassDecl {
|
||||
const dot = std.mem.indexOf(u8, name, ".") orelse return null;
|
||||
return self.module.lookupObjcDefinedClass(name[0..dot]);
|
||||
}
|
||||
|
||||
/// Lazily declare the `sx_jni_env_tl_get` / `sx_jni_env_tl_set`
|
||||
/// runtime externs (step 2.16c). The storage lives in
|
||||
/// `library/vendors/sx_jni_runtime/sx_jni_env_tl.c` as a
|
||||
|
||||
Reference in New Issue
Block a user