ffi 3.2 C4: migrate uikit.sx UIKit chrome cluster to #objc_class

Fourth cluster — was blocked on issue-0043, now unblocked by the
preceding `Self`-substitution fix.

Classes declared:

- UIScreen         → mainScreen (class), nativeScale + bounds (instance)
- UIView           → safeAreaInsets, addSubview, layer (all instance)
- UIWindow         → alloc (class), initWithWindowScene, setRootViewController,
                     makeKeyAndVisible, screen (instance)
- UIViewController → alloc (class), init, setView (instance)
- UITextField      → alloc (class), init, becomeFirstResponder,
                     resignFirstResponder (instance)

Migration sites in uikit.sx:

- `show_keyboard` / `hide_keyboard` → `tf.becomeFirstResponder()` /
  `tf.resignFirstResponder()` on a `*UITextField` cast of `text_field`.
- `uikit_refresh_safe_insets` → `gl_view.safeAreaInsets()` on a
  `*UIView` cast of `plat.gl_view`.
- `uikit_read_screen_scale` and GL-context bring-up →
  `UIScreen.mainScreen().nativeScale()`.
- `uikit_keyboard_will_change_frame` → `win.screen().bounds()`.
- `uikit_scene_will_connect_ios` (the function that triggered 0043) →
  `UIWindow.alloc().initWithWindowScene(scene)`,
  `UIViewController.alloc().init()`, `vc.setView(...)`,
  `win.setRootViewController(...)`, `gl_view.layer()`,
  `UITextField.alloc().init()`, `gl_view.addSubview(...)`,
  `win.makeKeyAndVisible()`.

Three `objc_getClass(...)` lookups (UIWindow, UIViewController,
UITextField) are gone — the class slots come from the declarative
bindings via `__sx_objc_class_init`. UIScreen has the same shape.

167/167 example tests; chess clean on macOS / iOS sim / Android via
`tools/verify-step.sh`.
This commit is contained in:
agra
2026-05-25 17:53:11 +03:00
parent 2b717d9b38
commit 5b4969f9be
2 changed files with 140 additions and 55 deletions

View File

