refactor(ir): extract ObjcLowering (ffi_objc.zig) for pure Obj-C decision helpers (A6.1 step 2)
Move the pure Obj-C decision helpers out of lower.zig into src/ir/ffi_objc.zig
behind an ObjcLowering *Lowering facade (Principle 5, like the A4/A5 resolvers).
Behavior-preserving relocation — the only non-self.l rewrites are facade
plumbing.
Moved verbatim (self. -> self.l. for Lowering members):
- deriveObjcSelector (selector derivation)
- objcTypeEncodingFromSignature + appendObjcEncoding + bailObjcEncoding +
the ObjcEncodingStack type
- objcPropertyKind + the ObjcPropertyKind enum
- isObjcClassPointer
- objcDefinedStateStructType + objcStateAllocatorType
Emission-heavy code stays in lower.zig per PLAN A6.1 step 6: emitObjc* IMP
builders, lowerObjc*Call, registerObjc*, declareObjc*, the lookupObjc* property/
state lookups, and the Self-substitution resolvers.
- Call sites rerouted through a new objc() accessor: 15 in lower.zig, 1 in
expr_typer.zig, 39 in lower.test.zig (the A6.1 scaffolding tests now drive the
facade). No Lowering wrappers kept. Barrel-wired ffi_objc + ObjcLowering.
- No new visibility widening beyond sub-step 1's two pubs — the facade reads
self.l.{alloc,module,program_index,diagnostics} (fields) + the already-pub
resolveType. lower.zig -478 (->16615); ffi_objc.zig 428.
- Doc-only re-home: the property-IMP getter/setter comment was attached (a
pre-existing artifact) to the moving ObjcPropertyKind enum, two decls away from
its real subject emitObjcDefinedClassPropertyImps (which had no doc). Re-homed
it there so the move neither orphans a `///` block (Zig errors on a dangling doc
comment) nor misattributes it to ensureArcRuntimeDecls.
Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0
(48 13xx Obj-C examples + 4 Obj-C .ir snapshots green, no churn).
This commit is contained in:
@@ -296,42 +296,42 @@ test "lower: objcTypeEncodingFromSignature emits primitive shapes" {
|
||||
var lowering = Lowering.init(&module);
|
||||
|
||||
// Niladic void method: -(void)greet → "v@:"
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(.void, &.{}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("v@:", e1);
|
||||
|
||||
// Returns s32, takes s32: -(int)add:(int)x → "i@:i"
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(.s32, &.{.s32}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.s32, &.{.s32}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("i@:i", e2);
|
||||
|
||||
// s64 return, two s64 args: "q@:qq"
|
||||
const e3 = try lowering.objcTypeEncodingFromSignature(.s64, &.{ .s64, .s64 }, null);
|
||||
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.s64, &.{ .s64, .s64 }, null);
|
||||
defer alloc.free(e3);
|
||||
try std.testing.expectEqualStrings("q@:qq", e3);
|
||||
|
||||
// BOOL return (s8): "c@:"
|
||||
const e4 = try lowering.objcTypeEncodingFromSignature(.s8, &.{}, null);
|
||||
const e4 = try lowering.objc().objcTypeEncodingFromSignature(.s8, &.{}, null);
|
||||
defer alloc.free(e4);
|
||||
try std.testing.expectEqualStrings("c@:", e4);
|
||||
|
||||
// Float/double: "f@:d"
|
||||
const e5 = try lowering.objcTypeEncodingFromSignature(.f32, &.{.f64}, null);
|
||||
const e5 = try lowering.objc().objcTypeEncodingFromSignature(.f32, &.{.f64}, null);
|
||||
defer alloc.free(e5);
|
||||
try std.testing.expectEqualStrings("f@:d", e5);
|
||||
|
||||
// bool (i1) is `B` — distinct from BOOL (`c`).
|
||||
const e6 = try lowering.objcTypeEncodingFromSignature(.bool, &.{.bool}, null);
|
||||
const e6 = try lowering.objc().objcTypeEncodingFromSignature(.bool, &.{.bool}, null);
|
||||
defer alloc.free(e6);
|
||||
try std.testing.expectEqualStrings("B@:B", e6);
|
||||
|
||||
// usize / isize on the 64-bit target.
|
||||
const e7 = try lowering.objcTypeEncodingFromSignature(.usize, &.{.isize}, null);
|
||||
const e7 = try lowering.objc().objcTypeEncodingFromSignature(.usize, &.{.isize}, null);
|
||||
defer alloc.free(e7);
|
||||
try std.testing.expectEqualStrings("Q@:q", e7);
|
||||
|
||||
// Unsigned variants u8/u16/u32/u64.
|
||||
const e8 = try lowering.objcTypeEncodingFromSignature(.u32, &.{ .u8, .u16, .u64 }, null);
|
||||
const e8 = try lowering.objc().objcTypeEncodingFromSignature(.u32, &.{ .u8, .u16, .u64 }, null);
|
||||
defer alloc.free(e8);
|
||||
try std.testing.expectEqualStrings("I@:CSQ", e8);
|
||||
}
|
||||
@@ -344,19 +344,19 @@ test "lower: objcTypeEncodingFromSignature emits pointer shapes" {
|
||||
|
||||
// Generic `*void` → `^v`.
|
||||
const void_ptr = module.types.ptrTo(.void);
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(void_ptr, &.{void_ptr}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(void_ptr, &.{void_ptr}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("^v@:^v", e1);
|
||||
|
||||
// `[*]u8` C-string carrier → `*`.
|
||||
const u8_many = module.types.intern(.{ .many_pointer = .{ .element = .u8 } });
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(.void, &.{u8_many}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{u8_many}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("v@:*", e2);
|
||||
|
||||
// `[*]s32` (non-u8 many-pointer) → `^v`.
|
||||
const s32_many = module.types.intern(.{ .many_pointer = .{ .element = .s32 } });
|
||||
const e3 = try lowering.objcTypeEncodingFromSignature(.void, &.{s32_many}, null);
|
||||
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{s32_many}, null);
|
||||
defer alloc.free(e3);
|
||||
try std.testing.expectEqualStrings("v@:^v", e3);
|
||||
}
|
||||
@@ -390,7 +390,7 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
|
||||
.is_main = false,
|
||||
};
|
||||
|
||||
const state_ty = lowering.objcDefinedStateStructType(&fcd);
|
||||
const state_ty = lowering.objc().objcDefinedStateStructType(&fcd);
|
||||
const info = module.types.get(state_ty);
|
||||
try std.testing.expectEqual(@as(std.meta.Tag(@TypeOf(info)), .@"struct"), std.meta.activeTag(info));
|
||||
|
||||
@@ -403,7 +403,7 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
|
||||
try std.testing.expectEqual(TypeId.s64, s.fields[1].ty);
|
||||
|
||||
// Idempotency: a second call returns the same TypeId (cache hit on name).
|
||||
const state_ty2 = lowering.objcDefinedStateStructType(&fcd);
|
||||
const state_ty2 = lowering.objc().objcDefinedStateStructType(&fcd);
|
||||
try std.testing.expectEqual(state_ty, state_ty2);
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ test "lower: objcDefinedStateStructType handles empty field set" {
|
||||
.is_main = false,
|
||||
};
|
||||
|
||||
const state_ty = lowering.objcDefinedStateStructType(&fcd);
|
||||
const state_ty = lowering.objc().objcDefinedStateStructType(&fcd);
|
||||
const info = module.types.get(state_ty);
|
||||
try std.testing.expectEqualStrings("__SxEmptyState", module.types.getString(info.@"struct".name));
|
||||
try std.testing.expectEqual(@as(usize, 0), info.@"struct".fields.len);
|
||||
@@ -454,7 +454,7 @@ test "lower: objcDefinedStateStructType skips non-field members" {
|
||||
.is_main = false,
|
||||
};
|
||||
|
||||
const state_ty = lowering.objcDefinedStateStructType(&fcd);
|
||||
const state_ty = lowering.objc().objcDefinedStateStructType(&fcd);
|
||||
const info = module.types.get(state_ty);
|
||||
try std.testing.expectEqual(@as(usize, 1), info.@"struct".fields.len);
|
||||
try std.testing.expectEqualStrings("counter", module.types.getString(info.@"struct".fields[0].name));
|
||||
@@ -482,12 +482,12 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
|
||||
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
|
||||
|
||||
// Return *NSString, no args: "@@:"
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(ns_ptr, &.{}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(ns_ptr, &.{}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("@@:", e1);
|
||||
|
||||
// Return *NSString, take *NSString: "@@:@"
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(ns_ptr, &.{ns_ptr}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(ns_ptr, &.{ns_ptr}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("@@:@", e2);
|
||||
}
|
||||
@@ -515,14 +515,14 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
|
||||
// `?s64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary.
|
||||
const opt_s64 = module.types.optionalOf(.s64);
|
||||
const opt_ns = module.types.optionalOf(ns_ptr);
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(opt_ns, &.{opt_s64}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(opt_ns, &.{opt_s64}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("@@:q", e1);
|
||||
|
||||
// Nested optional unwrap (`??f64`) — same as `f64` at the wire.
|
||||
const opt_f64 = module.types.optionalOf(.f64);
|
||||
const opt_opt_f64 = module.types.optionalOf(opt_f64);
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(.void, &.{opt_opt_f64}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{opt_opt_f64}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("v@:d", e2);
|
||||
}
|
||||
@@ -544,12 +544,12 @@ test "lower: objcTypeEncodingFromSignature emits structs as {Name=fields...}" {
|
||||
const cgpoint = module.types.intern(.{ .@"struct" = .{ .name = cgpoint_name, .fields = &cgpoint_fields } });
|
||||
|
||||
// `-(void)setOrigin:(CGPoint)p` → `v@:{CGPoint=dd}`
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(.void, &.{cgpoint}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{cgpoint}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("v@:{CGPoint=dd}", e1);
|
||||
|
||||
// `-(CGPoint)origin` → `{CGPoint=dd}@:`
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(cgpoint, &.{}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(cgpoint, &.{}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("{CGPoint=dd}@:", e2);
|
||||
|
||||
@@ -564,7 +564,7 @@ test "lower: objcTypeEncodingFromSignature emits structs as {Name=fields...}" {
|
||||
.{ .name = len_name, .ty = .u64 },
|
||||
};
|
||||
const nsrange = module.types.intern(.{ .@"struct" = .{ .name = nsrange_name, .fields = &nsrange_fields } });
|
||||
const e3 = try lowering.objcTypeEncodingFromSignature(nsrange, &.{ nsrange, .s64 }, null);
|
||||
const e3 = try lowering.objc().objcTypeEncodingFromSignature(nsrange, &.{ nsrange, .s64 }, null);
|
||||
defer alloc.free(e3);
|
||||
try std.testing.expectEqualStrings("{_NSRange=QQ}@:{_NSRange=QQ}q", e3);
|
||||
}
|
||||
@@ -606,12 +606,12 @@ test "lower: objcTypeEncodingFromSignature emits nested structs (CGRect)" {
|
||||
const cgrect = module.types.intern(.{ .@"struct" = .{ .name = cgrect_name, .fields = &cgrect_fields } });
|
||||
|
||||
// `-(CGRect)frame` → `{CGRect={CGPoint=dd}{CGSize=dd}}@:`
|
||||
const e1 = try lowering.objcTypeEncodingFromSignature(cgrect, &.{}, null);
|
||||
const e1 = try lowering.objc().objcTypeEncodingFromSignature(cgrect, &.{}, null);
|
||||
defer alloc.free(e1);
|
||||
try std.testing.expectEqualStrings("{CGRect={CGPoint=dd}{CGSize=dd}}@:", e1);
|
||||
|
||||
// `-(void)setFrame:(CGRect)f` round-trip.
|
||||
const e2 = try lowering.objcTypeEncodingFromSignature(.void, &.{cgrect}, null);
|
||||
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{cgrect}, null);
|
||||
defer alloc.free(e2);
|
||||
try std.testing.expectEqualStrings("v@:{CGRect={CGPoint=dd}{CGSize=dd}}", e2);
|
||||
}
|
||||
@@ -631,20 +631,20 @@ test "lower: deriveObjcSelector — niladic / keyword / multi-keyword / override
|
||||
var lowering = Lowering.init(&module);
|
||||
|
||||
// arity 0 → bare name, no colons, not an override.
|
||||
const niladic = lowering.deriveObjcSelector(objcMethod("count"), 0);
|
||||
const niladic = lowering.objc().deriveObjcSelector(objcMethod("count"), 0);
|
||||
try std.testing.expectEqualStrings("count", niladic.sel);
|
||||
try std.testing.expectEqual(@as(usize, 0), niladic.keyword_count);
|
||||
try std.testing.expectEqual(false, niladic.is_override);
|
||||
|
||||
// arity ≥ 1, no `_` → single trailing colon, one keyword.
|
||||
const single = lowering.deriveObjcSelector(objcMethod("setValue"), 1);
|
||||
const single = lowering.objc().deriveObjcSelector(objcMethod("setValue"), 1);
|
||||
defer alloc.free(single.sel);
|
||||
try std.testing.expectEqualStrings("setValue:", single.sel);
|
||||
try std.testing.expectEqual(@as(usize, 1), single.keyword_count);
|
||||
try std.testing.expectEqual(false, single.is_override);
|
||||
|
||||
// each `_` → `:`, plus a trailing `:`; piece count = (#`_`) + 1.
|
||||
const multi = lowering.deriveObjcSelector(objcMethod("setValue_forKey"), 2);
|
||||
const multi = lowering.objc().deriveObjcSelector(objcMethod("setValue_forKey"), 2);
|
||||
defer alloc.free(multi.sel);
|
||||
try std.testing.expectEqualStrings("setValue:forKey:", multi.sel);
|
||||
try std.testing.expectEqual(@as(usize, 2), multi.keyword_count);
|
||||
@@ -653,7 +653,7 @@ test "lower: deriveObjcSelector — niladic / keyword / multi-keyword / override
|
||||
// `#selector(...)` override: used verbatim, keyword_count = #colons.
|
||||
var m = objcMethod("init_with_frame_style");
|
||||
m.selector_override = "initWithFrame:style:";
|
||||
const overridden = lowering.deriveObjcSelector(m, 2);
|
||||
const overridden = lowering.objc().deriveObjcSelector(m, 2);
|
||||
try std.testing.expectEqualStrings("initWithFrame:style:", overridden.sel);
|
||||
try std.testing.expectEqual(@as(usize, 2), overridden.keyword_count);
|
||||
try std.testing.expectEqual(true, overridden.is_override);
|
||||
@@ -678,7 +678,7 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
|
||||
.is_main = false,
|
||||
};
|
||||
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
|
||||
try std.testing.expect(lowering.isObjcClassPointer(ns_ptr));
|
||||
try std.testing.expect(lowering.objc().isObjcClassPointer(ns_ptr));
|
||||
|
||||
// *NSCopying where NSCopying is a registered Obj-C *protocol* → also true
|
||||
// (the predicate accepts .objc_class OR .objc_protocol).
|
||||
@@ -694,16 +694,16 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
|
||||
.is_main = false,
|
||||
};
|
||||
try lowering.program_index.foreign_class_map.put("NSCopying", &proto_fcd);
|
||||
try std.testing.expect(lowering.isObjcClassPointer(proto_ptr));
|
||||
try std.testing.expect(lowering.objc().isObjcClassPointer(proto_ptr));
|
||||
|
||||
// *Plain where Plain is a non-foreign struct → false.
|
||||
const plain_name = module.types.internString("Plain");
|
||||
const plain_struct = module.types.intern(.{ .@"struct" = .{ .name = plain_name, .fields = &.{} } });
|
||||
try std.testing.expect(!lowering.isObjcClassPointer(module.types.ptrTo(plain_struct)));
|
||||
try std.testing.expect(!lowering.objc().isObjcClassPointer(module.types.ptrTo(plain_struct)));
|
||||
|
||||
// *void and a builtin scalar → false (not object pointers).
|
||||
try std.testing.expect(!lowering.isObjcClassPointer(module.types.ptrTo(.void)));
|
||||
try std.testing.expect(!lowering.isObjcClassPointer(.s32));
|
||||
try std.testing.expect(!lowering.objc().isObjcClassPointer(module.types.ptrTo(.void)));
|
||||
try std.testing.expect(!lowering.objc().isObjcClassPointer(.s32));
|
||||
}
|
||||
|
||||
test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
|
||||
@@ -728,13 +728,13 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
|
||||
// Primitive field, no modifiers → assign (the non-object default).
|
||||
const prim = ast.ForeignFieldDecl{ .name = "count", .field_type = typeKeyword(alloc, "s32"), .is_property = true };
|
||||
defer alloc.destroy(prim.field_type);
|
||||
try std.testing.expect(lowering.objcPropertyKind(prim) == .assign);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(prim) == .assign);
|
||||
|
||||
// Object-pointer field, no modifiers → strong (the object default).
|
||||
const obj_ty = typeKeyword(alloc, "*NSString");
|
||||
defer alloc.destroy(obj_ty);
|
||||
const obj_default = ast.ForeignFieldDecl{ .name = "title", .field_type = obj_ty, .is_property = true };
|
||||
try std.testing.expect(lowering.objcPropertyKind(obj_default) == .strong);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(obj_default) == .strong);
|
||||
|
||||
// Protocol-pointer field → also strong by default (same object-pointer
|
||||
// predicate accepts .objc_protocol).
|
||||
@@ -752,17 +752,17 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
|
||||
const proto_ty = typeKeyword(alloc, "*NSCoding");
|
||||
defer alloc.destroy(proto_ty);
|
||||
const proto_default = ast.ForeignFieldDecl{ .name = "coder", .field_type = proto_ty, .is_property = true };
|
||||
try std.testing.expect(lowering.objcPropertyKind(proto_default) == .strong);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(proto_default) == .strong);
|
||||
|
||||
// Explicit modifiers on an object pointer win over the default.
|
||||
const weak_mods = [_][]const u8{"weak"};
|
||||
try std.testing.expect(lowering.objcPropertyKind(.{ .name = "delegate", .field_type = obj_ty, .is_property = true, .property_modifiers = &weak_mods }) == .weak);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(.{ .name = "delegate", .field_type = obj_ty, .is_property = true, .property_modifiers = &weak_mods }) == .weak);
|
||||
|
||||
const copy_mods = [_][]const u8{"copy"};
|
||||
try std.testing.expect(lowering.objcPropertyKind(.{ .name = "name", .field_type = obj_ty, .is_property = true, .property_modifiers = ©_mods }) == .copy);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(.{ .name = "name", .field_type = obj_ty, .is_property = true, .property_modifiers = ©_mods }) == .copy);
|
||||
|
||||
const assign_mods = [_][]const u8{"assign"};
|
||||
try std.testing.expect(lowering.objcPropertyKind(.{ .name = "raw", .field_type = obj_ty, .is_property = true, .property_modifiers = &assign_mods }) == .assign);
|
||||
try std.testing.expect(lowering.objc().objcPropertyKind(.{ .name = "raw", .field_type = obj_ty, .is_property = true, .property_modifiers = &assign_mods }) == .assign);
|
||||
}
|
||||
|
||||
// ── Pack projection name resolution (Feature 1, Step 2.2) ────────────
|
||||
|
||||
Reference in New Issue
Block a user