Triage pass: every issue file in `issues/` was re-verified against HEAD. Three (0041, 0042, 0043) reproduce no longer — they were silently fixed by adjacent work since the issue was filed. 0047 landed in the previous commit. All four header sections now lead with **FIXED** + a one-line locator so the next reader doesn't re-investigate. After this, `issues/` is the actual open-issue list: | Issue | Status | |---|---| | 0041 | FIXED (silently, by alias/parser work) | | 0042 | FIXED (silently, type_alias_map lookup landed) | | 0043 | FIXED (silently, lazy-lower foreign-class dispatch) | | 0044 | FIXED | | 0045 | FIXED | | 0046 | FIXED | | 0047 | FIXED (commit0119c9c) | | 0048 | FIXED (commit0ede097) | | 0049 | FIXED (commitb5301c4) | | 0050 | FIXED (commit5316bf7) | No open issues remain. The files stay in tree as a record; new issues take the next free number (0051).
5.8 KiB
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 <file> 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
#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:
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 and the field-
access method dispatch at
src/ir/lower.zig:5290 (around the
foreign_class_map.get(sname_for_foreign) lookup).
Hypotheses:
lazyLowerFunctionswaps some piece of lowering state on entry (e.g.,saved_source_file,current_ctx_ref) but doesn't preserve access toforeign_class_map. Check whether the map is instance-state vs. shared.- The receiver type for
win(*UIWindow) isn't being resolved to itsgetStructTypeNamecorrectly during lazy lowering — possiblyinferExprTypefor the lazy-lowered context resolves to an anonymous type instead ofUIWindow. - The foreign-class declarations are added to
foreign_class_mapduring a pre-scan pass that runs BEFORE the outer functionA's body is lowered, but lazy lowering ofBfrom withinAmight 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_mapcontains"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.getbranch 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.