A `:=`-bound closure with no explicit `-> T` and a BLOCK body inferred its
return type via inferExprType(lam.body), which yields the last statement's
type. A block whose value comes only from early `return`s ends in a return
statement (void/noreturn), so the closure was built with a void return while
the body returned i64 — the call site then fed `i64 undef` and LLVM
verification failed. (A block whose tail referenced a block-local hit the
sibling failure: inferExprType returned .unresolved → an LLVM panic.)
Infer the return type exactly as a named fn does (resolveReturnType in
lower.zig): an arrow body `(params) => expr` uses the expression type; a block
body `(params) { stmts }` takes the first explicit `return <val>` type via
findReturnValueType, else void (the block tail is a discarded statement unless
an explicit `-> R` makes it the value). Regression test:
examples/closures/0313-closure-inferred-return-early.sx.
4.3 KiB
RESOLVED. Root cause: the lambda return-type inference in
lowerLambda(src/ir/lower/closure.zig) always usedinferExprType(lam.body), which for a block body returns the last statement's value type — and a block whose value comes only from earlyreturns ends in areturnstatement (typed void/noreturn), so the closure was built with a void return while the body returnedi64. Fix: distinguish the two body forms exactly as a named fn does (resolveReturnTypeinsrc/ir/lower.zig) —
- arrow
(params) => expr→inferExprType(expr)(the expression IS the value);- block
(params) { stmts }→ first explicitreturn <val>type viafindReturnValueType, else void (the block's tail is a discarded statement, not an implicit return — only an explicit-> Rmakes the tail the value).This also subsumes the block-tail-references-a-local case (a
closure(() { x := 5; x * 2 })with no-> Ris now correctly void, not an.unresolvedtype reaching LLVM and panicking). Regression test:examples/closures/0313-closure-inferred-return-early.sx.Syntax note: the original repro above used the malformed
() => { ... }(arrow + block) form, which the parser currently accepts but the spec does not define — a block body is theclosure((params) -> R? { ... })form, and the=>lambda takes an EXPRESSION (specs.md §Lambda / §Closures). The bug is real under the valid form too:closure(() { if c { return 11; } return 22; })with an inferred return failed identically before the fix. The regression test uses the validclosure(...)syntax. (The parser accepting() => { block }at all is a separate leniency gap, not tracked here.)
0187 — lambda with INFERRED return type + block body with early returns mis-infers its return type (LLVM verifier failure)
Symptom
A :=-bound lambda (closure literal) that has NO explicit -> T return type
and whose body is a BLOCK containing return statements infers the WRONG
return type (apparently void). Calling it and using the value fails LLVM
verification (Call parameter type does not match function signature! ... i64 undef / Function arguments must have first-class types!). Adding an explicit
-> T makes it work. No optionals or flow narrowing are involved — found while
verifying issue 0186.
Reproduction
#import "modules/std.sx";
main :: () {
// inferred return type, block body with early returns — NO optionals
f := () => { if 1 > 0 { return 11; } return 22; };
print("f: {}\n", f()); // LLVM verification failed (return type inferred void/undef)
}
Workaround / contrast (works): annotate the return type —
f := () -> i64 => { if 1 > 0 { return 11; } return 22; };
Root cause (hypothesis)
The lambda return-type inference in lowerLambda (src/ir/lower/closure.zig,
the ret_ty computation around line 164: const inferred = self.inferExprType(lam.body);) does not infer the type from the body's
return statements when the body is a block. For a block whose value is
produced only via early returns (not a trailing tail expression),
inferExprType likely yields .void, so the lambda is built with a void
return while the body actually returns i64 — the mismatch surfaces at the
call site / LLVM verifier.
Investigation prompt
In src/ir/lower/closure.zig, the lambda return-type inference path
(inferExprType(lam.body) ~line 164) must, for a block body, infer the return
type from the body's return statement operands (matching how
lowerValueBody / the function-decl return inference handles bodies with early
returns), not just the block's tail-expression value. Reuse the existing
return-type inference the top-level fn path uses (a top-level
f :: () { if c { return 11; } return 22; } with inferred return works — see
why, and apply the same to lambdas). Verify:
- The repro prints
f: 11. examples/optionals/0919/0921andexamples/closures/0312still pass (0312 deliberately uses explicit-> i64to dodge this bug — once fixed, an inferred-return variant should also work).- Add a regression
examples/closures/03xx-lambda-inferred-return-early.sx.
Unrelated to the optional-unwrap family (0179/0185) and the closure-arg coercion fix (0186); purely lambda return-type inference.