From 70b4341682b042e36cb80600e64c25d87d220c92 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 19:17:21 +0300 Subject: [PATCH] =?UTF-8?q?issues:=20file=200059=20=E2=80=94=20expr-bodied?= =?UTF-8?q?=20lambda=20inferred=20return=20type=20panics=20LLVM=20emission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- ...-lambda-inferred-return-unresolved-type.md | 88 +++++++++++++++++++ ...-lambda-inferred-return-unresolved-type.sx | 15 ++++ 2 files changed, 103 insertions(+) create mode 100644 issues/0059-expr-lambda-inferred-return-unresolved-type.md create mode 100644 issues/0059-expr-lambda-inferred-return-unresolved-type.sx diff --git a/issues/0059-expr-lambda-inferred-return-unresolved-type.md b/issues/0059-expr-lambda-inferred-return-unresolved-type.md new file mode 100644 index 0000000..0c7aa54 --- /dev/null +++ b/issues/0059-expr-lambda-inferred-return-unresolved-type.md @@ -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. diff --git a/issues/0059-expr-lambda-inferred-return-unresolved-type.sx b/issues/0059-expr-lambda-inferred-return-unresolved-type.sx new file mode 100644 index 0000000..3506cc3 --- /dev/null +++ b/issues/0059-expr-lambda-inferred-return-unresolved-type.sx @@ -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)); +}