issues: file 0059 — expr-bodied lambda inferred return type panics LLVM emission

An expression-bodied lambda `f :: (x: s32) => x * 2;` without an explicit
return type reaches emit_llvm declareFunction with func.ret = .unresolved and
trips the emission guard panic. Explicit `-> s32` works; the same lambda inside
a large module (the old 50-smoke.sx) resolves fine — so inferred-return
resolution for => lambdas runs only conditionally. Repro co-located.

Surfaced while splitting 50-smoke.sx (test-layout migration); the functions
section uses this exact construct, so the split is paused per the impassable
rule (no workaround).
This commit is contained in:
agra
2026-06-01 19:17:21 +03:00
parent 4e942b5373
commit 70b4341682
2 changed files with 103 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# 0059 — expression-bodied lambda with inferred return type reaches LLVM emission unresolved
## Symptom
An expression-bodied lambda (`name :: (params) => expr;`) **without** an explicit
return-type annotation aborts the compiler with:
```
thread … panic: unresolved type reached LLVM emission — a type resolution failure was not diagnosed/aborted
src/ir/emit_llvm.zig:4594 toLLVMTypeInfo (.unresolved => @panic(...))
src/ir/emit_llvm.zig:4457 toLLVMType
src/ir/emit_llvm.zig:1658 declareFunction ← const raw_ret_ty = self.toLLVMType(func.ret);
src/ir/emit_llvm.zig:322 emit
```
- **Observed:** the lambda's `func.ret` is `.unresolved` when `declareFunction`
runs, so emission panics (SIGABRT, exit 134).
- **Expected:** the inferred return type (`s32` here) is resolved before
emission; the program prints `14` and exits 0.
## Reproduction
`issues/0059-expr-lambda-inferred-return-unresolved-type.sx` (standalone):
```sx
#import "modules/std.sx";
f :: (x: s32) => x * 2; // inferred return type
main :: () {
print("{}\n", f(7)); // want: 14
}
```
`./zig-out/bin/sx run issues/0059-expr-lambda-inferred-return-unresolved-type.sx`
→ panic above.
### Key contrasts (narrowing clues)
- **Explicit return type works:** `f :: (x: s32) -> s32 => x * 2;` → prints `14`,
exit 0.
- **The same lambda inside a large program works:** the old monolithic
`50-smoke.sx` contained `double :: (x: s32) => x * 2;` as a local const and ran
clean (exit 0). The panic only appears when the expr-bodied inferred-return
lambda is compiled in a *small* module (minimal file, or as the first/only such
function). This strongly suggests the return-type inference for `=>` lambdas is
triggered as a side effect of some other pass that the large file happens to run
and the minimal file does not — rather than being driven unconditionally for
every expr-bodied lambda.
- Both the **top-level** form (`f :: (x: s32) => x * 2;`) and the **local-const**
form (inside `main`) panic identically.
## Investigation prompt (paste into a fresh session)
> An expression-bodied lambda `f :: (x: s32) => x * 2;` with an inferred return
> type panics at `src/ir/emit_llvm.zig:4594` ("unresolved type reached LLVM
> emission") because `func.ret` is still `.unresolved` when `declareFunction`
> (emit_llvm.zig:1658) emits it. Adding an explicit `-> s32` fixes it, and the
> same lambda compiled inside a large module (the old 50-smoke.sx) resolves fine
> — so the inferred-return resolution for `=>` lambdas is running only
> conditionally.
>
> Suspected area: the return-type inference for expression-bodied (`=>`) lambdas
> in `src/ir/lower.zig` (lambda lowering / `inferExprType` of the lambda body) or
> the sema lambda path — the inferred return type is computed lazily / on-demand
> and the demand isn't created for a lambda that is only *called* (not, e.g.,
> involved in whatever the big file does). Compare the block-bodied lambda path
> (`(x) { ... }`) and the explicit-`->`-return path, both of which resolve.
>
> Fix likely needs to: force the expr-bodied lambda's return type to be inferred
> and resolved during lowering of the lambda literal itself (so `func.ret` is
> concrete before emission), independent of call sites; OR, if a body genuinely
> can't be inferred, emit a real diagnostic instead of leaving `.unresolved` to
> trip the emission guard.
>
> Verification: `./zig-out/bin/sx run issues/0059-expr-lambda-inferred-return-unresolved-type.sx`
> should print `14` and exit 0. Then confirm the existing lambda/inference tests
> still pass: `0203-generics-infer-return-type`, `0300-closures-lambda`,
> `0021-basic-expression-bodied-fn`, plus `bash tests/run_examples.sh`.
## Impact on current work
Blocks the `50-smoke.sx` split (test-layout migration, Phase 2): the
**functions** section exercises exactly this construct
(`double :: (x: s32) => x * 2;`), so it cannot be extracted into a standalone
example until this is fixed. Working around it (adding an explicit return type)
would stop testing inferred-return lambdas and hide the bug, so per the project's
impassable rule the split is paused here.

View File

@@ -0,0 +1,15 @@
// Repro for issue 0059: an expression-bodied `=>` lambda with an INFERRED
// return type reaches LLVM emission with an unresolved return type and panics.
// Adding an explicit `-> s32` makes it work; so does burying the same lambda
// inside a large program (examples/.../50-smoke had it and ran fine).
//
// Expected: prints `14`, exit 0.
// Actual: panic "unresolved type reached LLVM emission" (SIGABRT, exit 134).
#import "modules/std.sx";
f :: (x: s32) => x * 2;
main :: () {
print("{}\n", f(7));
}