@@ -568,32 +568,59 @@ blocks. The `link` parameter on the `sxTick:` callback is now cast
to `*CADisplayLink` at function entry so subsequent method calls
type-check.
**Phase 3.2 C4/C5 BLOCKED on issue-0043.** Attempted C4 migration
(UIKit chrome: UIScreen / UIWindow / UIViewController / UITextField
/ UIView) surfaced a real compiler bug: lazy-lowered function bodies
don't resolve foreign-class method dispatch when invoked transitively
from an `inline if OS == .ios` branch in another function. The
specific failure is in `uikit_scene_will_connect_ios` whose body
contains `UIWindow.alloc().initWithWindowScene(scene)` and
`win.setRootViewController(...)` — both work in isolated probes but
fail at compile time when the function is reached via lazy lowering
from chess's iOS scene-connect hook. macOS target builds fine; only
ios-sim trips it. C1/C2/C3 happened to land cleanly because the
methods they migrate are reached eagerly (or are niladic so the
dispatch path doesn't hit the failing branch).
**issue-0043 closed.** The "lazy-lower" framing in the issue file
turned out to be a red herring: the actual root cause was that
`inferExprType` for a chained call `Cls.static().instance(...)` never
looked the inner call's foreign-class declaration up, so the outer
dispatch saw a `.s64` receiver, the `foreign_class_map.get(...)` lookup
missed, and lowering emitted `error: unresolved 'method'`. The macOS
target appeared to work because `inline if OS == .ios { ... }` strips
the gated body before lowering — eliding every call that would have
exercised the broken path.
The C4 work is reverted to keep the tree green at C3. C4+C5 stay
pending until issue-0043 is fixed in a separate session.
Fix in `src/ir/lower.zig`:
1. `inferExprType` for `.call` with `.field_access` callee now checks
`foreign_class_map` for both shapes — `Cls.static_method(args)` (object
identifier matches a foreign-class alias, look up static members) and
`inst.instance_method(args)` (receiver is a pointer to a foreign-class
struct, look up non-static members).
2. New helpers `resolveForeignMethodReturnType` /
`resolveForeignClassMemberType` substitute `*Self` / `Self` to the
foreign-class struct so a `*Self` return doesn't synthesize a phantom
`Self`-named struct that future dispatches can't resolve.
3. The Obj-C lowering paths (`lowerObjcMethodCall`, `lowerObjcStaticCall`)
route through the same helper for `ret_ty` so the IR Ref's type matches
what `inferExprType` reports.
`examples/138-foreign-class-chained-dispatch.sx` locks in the regression
via two shapes against NSObject's `+alloc` / `-init` chain: `*NSObject`
return then `*Self` return, and `*Self` then `*Self`. Runs on the host
(macOS) for live exercise; non-macOS hosts fall through to a stub
matching the expected output.
Phase 3.2 C4 landed: UIKit chrome cluster migrated. Six classes
declared (UIScreen, UIView, UIWindow, UIViewController, UITextField
— plus the existing C1/C2/C3 classes already in place). Migration
sites: `show_keyboard` / `hide_keyboard` use `tf.becomeFirstResponder()`
/ `tf.resignFirstResponder()`; `uikit_refresh_safe_insets` uses
`gl_view.safeAreaInsets()`; `uikit_read_screen_scale` and the GL-
context bring-up both use `UIScreen.mainScreen().nativeScale()`;
keyboard-frame callback uses `win.screen().bounds()`; the scene-
connect bring-up chains `UIWindow.alloc().initWithWindowScene(scene)`
and `UIViewController.alloc().init()` then `vc.setView(...)`,
`win.setRootViewController(...)`, `gl_view.layer()`,
`UITextField.alloc().init()`, `gl_view.addSubview(...)`,
`win.makeKeyAndVisible()`. Three `objc_getClass(...)` calls (UIWindow,
UIViewController, UITextField) are gone — the class slots come from
the declarative bindings via `__sx_objc_class_init`. C4 is the
cluster that triggered issue-0043; with the fix in, the chained
dispatch resolves correctly under lazy lowering. 167/167 tests +
chess clean on macOS / iOS sim / Android.
Open work:
- **issue-0043** — investigate + fix the lazy-lower foreign-class
dispatch bug. See `issues/0043-lazy-lower-loses-foreign-class-method-dispatch.md`
for the reproduction and investigation prompt.
- **Phase 3 step 3.2 — C4..C5** — uikit.sx migration, blocked until
0043 lands.
test for the default-mangling table. Escape hatch for selectors
that don't fit the underscore-split rule (e.g. `tableView_
numberOfRowsInSection_` with an asymmetric keyword count).
- **Phase 3 step 3.2 — C5** — uikit.sx migration (view tree + GL
drawables: CAEAGLLayer, EAGLContext, plus any remaining
CAMetalLayer / NSTimer sites).
- **Phase 3 step 3.3** — `property name: Type` synthesizes
`inst.name``[inst name]` getter and `inst.name = x`
`[inst setName: x]` setter. `#setter("...")` overrides the setter
@@ -989,6 +1016,27 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
`Gles3Gpu`. Instrumentation stripped after fix. 140 host + 9
cross tests green.
- 2026-05-25: issue-0043 closed — chained `Cls.static().instance(...)`
foreign-class dispatch. `inferExprType` for `.call` with `.field_access`
callee now consults `foreign_class_map` for both static (object is the
alias) and instance (receiver type is `*ForeignClass`) shapes. New
`resolveForeignMethodReturnType` / `resolveForeignClassMemberType` /
`foreignClassStructType` helpers substitute `*Self` / `Self` to the
foreign class's own struct so the chained receiver type doesn't
collapse to a phantom `Self`-named struct. `lowerObjcMethodCall` /
`lowerObjcStaticCall` route through the same helper so the IR Ref's
recorded ret_ty matches what `inferExprType` reports. Pre-fix:
`UIWindow.alloc().initWithWindowScene(scene)` (and any other chained
shape) collapsed the inner ret to `.s64`, the next dispatch's
`foreign_class_map.get(...)` missed, and lowering emitted
`error: unresolved 'initWithWindowScene'`. The "lazy-lower" wording in
the issue file is a red herring — the bug fires on direct calls too;
macOS chess hides it only because `inline if OS == .ios { ... }`
strips the gated bodies that exercise the chain. Locked in by
`examples/138-foreign-class-chained-dispatch.sx` (NSObject `+alloc` /
`-init` chain in both `*Cls` and `*Self` return-type shapes). 167
host + 7 cross tests green. Phase 3.2 C4/C5 is unblocked.
## Known issues
- `signed char` C maps to sx `u8` in c_import.zig (current behavior;