File four issue write-ups discovered alongside the 0179 work: - 0185: binary-op operand auto-unwrap silently miscompiles a NULL ?T - 0186: closure VALUE call does not coerce arg to ?T parameter - 0187: lambda with inferred return type + block body with early returns mis-infers its return type - 0188: closure-VALUE calls skip argument validation (arity + tuple spread)
56 lines
2.6 KiB
Markdown
56 lines
2.6 KiB
Markdown
# 0187 — lambda with INFERRED return type + block body with early `return`s 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
|
|
|
|
```sx
|
|
#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 `return`s (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:
|
|
1. The repro prints `f: 11`.
|
|
2. `examples/optionals/0919`/`0921` and `examples/closures/0312` still pass
|
|
(0312 deliberately uses explicit `-> i64` to dodge this bug — once fixed, an
|
|
inferred-return variant should also work).
|
|
3. 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.
|