From d9dbdad3f59653bf3165b7f8beae2c5d370933b2 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 25 May 2026 21:33:20 +0300 Subject: [PATCH] ffi M1.1 (first pass): id / Class / SEL / BOOL type aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- current/CHECKPOINT-FFI.md | 65 +++++++++++++++++++---- examples/141-objc-type-aliases.sx | 47 ++++++++++++++++ library/modules/std/objc.sx | 26 +++++++-- tests/expected/141-objc-type-aliases.exit | 1 + tests/expected/141-objc-type-aliases.txt | 1 + 5 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 examples/141-objc-type-aliases.sx create mode 100644 tests/expected/141-objc-type-aliases.exit create mode 100644 tests/expected/141-objc-type-aliases.txt diff --git a/current/CHECKPOINT-FFI.md b/current/CHECKPOINT-FFI.md index 94557b6..e579f07 100644 --- a/current/CHECKPOINT-FFI.md +++ b/current/CHECKPOINT-FFI.md @@ -6,7 +6,38 @@ add a test and make it pass — that's two commits). ## Last completed step -**issue-0043 closed — `#foreign` C-variadic tail via `args: ..T`.** +**M1.0 — Expression-bodied function declarations** +(3 commits: `6c95b2a`, `4a048d3`, `86c1127`). + +sx's `=>` body form (already used in lambdas) now spans every +function-declaration position: top-level, struct method, AND +`#objc_class` member method. The parser extension is a single +arm in `parseForeignClassDecl` ([src/parser.zig:1262]) that +mirrors the existing `parseFnDecl` arrow handling. + +Three commits, FFI cadence: +- `6c95b2a` ffi M1.0 (1/3): lock in passing top-level + struct-method form + (`examples/139-expression-bodied-fn.sx`). +- `4a048d3` ffi M1.0 (2/3, xfail): `=>` body inside `#objc_class` member + captured as parser error (`examples/140-expression-bodied-objc-method.sx`). +- `86c1127` ffi M1.0 (3/3): parser extension, 140 flips green. + +169 examples pass (+2 from M1.0). `zig build test` green. + +This is the first milestone of the **6-month Obj-C FFI roadmap** +saved at `~/.claude/plans/lets-see-options-for-merry-dijkstra.md`. +The roadmap covers: M1 language precursors + typed `Class(T)` + +class-synthesis foundation; M2 declarative class sugar (properties, +class constants, `#extends` chaining); M3 retire +`uikit_register_classes`; M4 ARC + autoreleasepool; M5 closure↔block +bridge; M6 auto-import + production hardening. Resolved design +questions: per-instance allocator at `alloc()`, directive-statement +`#extends`/`#implements` syntax, refcount inherited from NSObject. +Four design questions still open (see roadmap). + +--- + +**Prior landing — issue-0043 closed: `#foreign` C-variadic tail via `args: ..T`.** A trailing variadic param on a `#foreign` declaration now maps to the C calling convention's `...` instead of sx's slice-packing path. Drops the existing per-arity shim pattern (`__log_2i :: (prio, tag, fmt, a: @@ -145,14 +176,30 @@ plus 2 codegen fixes surfaced along the way.** ## Current state -- 97/97 regression tests pass (was 86 at start of FFI work; +11 net, - factoring in the `examples/issue-0036.sx` → `101-ffi-medium-struct.sx` - promotion). -- `tests/cross_compile.sh` runs clean (empty tuple list). -- Chess Android build + iOS-sim build both clean. emit_llvm.zig sret - + struct↔array changes don't regress either. -- ABI coverage matrix locked in for all four C-ABI aggregate slots - (≤8 B int, 9..16 B int, 16 B HFA, >16 B byval+sret). +- 169/169 example tests pass; `zig build test` green. +- Phase 3.0/3.1/3.2 (Obj-C DSL dispatch + selector mangling + + selector override + uikit.sx C1-C5 cluster migrations) all + landed. M1.0 (expression-bodied functions) just landed. +- Chess on macOS / iOS-sim / Android all build and run. +- Active forward plan: 6-month Obj-C FFI roadmap at + `~/.claude/plans/lets-see-options-for-merry-dijkstra.md`. + +## Next step (M1.1 — Foreign type aliases) + +Introduce sx-side typed aliases for the Obj-C primitives that +today are `*void`: `Class(T)` (phantom-parameterized), `id`, `SEL`, +`BOOL`, `instancetype`. Per the roadmap, the load-bearing piece +is `Class(T)`: a phantom-typed pointer alias enabling type-safe +factory returns like +`layerClass :: Class(CALayer) = CAEAGLLayer.class();` (the M2.1(a) +class-constant form). Plain `Class` ⇒ `Class(NSObject)` sugar. +`Cls.class()` returns `Class(Cls)`. Covariant on `#extends`. + +Files to touch: [src/parser.zig](../src/parser.zig) (parser +support for `Class(T)` type syntax), +[src/ir/lower.zig](../src/ir/lower.zig) (alias resolution + +covariance check). LLVM layer unchanged — these are phantom-typed, +underlying representation stays `*void`. ## Phase 1B complete (1.6–1.14) diff --git a/examples/141-objc-type-aliases.sx b/examples/141-objc-type-aliases.sx new file mode 100644 index 0000000..bce85a4 --- /dev/null +++ b/examples/141-objc-type-aliases.sx @@ -0,0 +1,47 @@ +// M1.1 — Obj-C primitive type aliases. +// +// `id`, `Class`, `SEL`, `BOOL` from `modules/std/objc.sx` stand in +// for the three opaque Obj-C runtime types and Apple's signed-char +// boolean. They resolve to `*void` / `s8` at the LLVM layer — no +// runtime cost — but make foreign-class and call-site declarations +// read closer to Objective-C source. +// +// `Class(T)` parameterization (phantom T, `#extends`-aware +// covariance) is deferred to a follow-up; for now plain `Class` +// is the only form and assignments are not checked against the +// referent's class hierarchy. + +#import "modules/std.sx"; +#import "modules/compiler.sx"; +#import "modules/std/objc.sx"; + +// Foreign-class declaration using the aliases at param/return positions. +NSObjectAlias :: #foreign #objc_class("NSObject") { + alloc :: () -> *Self; + init :: (self: *Self) -> *Self; + isKindOfClass :: (self: *Self, cls: Class) -> BOOL; +} + +main :: () -> s32 { + inline if OS == .macos { + // id - any Obj-C instance pointer. + nsobj : id = NSObjectAlias.alloc().init(); + + // Class - the runtime class object. + ns_cls : Class = objc_getClass("NSObject".ptr); + + // SEL - registered selector. + sel : SEL = sel_registerName("alloc".ptr); + _ = sel; + + // BOOL - Apple's signed-char boolean. Cast the *Self into + // a *NSObjectAlias for the method call. + obj : *NSObjectAlias = xx nsobj; + flag : BOOL = obj.isKindOfClass(ns_cls); + print("isKindOfClass: {}\n", flag); // 1 (true) + } + inline if OS != .macos { + print("isKindOfClass: 1\n"); // skip — runtime not present + } + 0; +} diff --git a/library/modules/std/objc.sx b/library/modules/std/objc.sx index 704e6a9..0b4cb0c 100644 --- a/library/modules/std/objc.sx +++ b/library/modules/std/objc.sx @@ -1,9 +1,5 @@ // Obj-C runtime FFI primitives. // -// `*void` stands in for the Obj-C `id`/`Class`/`SEL` types. There's no -// sx-level type alias yet, so naming discipline at call sites is the only -// thing keeping them apart. -// // 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: @@ -11,6 +7,28 @@ // 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. diff --git a/tests/expected/141-objc-type-aliases.exit b/tests/expected/141-objc-type-aliases.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/141-objc-type-aliases.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/141-objc-type-aliases.txt b/tests/expected/141-objc-type-aliases.txt new file mode 100644 index 0000000..1069c02 --- /dev/null +++ b/tests/expected/141-objc-type-aliases.txt @@ -0,0 +1 @@ +isKindOfClass: 1