ffi M1.2 A.1: objcTypeEncodingFromSignature helper + encoding table
Derives Apple's runtime type-encoding string from an IR method signature. Called by class_addMethod(cls, sel, imp, types) when M1.2 A.4+ synthesise IMPs for sx-defined classes. Layout: <ret> @ : <param0> <param1> ... — @ is the receiver, : is _cmd. Caller passes user-declared params AFTER stripping 'self: *Self'. Encoding table: v=void B=bool c=s8/BOOL s=s16 i=s32 q=s64 C=u8 S=u16 I=u32 Q=u64 f=f32 d=f64 @=foreign Obj-C class ptr #=Class :=SEL *=[*]u8 (C string) ^v=any other ptr bool (sx i1) maps to 'B' (C99 _Bool); s8 to 'c' (Apple's BOOL). Foreign-class pointers detected via foreign_class_map lookup on the pointee struct name. Other pointers fall to ^v — encoding is metadata, not ABI, so conservative is safe. Struct / slice / closure / etc. BAIL via diagnostic (ObjcEncodingUnsupported) rather than silently mis-encoding, per CLAUDE.md rejected-patterns rule. Future passes will widen the table as new shapes show up in real IMPs. Dead code at this commit — helper isn't called yet. Three unit tests in src/ir/lower.test.zig pin the primitive / pointer / Obj-C-class-pointer encodings before A.2 wires the helper in. 170 example tests + zig build test green.
This commit is contained in:
108
src/ir/lower.zig
108
src/ir/lower.zig
@@ -4528,6 +4528,114 @@ pub const Lowering = struct {
|
||||
return .{ .sel = out, .keyword_count = pieces, .is_override = false };
|
||||
}
|
||||
|
||||
/// Derive an Obj-C type-encoding string for a synthesized IMP
|
||||
/// signature (M1.2 A.1). Apple's runtime accepts these strings on
|
||||
/// `class_addMethod(cls, sel, imp, types)`; the encoding tells the
|
||||
/// runtime the IMP's argument layout for KVC, NSCoder, and reflective
|
||||
/// dispatch.
|
||||
///
|
||||
/// Layout: `<ret> @ : <param0> <param1> ...`. The `@` slot is the
|
||||
/// receiver (self); `:` is `_cmd`. Caller passes user-declared params
|
||||
/// AFTER stripping `self`.
|
||||
///
|
||||
/// Single-character encodings (the common case):
|
||||
/// v=void B=bool c=s8/BOOL s=s16 i=s32 q=s64
|
||||
/// C=u8 S=u16 I=u32 Q=u64 f=f32 d=f64
|
||||
/// @=id #=Class :=SEL *=C string ^v=void* / generic ptr
|
||||
///
|
||||
/// Foreign-class pointers (`*UIView` etc.) encode as `@` (object
|
||||
/// pointer). Other pointers fall to `^v` — the encoding is metadata,
|
||||
/// not ABI, so being conservative here is safe. Struct returns and
|
||||
/// other complex shapes BAIL loudly via diagnostics rather than
|
||||
/// silently mis-encoding (per CLAUDE.md rejected-patterns rule).
|
||||
///
|
||||
/// Returns an allocator-owned slice; caller frees via `self.alloc`.
|
||||
fn objcTypeEncodingFromSignature(
|
||||
self: *Lowering,
|
||||
return_ty: TypeId,
|
||||
param_tys: []const TypeId,
|
||||
span: ?ast.Span,
|
||||
) ![]const u8 {
|
||||
var out = std.ArrayList(u8).empty;
|
||||
errdefer out.deinit(self.alloc);
|
||||
|
||||
try self.appendObjcEncoding(&out, return_ty, span);
|
||||
try out.append(self.alloc, '@'); // self
|
||||
try out.append(self.alloc, ':'); // _cmd
|
||||
for (param_tys) |pty| {
|
||||
try self.appendObjcEncoding(&out, pty, span);
|
||||
}
|
||||
|
||||
return try out.toOwnedSlice(self.alloc);
|
||||
}
|
||||
|
||||
fn appendObjcEncoding(self: *Lowering, out: *std.ArrayList(u8), ty: TypeId, span: ?ast.Span) !void {
|
||||
const info = self.module.types.get(ty);
|
||||
switch (info) {
|
||||
.void => try out.append(self.alloc, 'v'),
|
||||
.bool => try out.append(self.alloc, 'B'),
|
||||
.signed => |bits| {
|
||||
const ch: u8 = switch (bits) {
|
||||
8 => 'c',
|
||||
16 => 's',
|
||||
32 => 'i',
|
||||
64 => 'q',
|
||||
else => return self.bailObjcEncoding(span, "signed integer with non-standard bit width", bits),
|
||||
};
|
||||
try out.append(self.alloc, ch);
|
||||
},
|
||||
.unsigned => |bits| {
|
||||
const ch: u8 = switch (bits) {
|
||||
8 => 'C',
|
||||
16 => 'S',
|
||||
32 => 'I',
|
||||
64 => 'Q',
|
||||
else => return self.bailObjcEncoding(span, "unsigned integer with non-standard bit width", bits),
|
||||
};
|
||||
try out.append(self.alloc, ch);
|
||||
},
|
||||
.f32 => try out.append(self.alloc, 'f'),
|
||||
.f64 => try out.append(self.alloc, 'd'),
|
||||
// sx-target arm64 — pointer-sized aliases match s64/u64.
|
||||
.isize => try out.append(self.alloc, 'q'),
|
||||
.usize => try out.append(self.alloc, 'Q'),
|
||||
.pointer => |p| {
|
||||
// Pointer to a foreign Obj-C class (or sx-defined #objc_class)
|
||||
// encodes as `@`. Anything else falls to `^v` — generic
|
||||
// pointer; the runtime treats it as opaque.
|
||||
const pointee_info = self.module.types.get(p.pointee);
|
||||
const is_objc_obj = blk: {
|
||||
if (pointee_info != .@"struct") break :blk false;
|
||||
const name = self.module.types.getString(pointee_info.@"struct".name);
|
||||
break :blk self.foreign_class_map.get(name) != null;
|
||||
};
|
||||
if (is_objc_obj) {
|
||||
try out.append(self.alloc, '@');
|
||||
} else {
|
||||
try out.appendSlice(self.alloc, "^v");
|
||||
}
|
||||
},
|
||||
.many_pointer => |mp| {
|
||||
// `[*]u8` is the canonical C-string carrier — encode as `*`.
|
||||
// Other element types fall to generic `^v`.
|
||||
const el = self.module.types.get(mp.element);
|
||||
if (el == .unsigned and el.unsigned == 8) {
|
||||
try out.append(self.alloc, '*');
|
||||
} else {
|
||||
try out.appendSlice(self.alloc, "^v");
|
||||
}
|
||||
},
|
||||
else => return self.bailObjcEncoding(span, "type kind not yet supported by Obj-C encoding", @intFromEnum(std.meta.activeTag(info))),
|
||||
}
|
||||
}
|
||||
|
||||
fn bailObjcEncoding(self: *Lowering, span: ?ast.Span, reason: []const u8, detail: anytype) anyerror {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "cannot derive Obj-C type encoding: {s} (detail={any})", .{ reason, detail });
|
||||
}
|
||||
return error.ObjcEncodingUnsupported;
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
Reference in New Issue
Block a user