ns_string's only caller was impl Into(*NSString) for string, so +stringWithUTF8String: is inlined there. c_string's one use (NSBundle.resourcePath in uikit) becomes rsrc.UTF8String() with resourcePath retyped *NSString. ffi-objc-call-06 and ffi-objc-dsl-07 .ir snapshots regenerated — they only drop the now-absent extern declares.
`format` and `print` move from `..args: []Any` to `..$args`. The
pack-fn machinery monomorphises each call shape, so the
build_format-emitted body's `any_to_string(args[i])` substitutes
to the i-th concrete-typed call arg via packArgNodeAt — no more
runtime Any-boxing for static args. The Any boxing path still
fires for arg positions whose types collapse to `.any` (already
Any-typed inputs).
Net effect:
- Calls with statically-typed args produce per-shape monos
(`print__ct_<fmt_hash>__pack_s64_string_bool` etc). The mono
cache key now reflects both the format string AND the arg
types, so different shapes get distinct emit paths.
- Compile-time arity errors are now possible (callers passing
the wrong number of args mismatch the mono's positional
binding instead of silently mis-boxing).
- Optionals flow through the new `case optional:` arm in
`any_to_string` (commit ce77867); the variadic auto-unwrap
in `packVariadicCallArgs` stays as a fast-path but is no
longer load-bearing.
IR snapshots regenerated for 13 tests where the print/format
mono shape changed the string-constant pool: 142, the ffi-jni
test cluster, ffi-objc-call-03/06, ffi-objc-dsl-07. Test
08-types' undef-memory-read snapshot also shifted (the test
exercises `field = ---` reads from a print call's stack
neighbours; the new pack-mono lays out its stack frame
differently, so the previously-stale 1s now read as 0s — same
undefined behaviour, different garbage).
218/218 example tests + `zig build test` green.
Closes the optional-through-Any gap that test 178 pinned.
Stdlib (`library/modules/std.sx`):
- New `optional_to_string :: (o: $T) -> string` returns `"null"`
when the optional is None, otherwise recurses through
`any_to_string` on the unwrapped inner value. Per-shape
monomorphisation re-emits this for each concrete `?T`.
- `any_to_string` grows a `case optional:` arm that dispatches
through `cast(type) val` (same shape as `case struct:` etc.).
The cast picks up the dynamic optional type from the Any tag.
Compiler (`src/ir/lower.zig`):
- `resolveTypeCategoryTags` recognises "optional" as a dynamic
category, scanning the TypeTable for `info == .optional`. The
type-switch dispatch then routes any ?T tag into the optional
arm.
IR snapshots regenerated where the optional addition shifted
constant pool / string numbering: 142, ffi-objc-call-06,
ffi-objc-dsl-07. 218/218 (test 178 included).
The variadic auto-unwrap in `packVariadicCallArgs` stays in
place — direct `print(opt)` calls still flow through it. The new
arm closes the gap for struct fields, slice elements, and any
other path that boxes an optional before stringifying.
Declare `NSObject` in std/objc.sx as `#foreign #objc_class("NSObject")`
with the canonical instance + class-method surface every Obj-C class
inherits: `retain`/`release`/`autorelease`/`new`/`alloc`/`init`/
`description`/`hash`/`isEqual_`/`isKindOfClass_`/`respondsToSelector_`/
`class`. Root the foreign-class hierarchy in uikit.sx at NSObject by
adding `#extends NSObject;` to every previously-unrooted declaration
(NSValue, NSNumber, NSDictionary, NSSet, NSNotification, NSBundle,
NSNotificationCenter, NSRunLoop, CADisplayLink, CALayer, EAGLContext,
UIScreen, UIResponder) plus deeper chain fixes (NSMutableDictionary
extends NSDictionary; UIWindow extends UIView; UIViewController
extends UIResponder). After this, M2.3's extends-chain walk finds
`retain`/`release` on any UIKit-typed value:
view := UIView.alloc().init();
defer view.release(); // canonical sx idiom — no language magic
Plus `autoreleasepool(body: Closure())` stdlib helper that wraps
`body` in `objc_autoreleasePoolPush` / `defer objc_autoreleasePoolPop`.
Required for Foundation factory returns; closure-call frame is real
cost so hot loops should inline the push/defer-pop pattern manually.
Smoke test `ffi-objc-arc-01-autoreleasepool.sx` exercises both
patterns; refresh of two IR snapshots picks up the new stdlib decls
appearing in test outputs that include `modules/std/objc.sx`.
185/185 example tests pass; chess on iOS-sim green.
Three threads, one commit because they're entangled:
1. Helper free functions on `*UIKitPlatform` (refresh_safe_insets,
read_screen_scale, create_gl_context, setup_renderbuffer,
present_renderbuffer, compute_layer_pixel_size) → methods on the
`impl Platform for UIKitPlatform` block. IMP-shape trampolines
(`uikit_keyboard_will_change_frame`, `uikit_scene_will_connect[_ios]`,
`uikit_gl_view_tick/layout/touches_*`, `uikit_subscribe_keyboard_notifications`)
also collapse into methods on UIKitPlatform — the
`(self: *void, _cmd: *void, ...)` form is no longer needed since
M3 made the #objc_class trampolines compiler-synthesized. Class
method bodies in SxAppDelegate / SxSceneDelegate / SxGLView /
SxMetalView now read `if g_uikit_plat == null { return; }
g_uikit_plat.x();` — no more `xx self, xx 0` casts at every IMP
call site.
2. Declarative `layerClass` form. SxGLView and SxMetalView promote
from the M2.1(a) constant-with-runtime-string-lookup workaround
(`layerClass :: *void = objc_getClass("CAEAGLLayer".ptr);`) to
the class-method expression-body form
(`layerClass :: () => CAEAGLLayer.class();`). Type stays `*void`
until M1.1.b lands `Class(T)` parameterisation; the value side
already matches the plan. Backing this: foreign-class declarations
for CAEAGLLayer (extended with `class :: () -> *void;`) and a new
CAMetalLayer foreign-class declaration alongside it. Both
`#extends CALayer` so the dispatch chain knows about the parent.
3. Optional-shape idiom pass on uikit.sx. `xx`-as-optional-wrap on
field assignments (`plat.gl_ctx = xx ctx`, `plat.text_field = xx tf`,
`plat.display_link = xx link`) dropped — implicit `T → ?T` does
the right thing. `!` force-unwraps replaced with `if val := opt
{ ... }` safe-narrowing (the keyboard handler, the GL-context
read in setup/present renderbuffer, the gl_view read in scene
bootstrap). `orelse` (Zig keyword) that briefly snuck into the
keyboard handler removed in favour of the `if win := plat.window`
narrowing pattern. Result: no `xx` casts left on the implicit
T→?T path; all optional access goes through `if val :=`.
IR snapshots `ffi-objc-call-06-sret-return.ir` and
`ffi-objc-dsl-07-mangling-table.ir` refresh to pick up the new
`object_getIvar` / `object_setIvar` runtime-helper declarations
introduced when M1.2 A.3 made instance-method bodies route field
access through the state ivar.
Chess on iOS-sim green throughout. 184/184 example tests pass.
`examples/ffi-objc-dsl-07-mangling-table.sx` exercises every common
mangling shape in one fixture and pins the resolved selectors via
both `.txt` and `.ir` snapshots:
| sx method | derived selector |
|-----------------------------------|----------------------------|
| `length` | `length` |
| `addObject(o)` | `addObject:` |
| `combine_and(a, b)` | `combine:and:` |
| `insert_after_index(a, b, c)` | `insert:after:index:` |
| `add_observer_for_event(a, b, c, d)` | `add:observer:for:event:` |
| `initWithFrame_options(f, o)` | `initWithFrame:options:` |
| `custom_name #selector("actualSelectorName")` | `actualSelectorName` |
The class is synthesized at runtime via `objc_allocateClassPair` +
`class_addMethod` per selector (mirrors the pattern in
`ffi-objc-dsl-{01..05}.sx`), so the test actually dispatches through
the real Obj-C runtime on macOS.
Single commit because the implementation already shipped in 3.0/3.2;
this is a new regression that locks in current behavior, not a
test-then-make-green pair.
The `.ir` snapshot opts in via the existing run_examples.sh mechanism
(presence of a `.ir` file for the same name triggers capture). The
captured `OBJC_METH_VAR_NAME_*` constants surface every selector
string change at a glance.
166/166 tests.