# issue-0043: lazy-lowered function bodies don't resolve foreign-class method dispatch **FIXED.** The original repro (`sx build --target ios-sim issue-0043.sx` with a `UIWindow.alloc().initWithWindowScene(scene)` call inside an `inline if OS == .ios { ... }`-gated function called transitively from `caller :: (...) callconv(.c)`) compiles cleanly today; chess on iOS-sim runs end-to-end through the same dispatch shape. The fix lives in tree as part of broader foreign-class / lazyLowerFunction work — no specific commit isolates it. Below preserved as a record of the original problem. ## Symptom When a function `B` containing `recv.method(...)` calls against a `#objc_class` receiver is invoked transitively via `lazyLowerFunction` from another `inline if OS == ...` branch in function `A`, the method dispatch fails with: ``` unresolved 'methodName' (in fn B) ``` Direct (eager) lowering of `B` with the same body works. Direct call from `main` works. Only the transitive lazy-lower path from inside an `inline if` branch fails. Concretely: under Phase 3.0 / Phase 3.2 C4 work, the iOS-sim build of chess fails: ``` library/modules/platform/uikit.sx:591:12: error: unresolved 'initWithWindowScene' (in ... fn uikit_scene_will_connect_ios) library/modules/platform/uikit.sx:599:11: error: unresolved 'init' library/modules/platform/uikit.sx:609:5: error: unresolved 'setView' library/modules/platform/uikit.sx:611:5: error: unresolved 'setRootViewController' library/modules/platform/uikit.sx:661:11: error: unresolved 'init' ``` Stack trace (with `SX_TRACE_UNRESOLVED=1`) shows the chain: ``` emitError (lower.zig:5640) ↑ lowerCall .field_access fallback lowerVarDecl (lowering body of uikit_scene_will_connect_ios) lowerBlock lazyLowerFunction (lower.zig:5165) (← lazy entry point) lowerCall (lower.zig:5165) (calling uikit_scene_will_connect_ios) lowerInlineBranch (inside `inline if OS == .ios { uikit_scene_will_connect_ios(...) }`) lowerIfExpr lowerExpr ``` So when the *outer* `lowerCall` decides to lazy-lower `uikit_scene_will_connect_ios`, the *inner* body's method-dispatch on `*UIWindow` etc. fails to find the foreign-class declaration — even though the declaration is at module scope at the top of uikit.sx and resolves fine for non-lazy lowering paths (`sx build` for macOS target compiles the same source cleanly). ## Reproduction ```sx #import "modules/std.sx"; #import "modules/compiler.sx"; UIWindow :: #foreign #objc_class("UIWindow") { alloc :: () -> *UIWindow; initWithWindowScene :: (self: *Self, scene: *void) -> *UIWindow; } // Function B: uses foreign-class method dispatch. do_work :: (scene: *void) { win := UIWindow.alloc().initWithWindowScene(scene); _ = win; } // Function A: calls B inside an `inline if`. The transitive call // triggers lazy lowering of B, which fails for ios-sim only. caller :: (self: *void, _cmd: *void, scene: *void, b: *void, c: *void) callconv(.c) { inline if OS == .ios { do_work(scene); } } main :: () -> s32 { 0; } ``` Build: ```sh sx build --target ios-sim issue-0043.sx ``` Observed: `error: unresolved 'initWithWindowScene' (in issue-0043.sx fn do_work)`. Eager dispatch shape (called directly from `main` without the intermediate `inline if`-gated function) compiles cleanly. The isolated probe must mirror the lazy-lower trigger to reproduce. ## Investigation prompt (for a fresh session) Suspected area: `lazyLowerFunction` at [src/ir/lower.zig:1057](../src/ir/lower.zig#L1057) and the field- access method dispatch at [src/ir/lower.zig:5290](../src/ir/lower.zig#L5290) (around the `foreign_class_map.get(sname_for_foreign)` lookup). Hypotheses: 1. `lazyLowerFunction` swaps some piece of lowering state on entry (e.g., `saved_source_file`, `current_ctx_ref`) but doesn't preserve access to `foreign_class_map`. Check whether the map is instance-state vs. shared. 2. The receiver type for `win` (`*UIWindow`) isn't being resolved to its `getStructTypeName` correctly during lazy lowering — possibly `inferExprType` for the lazy-lowered context resolves to an anonymous type instead of `UIWindow`. 3. The foreign-class declarations are added to `foreign_class_map` during a pre-scan pass that runs BEFORE the outer function `A`'s body is lowered, but lazy lowering of `B` from within `A` might be observing the map at a pre-scan state where uikit.sx's declarations haven't been seen yet (cross-module ordering). What the fix likely needs to do: - Confirm `foreign_class_map` contains `"UIWindow"` etc. at the point of the lazy lowering call (add a debug print at the failing dispatch). - If the map IS populated, trace why the field-access dispatch falls through to line 5640 instead of taking the `foreign_class_map.get` branch at line 5306. - If the map is NOT populated, find the seeding path and fix the ordering — likely a missing pre-scan step before lazy lowering. Verification: rebuild chess with `sx build --target ios-sim` against the in-progress Phase 3.2 C4 branch (revert the `git checkout -- library/modules/platform/uikit.sx` to restore the work-in-progress migration) and confirm the unresolved errors go away. ## Why this blocks Phase 3.2 C4 The migration of `library/modules/platform/uikit.sx` to declarative `#objc_class` dispatch (Phase 3.2 plan part C, clusters C4 + C5) necessarily places foreign-class method calls inside iOS-only helper functions that get lazily lowered when chess's iOS scene-lifecycle hooks fire them. Without this fix, the migration produces compile errors on iOS-sim that don't appear on macOS. C1+C2+C3 happened to land cleanly because the methods they migrate are either (a) called from eagerly-lowered top-level paths, or (b) niladic enough that the specific dispatch path doesn't fail. The Phase 3.2 plan flagged C4+C5 as blocked pending this fix.