ffi M1.2 A.7: open the dispatch gate — sx-defined class methods callable
Delete the bail at lower.zig:4407 that diagnosed sx-defined Obj-C
class dispatch as 'not yet supported'. Both foreign and
sx-defined '#objc_class' decls now flow through the same
'lowerObjcMethodCall' path — instance methods on sx-defined
classes dispatch via objc_msgSend, and the registered IMP
trampolines (M1.2 A.4b.iii) route to the sx bodies.
The runtime non-Obj-C branch (.swift_class / .swift_struct /
.swift_protocol) keeps its 'not yet supported' diagnostic;
M1.2 only addresses the Obj-C runtimes.
Constructor reorder in emit_llvm: emitObjcDefinedClassInit
runs BEFORE emitObjcClassInit. Otherwise the Phase 3.1
class-cache populator calls objc_getClass("SxFoo") before our
constructor registers the class — cache slot stored null and
'SxFoo.method()' dispatched against a null class pointer.
ffi-objc-defined-class-01-instance.sx (the integration test
from the plan) now runs the full lifecycle on macOS:
f := SxFoo.alloc() // synthesized +alloc IMP fires
f.bump() // dispatch → IMP trampoline → sx body
f.bump() // state persists across calls
f.bump()
f.get() // → 3
release_fn(f, sel_release) // synthesized -dealloc fires
The user declares 'alloc :: () -> *SxFoo;' bodyless to give the
synthesized +alloc IMP a typed contract at sx call sites —
same convention as foreign classes today.
M1.2 complete: A.0 A.1 A.2 A.3 A.4 A.4b.i A.4b.ii A.4b.iii
A.5 A.6 A.7. End-to-end class-synthesis foundation works.
177 example tests pass (+1 from the integration test). zig
build test green.
This commit is contained in:
@@ -275,19 +275,20 @@ pub const LLVMEmitter = struct {
|
||||
// Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5).
|
||||
self.emitObjcSelectorInit();
|
||||
|
||||
// Pass 2.5b: Emit Obj-C class-object init constructor (Phase 3.1).
|
||||
// Pass 2.5b: Emit Obj-C class-pair registration constructor for
|
||||
// sx-defined classes (M1.2 A.4+). Runs BEFORE the foreign
|
||||
// class-cache populator (2.5c) so a sx-defined class is already
|
||||
// registered with the Obj-C runtime by the time
|
||||
// `objc_getClass(\"SxFoo\")` runs to populate the Phase 3.1
|
||||
// class-object cache — otherwise the cache slot would store
|
||||
// null and `SxFoo.method()` dispatches against null.
|
||||
self.emitObjcDefinedClassInit();
|
||||
|
||||
// Pass 2.5c: Emit Obj-C class-object init constructor (Phase 3.1).
|
||||
// Same shape as the selector init — populates the per-module
|
||||
// cached `Class*` slots via `objc_getClass` at module-init time.
|
||||
self.emitObjcClassInit();
|
||||
|
||||
// Pass 2.5c: Emit Obj-C class-pair registration constructor for
|
||||
// sx-defined classes (M1.2 A.4). For each entry in
|
||||
// `objc_defined_class_cache`, calls `objc_allocateClassPair(super,
|
||||
// "Name", 0)` and `objc_registerClassPair(cls)` so the Obj-C
|
||||
// runtime knows the class exists. Methods, ivars, and the +alloc
|
||||
// override come in A.4b / A.5 / A.6.
|
||||
self.emitObjcDefinedClassInit();
|
||||
|
||||
// Pass 2.6: On macOS, chdir to the .app bundle's Resources dir at
|
||||
// startup so relative asset paths work when Finder/`open`
|
||||
// launches the binary with CWD=/. Non-bundled binaries no-op.
|
||||
|
||||
@@ -4431,22 +4431,24 @@ pub const Lowering = struct {
|
||||
return Ref.none;
|
||||
};
|
||||
|
||||
if (!fcd.is_foreign) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "sx-defined foreign classes can't yet be dispatched into (class '{s}' missing '#foreign' modifier? — runtime synthesis is a follow-up)", .{fcd.name});
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
// Obj-C instance dispatch (Phase 3 step 3.0). `inst.method(args)` on
|
||||
// an `#objc_class` / `#objc_protocol` receiver derives a selector
|
||||
// from the sx method name (default mangling: split on `_`, each
|
||||
// piece becomes a keyword with a trailing `:`; niladic stays
|
||||
// verbatim) and lowers to `objc_msg_send`. The Swift runtimes
|
||||
// still bail — Phase 4.
|
||||
// Obj-C instance dispatch (Phase 3 step 3.0 + M1.2 A.7).
|
||||
// `inst.method(args)` on an `#objc_class` / `#objc_protocol`
|
||||
// receiver derives a selector from the sx method name (default
|
||||
// mangling: split on `_`, each piece becomes a keyword with a
|
||||
// trailing `:`; niladic stays verbatim) and lowers to
|
||||
// `objc_msg_send`. Both foreign and sx-defined classes flow
|
||||
// through the same path — sx-defined classes have their IMPs
|
||||
// registered at module-init (M1.2 A.4b.iii) so `objc_msgSend`
|
||||
// finds them. The Swift runtimes still bail — Phase 4.
|
||||
if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) {
|
||||
return self.lowerObjcMethodCall(fcd, method, target, method_args, span);
|
||||
}
|
||||
if (!fcd.is_foreign) {
|
||||
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) });
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
if (fcd.runtime != .jni_class and fcd.runtime != .jni_interface) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "method calls on '{s}' runtime not yet supported (Phase 3/4)", .{@tagName(fcd.runtime)});
|
||||
|
||||
Reference in New Issue
Block a user