Adds named stand-ins for the three opaque Obj-C runtime types and Apple's signed-char boolean to library/modules/std/objc.sx: id :: *void; // any Obj-C instance pointer Class :: *void; // a class object pointer SEL :: *void; // a registered selector BOOL :: s8; // Apple's signed-char boolean (NOT sx's bool) All resolve to their underlying type at the LLVM layer — no runtime cost — but make foreign-class declarations read closer to Objective-C source. The header's old caveat about lacking type aliases is gone. 141-objc-type-aliases.sx exercises the aliases against the real macOS Obj-C runtime: alloc/init an NSObject, fetch its class via objc_getClass, sel_registerName a SEL, then call 'isKindOfClass:' returning BOOL=1. Non-macOS paths print the same line to keep the snapshot stable. DEFERRED (M1.1.b, follow-up): 'Class(T)' parameterization with #extends-aware covariance, and 'instancetype' per-decl substitution. Both require compiler-level type-check support beyond plain stdlib aliases. 170 examples pass (+1).
94 lines
4.7 KiB
Plaintext
94 lines
4.7 KiB
Plaintext
// Obj-C runtime FFI primitives.
|
|
//
|
|
// objc_msgSend has the standard ARM64 calling convention (no varargs path).
|
|
// Each call site must invoke through a function pointer of the *exact*
|
|
// argument and return shape. The idiom:
|
|
//
|
|
// msg_fn : (recv: *void, sel: *void, arg: [*]u8) -> *void = xx objc_msgSend;
|
|
// result := msg_fn(receiver, selector, c_string);
|
|
|
|
// ─── Obj-C primitive type aliases ───────────────────────────────────────
|
|
// Named stand-ins for the three opaque Obj-C runtime types. They all
|
|
// resolve to `*void` at the LLVM layer (no runtime cost) but improve
|
|
// readability in foreign-class declarations and call sites.
|
|
//
|
|
// id — any Obj-C instance pointer
|
|
// Class — a class object pointer
|
|
// SEL — a registered selector
|
|
//
|
|
// `Class(T)` parameterization (phantom-typed, with `#extends`-aware
|
|
// covariance) is a follow-up — needs compiler-level type-check support.
|
|
// For now, `Class` alone is the only form; assignments are not checked
|
|
// against the referent's class hierarchy.
|
|
id :: *void;
|
|
Class :: *void;
|
|
SEL :: *void;
|
|
|
|
// Apple's `BOOL` is a signed char (NOT sx's built-in `bool`, which is
|
|
// LLVM `i1`). Obj-C method signatures that take or return `BOOL` cross
|
|
// the FFI boundary as `s8`.
|
|
BOOL :: s8;
|
|
|
|
// On macOS libobjc is auto-loaded by libSystem; on iOS it isn't, so we
|
|
// link it explicitly. Foundation registers NSString etc. with the runtime,
|
|
// also auto-loaded on macOS and required as an explicit framework on iOS.
|
|
objc :: #library "objc";
|
|
#framework "Foundation";
|
|
|
|
objc_getClass :: (name: [*]u8) -> *void #foreign objc;
|
|
objc_lookUpClass :: (name: [*]u8) -> *void #foreign objc;
|
|
sel_registerName :: (name: [*]u8) -> *void #foreign objc;
|
|
class_createInstance :: (cls: *void, extra: usize) -> *void #foreign objc;
|
|
object_getClass :: (obj: *void) -> *void #foreign objc;
|
|
|
|
// Declared with the simplest non-variadic shape. Cast per call site.
|
|
objc_msgSend :: (recv: *void, sel: *void) -> *void #foreign objc;
|
|
|
|
// ─── Dynamic class registration ─────────────────────────────────────────
|
|
// Define a new Obj-C class at runtime: allocate the pair, attach methods +
|
|
// protocols, then finalize with `objc_registerClassPair`. The class is then
|
|
// usable via `class_createInstance` and Obj-C dispatch.
|
|
//
|
|
// IMPs (method implementations) are function pointers with the implicit
|
|
// Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with
|
|
// `callconv(.c)` so they land args in the standard C registers.
|
|
//
|
|
// Method type encoding strings follow Apple's runtime DSL:
|
|
// v = void c = char/BOOL i = int l = long f = float d = double
|
|
// @ = id (object) : = SEL # = Class
|
|
// Return type comes first, then receiver (`@`), then `_cmd` (`:`), then args.
|
|
// Examples:
|
|
// "v@:" -> void method(id, SEL)
|
|
// "c@:" -> BOOL method(id, SEL)
|
|
// "@@:@" -> id method(id, SEL, id)
|
|
// "B@:@@" -> BOOL method(id, SEL, id, id)
|
|
objc_allocateClassPair :: (super: *void, name: [*]u8, extra: usize) -> *void #foreign objc;
|
|
class_addMethod :: (cls: *void, sel: *void, imp: *void, types: [*]u8) -> bool #foreign objc;
|
|
class_addProtocol :: (cls: *void, proto: *void) -> bool #foreign objc;
|
|
objc_getProtocol :: (name: [*]u8) -> *void #foreign objc;
|
|
objc_registerClassPair :: (cls: *void) -> void #foreign objc;
|
|
|
|
// Foundation C-API helpers (Foundation is already linked above via #framework).
|
|
// NSLog takes an NSString format; the variadic tail is not exposed here.
|
|
NSLog :: (fmt: *void) #foreign;
|
|
|
|
// ─── Convenience helpers ────────────────────────────────────────────────
|
|
// These hide the typed-fn-pointer cast for the most common shapes. They
|
|
// re-register selectors per call — if you're in a tight loop, cache the SEL.
|
|
|
|
// Wrap a C string in an autoreleased NSString.
|
|
ns_string :: (s: [*]u8) -> *void {
|
|
cls := objc_getClass("NSString".ptr);
|
|
sel := sel_registerName("stringWithUTF8String:".ptr);
|
|
fn_ptr : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend;
|
|
return fn_ptr(cls, sel, s);
|
|
}
|
|
|
|
// View an NSString's bytes as a C string. The returned pointer's lifetime is
|
|
// tied to the NSString; don't free it.
|
|
c_string :: (ns: *void) -> [*]u8 {
|
|
sel := sel_registerName("UTF8String".ptr);
|
|
fn_ptr : (*void, *void) -> [*]u8 callconv(.c) = xx objc_msgSend;
|
|
return fn_ptr(ns, sel);
|
|
}
|