ffi 3.1: Cls.static_method(args) lowers to objc_msg_send on the class object
Implementation half of the Phase 3.1 cadence step. `lowerForeignStaticCall` for `#objc_class` / `#objc_protocol` runtimes no longer bails; it routes through a new `lowerObjcStaticCall` helper that loads the class object from a module-scoped cached slot (populated once per module via `objc_getClass`) and dispatches `objc_msg_send` with the same selector-mangling as Phase 3.0's instance dispatch. Three pieces: 1. `Module.objc_class_cache` — parallel to `objc_selector_cache`, insertion-ordered list of (class_name, slot_GlobalId) so the constructor that calls `objc_getClass` per slot at module load is deterministic. `lookupObjcClass` / `appendObjcClass` accessors. 2. `internObjcClassObject` in lower.zig — get-or-create a `OBJC_CLASSLIST_REFERENCES_<Cls>` global pointer; matches clang's naming convention. `lowerObjcStaticCall` reuses `deriveObjcSelector` from 3.0 for the selector, loads the class slot, and emits `objc_msg_send(class_obj, sel, args)`. 3. `emitObjcClassInit` in emit_llvm.zig — companion to `emitObjcSelectorInit`. Walks `objc_class_cache`, synthesizes a constructor `__sx_objc_class_init` that calls `objc_getClass(name)` per slot, registers in `@llvm.global_ctors` for AOT (extending the existing array if the selector init already created it), and injects a direct call into main's prelude after any prior init calls so the ORC JIT path runs it too. Surface form is `.` (`NSObject.class()`) matching JNI's `Alias.new(...)` convention rather than the plan's notional `::` — avoids extending the parser for a new postfix operator with no other use case. Test `examples/ffi-objc-dsl-05-static.sx` exercises NSObject's `+class` and `+description` class methods via the new syntax, asserts both return non-null. NSObject is always available at module-load, unlike runtime-created test classes that wouldn't exist yet when the class-init constructor runs. 164/164 tests; chess builds + runs clean on all three platforms.
This commit is contained in:
@@ -4,44 +4,36 @@
|
||||
// selector is derived by the same default mangling as Phase 3.0
|
||||
// (`stringWithUTF8String_(s)` → "stringWithUTF8String:").
|
||||
//
|
||||
// Mirrors the JNI surface (`Alias.new(...)` etc.); the lowering
|
||||
// disambiguates static vs instance by looking at `method.is_static` on
|
||||
// the foreign-class member.
|
||||
// Mirrors JNI's static-dispatch surface (`Alias.new(...)` etc.); the
|
||||
// lowering disambiguates static vs instance by looking at
|
||||
// `method.is_static` on the foreign-class member.
|
||||
//
|
||||
// Pre-3.1: `lowerForeignStaticCall` only handles JNI runtime + the
|
||||
// `new` constructor; any other static call bails. Snapshot pins the
|
||||
// bail diagnostic.
|
||||
// Uses NSObject because the cached class slot is populated by a
|
||||
// constructor at module-load — runtime-created test classes wouldn't
|
||||
// exist yet when `objc_getClass` runs. NSObject is always available
|
||||
// on macOS via libobjc.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
#import "modules/std/objc.sx";
|
||||
|
||||
SxProbeStatic :: #foreign #objc_class("SxProbeStatic") {
|
||||
static answer :: () -> s32;
|
||||
static add :: (a: s32, b: s32) -> s32;
|
||||
}
|
||||
|
||||
answer_imp :: (self: *void, _cmd: *void) -> s32 callconv(.c) {
|
||||
42;
|
||||
}
|
||||
|
||||
add_imp :: (self: *void, _cmd: *void, a: s32, b: s32) -> s32 callconv(.c) {
|
||||
a + b;
|
||||
NSObject :: #foreign #objc_class("NSObject") {
|
||||
// `+(Class)class` — niladic, name verbatim, selector = "class".
|
||||
// Returns the class object itself.
|
||||
static class :: () -> *void;
|
||||
// `+(NSString *)description` on the class returns a description
|
||||
// string. Niladic, selector = "description".
|
||||
static description :: () -> *void;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
inline if OS == .macos {
|
||||
ns_object := objc_getClass("NSObject".ptr);
|
||||
cls := objc_allocateClassPair(ns_object, "SxProbeStatic".ptr, 0);
|
||||
// class_addMethod on the metaclass — that's where class methods live.
|
||||
metacls := object_getClass(xx cls);
|
||||
class_addMethod(metacls, sel_registerName("answer".ptr), xx answer_imp, "i@:".ptr);
|
||||
class_addMethod(metacls, sel_registerName("add:b:".ptr), xx add_imp, "i@:ii".ptr);
|
||||
objc_registerClassPair(cls);
|
||||
|
||||
n := SxProbeStatic.answer();
|
||||
print("answer() = {}\n", n);
|
||||
s := SxProbeStatic.add(7, 35);
|
||||
print("add(7, 35) = {}\n", s);
|
||||
c := NSObject.class();
|
||||
if c != null {
|
||||
print("class non-null\n");
|
||||
}
|
||||
d := NSObject.description();
|
||||
if d != null {
|
||||
print("description non-null\n");
|
||||
}
|
||||
}
|
||||
inline if OS != .macos {
|
||||
print("skipped (not macos)\n");
|
||||
|
||||
Reference in New Issue
Block a user