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).
154 lines
5.8 KiB
Markdown
154 lines
5.8 KiB
Markdown
# 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
|
|
|
|
```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.
|