fix(lower): null-FuncId path restores full caller state [0100 F2]

lazyLowerFunction's three exit paths (non-null branch, already-promoted
early return, null-FuncId `ns.fn` qualified-alias branch) each duplicated
the caller-state restore, and the null branch's copy had drifted: it
restored every saved field EXCEPT `block_terminated`. A qualified alias
whose body terminates (e.g. a constant-folded `if true { return ... }`)
leaves `block_terminated = true` after lowerFunction; the null path
returned without resetting it, so the flag leaked into the CALLER's body
lowering and the caller's own trailing statements / `return` were rejected
as dead-after-terminator ("function ... body produces no value").

Fix: collapse the three restores into a single `defer` registered right
after the state is saved, so every exit path restores the identical full
set and the class cannot diverge again. Fields restored on all paths:
current_source_file (F1), scope, func_defer_base, block_terminated (F2),
force_block_value, builder.func/current_block/inst_counter. The
foreign-class / jni-env / pack-mono / inline-return fields already had
their own defers and are unchanged.

Regression: examples/0721-modules-qualified-terminating-callee.sx — a
qualified alias `m.foo` folds `if true { return helper(); }` (helper from
m.sx's own import) and is followed by caller statements + the caller's own
`return 0`. Reports "body produces no value" pre-fix; prints
"terminating-callee: ok" / "after" and exits 0 after. 0719 (collision) and
0720 (F1 own-import visibility) stay green. issues/0100 RESOLVED banner
extended with the F2 follow-up.
This commit is contained in:
agra
2026-06-06 03:15:29 +03:00
parent 9274d47adf
commit d11f4c84b6
8 changed files with 125 additions and 31 deletions

View File

@@ -69,6 +69,53 @@ Regression: `examples/0720-modules-qualified-own-import.sx` — `calc.compute`
Reports `'triple' is not visible` on the attempt-1 code; passes after. 0719's
cross-module dual-`parse` assertion stays green.
## F2 follow-up — null-FuncId path must restore the FULL caller lowering state
The F1 fix patched the **source file** in `lazyLowerFunction`'s null-FuncId
branch, but that branch still restored only a SUBSET of the caller state the
non-null branch restores — it omitted `self.block_terminated`. A qualified
alias whose body terminates (e.g. a constant-folded `if true { return … }`)
leaves `block_terminated = true` after `lowerFunction`; the null branch then
returned without resetting it, so the flag leaked into the **caller's** body
lowering and the caller's own trailing statements / `return` were treated as
dead-after-terminator:
```
m :: #import "m.sx"; // m.sx: `#import "helper.sx"; foo :: () -> s64 { if true { return helper(); } return 0; }`
main :: () -> s32 {
x := m.foo();
print("after\n"); // dropped
return 0; // → error: body produces no value
}
```
**Fix** (`src/ir/lower.zig`): the three exit paths of `lazyLowerFunction` (the
null-FuncId branch, the already-promoted early return, and the bottom of the
non-null branch) duplicated the restore, and the null branch's copy drifted.
They are now collapsed into a **single `defer`** registered right after the
state is saved, so every exit path restores the identical full set and the
class can't diverge again. The fields the defer now restores on all paths:
- `current_source_file` (via `setCurrentSourceFile`, which also resyncs
`diagnostics.current_source_file`) — F1
- `scope`
- `func_defer_base`
- `block_terminated`**F2** (was missing on the null path)
- `force_block_value`
- `builder.func`
- `builder.current_block`
- `builder.inst_counter`
(The `current_foreign_class`, `jni_env_stack_base`, and pack-mono /
`inline_return_target` fields already had their own `defer`s and apply on all
paths; they are unchanged.)
Regression: `examples/0721-modules-qualified-terminating-callee.sx``m.foo`
(a qualified alias) folds `if true { return helper(); }` and is followed by
caller statements + the caller's own `return 0`. Reports `body produces no
value` on the attempt-2 code; prints `terminating-callee: ok` / `after` and
exits 0 after. 0719 and 0720 stay green.
## Symptom
- **Observed:** a program that imports two modules each exporting a
@@ -117,11 +164,15 @@ machinery already existed but was never fed module-qualified entries.
## Fix verification
- `zig build` → 0
- `zig build test` → 0 (incl. LSP corpus sweep, 472 examples)
- `bash tests/run_examples.sh` → 455 passed, 0 failed
- `zig build test` → 0 (incl. LSP corpus sweep, 473 examples; 397/397 tests)
- `bash tests/run_examples.sh` → 456 passed, 0 failed
- `examples/0719-modules-cli-and-json.sx`: panics pre-fix, passes post-fix.
- `examples/0720-modules-qualified-own-import.sx`: `'… is not visible'` on
the attempt-1 code, passes after the F1 fix.
- `examples/0721-modules-qualified-terminating-callee.sx`: `body produces no
value` on the attempt-2 code, passes after the F2 fix.
Regression tests: `examples/0719-modules-cli-and-json.sx` (collision),
`examples/0720-modules-qualified-own-import.sx` (F1 own-import visibility).
`examples/0720-modules-qualified-own-import.sx` (F1 own-import visibility),
`examples/0721-modules-qualified-terminating-callee.sx` (F2 terminating
qualified callee — caller state transparency).