diff --git a/examples/0308-closures-arrow-inferred-return.sx b/examples/0308-closures-arrow-inferred-return.sx new file mode 100644 index 0000000..f5935a6 --- /dev/null +++ b/examples/0308-closures-arrow-inferred-return.sx @@ -0,0 +1,24 @@ +// Regression (issue 0059): a function with NO explicit return type infers it +// from the body, which references the function's own parameters. The inference +// must see those params — before the fix they weren't in scope during +// return-type resolution, so the inferred type came out `.unresolved` and tripped +// the LLVM-emission guard ("unresolved type reached LLVM emission"). Whether it +// slipped through used to depend on a same-named binding lingering from earlier +// lowering. Covers the arrow (`=>`) and inferred-via-`return` forms, at top level +// and as locals. + +#import "modules/std.sx"; + +dbl :: (x: s32) => x * 2; // top-level arrow, inferred return +inc :: (x: s32) { return x + 1; } // top-level block, inferred via `return` + +main :: () { + print("{}\n", dbl(7)); // 14 + print("{}\n", inc(41)); // 42 + + tripl :: (x: s32) => x * 3; // local arrow, inferred return + print("{}\n", tripl(4)); // 12 + + half :: (x: f32) => x / 2.0; // inferred float return + print("{}\n", half(9.0)); // 4.500000 +} diff --git a/examples/expected/0308-closures-arrow-inferred-return.exit b/examples/expected/0308-closures-arrow-inferred-return.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0308-closures-arrow-inferred-return.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0308-closures-arrow-inferred-return.stderr b/examples/expected/0308-closures-arrow-inferred-return.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0308-closures-arrow-inferred-return.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0308-closures-arrow-inferred-return.stdout b/examples/expected/0308-closures-arrow-inferred-return.stdout new file mode 100644 index 0000000..d123bda --- /dev/null +++ b/examples/expected/0308-closures-arrow-inferred-return.stdout @@ -0,0 +1,4 @@ +14 +42 +12 +4.500000 diff --git a/issues/0059-expr-lambda-inferred-return-unresolved-type.md b/issues/0059-expr-lambda-inferred-return-unresolved-type.md index 0c7aa54..21138dc 100644 --- a/issues/0059-expr-lambda-inferred-return-unresolved-type.md +++ b/issues/0059-expr-lambda-inferred-return-unresolved-type.md @@ -1,5 +1,18 @@ # 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 diff --git a/issues/0059-expr-lambda-inferred-return-unresolved-type.sx b/issues/0059-expr-lambda-inferred-return-unresolved-type.sx deleted file mode 100644 index 3506cc3..0000000 --- a/issues/0059-expr-lambda-inferred-return-unresolved-type.sx +++ /dev/null @@ -1,15 +0,0 @@ -// 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)); -} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index ff10617..1c5b5d5 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11617,13 +11617,38 @@ pub const Lowering = struct { if (fd.return_type) |rt| { return self.resolveTypeWithBindings(rt); } - // Arrow functions without explicit return type: infer from body expression + // No explicit annotation — the type is inferred from the body, which + // references the function's own parameters (`(x: s32) => x * 2`). Those + // params aren't pushed into `self.scope` until body lowering, so bind + // them into a temporary scope here; otherwise `inferExprType` can't + // resolve `x`, the inference yields `.unresolved`, and that reaches LLVM + // emission as `func.ret` (issue 0059). Whether it slipped through used to + // depend on a same-named binding lingering from earlier lowering. + var tmp_scope = Scope.init(self.alloc, self.scope); + defer tmp_scope.deinit(); + const saved_scope = self.scope; + self.scope = &tmp_scope; + defer self.scope = saved_scope; + for (fd.params, 0..) |p, i| { + // Bind only plain annotated value params — that's all the body's + // return type can depend on by name. Skip variadic / pack / comptime + // params (their concrete types come from per-call substitution) and + // unannotated ones (no context here). Resolve the type directly via + // resolveTypeWithBindings rather than resolveParamType: the latter + // does variadic/pack bookkeeping that must run exactly once, at body + // lowering — calling it here too corrupts that state. + if (p.is_variadic or p.is_pack or p.is_comptime) continue; + if (p.type_expr.data == .inferred_type) continue; + const pty = self.resolveTypeWithBindings(p.type_expr); + tmp_scope.put(p.name, .{ .ref = Ref.fromIndex(@intCast(i)), .ty = pty, .is_alloca = false }); + } + // Arrow functions without explicit return type: infer from body expression. if (fd.is_arrow) { return self.inferExprType(fd.body); } - // No annotation, not arrow: an explicit `return ` statement - // wins. Otherwise default to void — the body's tail expression is - // a side-effect statement, not an implicit return. + // Not arrow: an explicit `return ` statement wins. Otherwise + // default to void — the body's tail expression is a side-effect + // statement, not an implicit return. if (self.findReturnValueType(fd.body)) |ty| return ty; return .void; }