# 0059 — expression-bodied lambda with inferred return type reaches LLVM emission unresolved

> **✅ RESOLVED.** Root cause: `resolveReturnType` ([src/ir/lower.zig]) infers a
> no-annotation function's return type from its body, but the body references the
> function's own params — which weren't in `self.scope` yet (they're bound later,
> at body lowering). So `inferExprType` couldn't resolve `x` in `(x: s32) => x * 2`
> and returned `.unresolved`, which reached LLVM emission. It only slipped through
> when a same-named binding happened to linger in scope from earlier lowering.
> Fix: bind the function's plain annotated value params into a temporary scope
> during return-type inference (resolving types directly via
> `resolveTypeWithBindings`, not `resolveParamType`, whose variadic/pack
> bookkeeping must run exactly once at body lowering). Covers both the arrow (`=>`)
> and inferred-via-`return` forms. Regression test:
> `examples/0308-closures-arrow-inferred-return.sx`.

## 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.
