Files
sx/examples/ffi-objc-call-09-in-construct.sx
agra 6dab8a157f ffi 1.11–1.13: #objc_call inside struct method / protocol / closure / generic
108/108 regression tests pass (+ffi-objc-call-09-in-construct,
+issue-0038 from the prior commit).

One trivial Obj-C call (`[obj hash]` returning NSUInteger) routed
through four sx surface constructs:

  1. struct method body          Probe.fetch
  2. protocol impl method body   impl Hashable for Probe
  3. closure value body          make_hasher
  4. generic function body       hash_through(recv: $T)

No new ABI shapes touched — pins that the `objc_msg_send` lowering
emits identical call shapes regardless of enclosing scope. Each
case validates the result `h_N == h_1` after threading `recv`
appropriately for each context.

The closure path reaches `recv` via a module-level global rather
than capturing the surrounding parameter — issue-0038 (prior
commit) documents the closure free-variable analyzer missing the
`FfiIntrinsicCall` node, with a clean workaround pinned.
2026-05-19 18:57:41 +03:00

86 lines
3.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Phase 1 steps 1.111.13 (PLAN-FFI.md): `#objc_call` call sites
// embedded inside the sx surface constructs. None touch a new ABI
// path — the lowering routes the call identically regardless of
// the enclosing scope, and this test pins that lemma.
//
// 1. Struct method body Probe.fetch
// 2. Protocol impl method body impl Hashable for Probe
// 3. Closure value body closure that calls hash
//
// 1.14 (separate test): `inline if OS == { case }` gating across
// targets — verified by `tests/cross_compile.sh`.
#import "modules/std.sx";
#import "modules/compiler.sx";
#import "modules/std/objc.sx";
// ── 1. Struct method calling #objc_call ─────────────────────────────
Probe :: struct {
receiver: *void = null;
fetch :: (self: *Probe) -> s64 {
#objc_call(s64)(self.receiver, "hash");
}
}
// ── 2. Protocol impl method ────────────────────────────────────────
Hashable :: protocol {
sx_hash :: (self: *Self) -> s64;
}
impl Hashable for Probe {
sx_hash :: (self: *Probe) -> s64 {
#objc_call(s64)(self.receiver, "hash") * 2;
}
}
// ── 3. Closure body invoking #objc_call ─────────────────────────────
// Closure-captured `recv` isn't traced through the `#objc_call` AST
// node by sema today, so we reach the receiver via a module-level
// global. The lemma we lock here is that lowering routes the call
// the same way inside a closure body as it does at top level.
g_hasher_recv : *void = null;
make_hasher :: () -> Closure(s32) -> s64 {
closure((dummy: s32) -> s64 => #objc_call(s64)(g_hasher_recv, "hash"));
}
// ── 4. Generic function body — instantiated per call site ───────────
hash_through :: (recv: $T) -> s64 {
p : *void = xx recv;
#objc_call(s64)(p, "hash");
}
main :: () -> s32 {
inline if OS == .macos {
ns_object := objc_getClass("NSObject".ptr);
p : Probe = .{ receiver = ns_object };
// 1. struct method
h1 := p.fetch();
print("fetch != 0 = {}\n", h1 != 0);
// 2. protocol method (doubles the raw hash; mostly checking
// dispatch / arg threading, not the math)
h2 := p.sx_hash();
print("protocol h2 = {}\n", h2 == h1 * 2);
// 3. closure (receives a dummy arg to keep the `Closure(T) -> R`
// arity matching 35-closures.sx; recv comes via a global —
// closure capture through `#objc_call` AST nodes isn't
// traced by sema today and would error "unresolved").
g_hasher_recv = ns_object;
hasher := make_hasher();
h3 := hasher(0);
print("closure h3 = {}\n", h3 == h1);
// 4. generic function — instantiates with T = *void here
h4 := hash_through(ns_object);
print("generic h4 = {}\n", h4 == h1);
}
inline if OS != .macos {
print("skipped (not macos)\n");
}
0;
}