Files
sx/issues/0166-null-coalesce-struct-literal-default-unresolved.md
agra 2ea25e84ec fix: thread optional child type into ?? struct-literal default (issue 0166)
The RHS of a null-coalesce was lowered with no target type, so a bare
struct literal default (x ?? .{ ... }) produced a struct_init with
.ty == .unresolved that panicked in emitStructInit. lowerNullCoalesce
now saves self.target_type, sets it to the optional's resolved child
before lowering nc.rhs, and restores it (leak-free). Verified across
struct/slice/enum/tuple/protocol/nested-optional/generic child types by
3 adversarial reviews.

Regression: examples/optionals/0912-null-coalesce-struct-literal.sx.
Filed adjacent pre-existing bug 0172 (?? on a non-optional lhs panics).
2026-06-22 22:17:01 +03:00

54 lines
2.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0166 — `?? .{ ... }` struct-literal default panics with "unresolved type reached LLVM emission"
> **RESOLVED.** The `??` RHS struct literal was lowered with no target type, so
> its `struct_init.ty` stayed `.unresolved` and reached `emitStructInit`. Fix:
> in `src/ir/lower/expr.zig` `lowerNullCoalesce`, save `self.target_type`, set it
> to `inner_ty` (the optional's resolved child) before lowering `nc.rhs`, and
> restore afterward (unconditional, leak-free — `lowerExpr` returns a plain
> `Ref`). Verified across struct/slice/enum/tuple/protocol/nested-optional/
> generic child types and present/absent branches by 3 adversarial reviews.
> Regression: `examples/optionals/0912-null-coalesce-struct-literal.sx`.
> (Adjacent pre-existing bug found + filed: 0172 — `??` on a NON-optional lhs
> panics; `lowerNullCoalesce` must diagnose it.)
## Symptom
Using a struct literal as the default of a `??` (null-coalesce) operator panics:
```
panic: unresolved type reached LLVM emission
```
in `emitStructInit` (exit 134 / SIGABRT). The coalesce result type is inferred
correctly (the optional's child `T`), but that target type is NOT threaded into
the RHS struct-literal lowering, so the `struct_init` instruction's `.ty` stays
`.unresolved` and reaches codegen.
## Reproduction
```sx
#import "modules/std.sx";
T :: struct { a: i64 = 0; }
mk :: () -> ?T { return null; }
main :: () { t := mk() ?? .{ a = 9 }; print("{}\n", t.a); }
```
Expected: `9` (null lhs → take the struct-literal default, typed as `T`).
Observed: `panic: unresolved type reached LLVM emission`, exit 134.
## Investigation prompt
The result type IS inferred correctly — `src/ir/expr_typer.zig` (~lines 413425)
returns the optional's child `T` for the coalesce expression. The gap is in
lowering: the `??` RHS struct literal is lowered without a target type. Suspected
area: `src/ir/lower/expr.zig` `lowerNullCoalesce` (dispatched ~expr.zig:2417)
must set the lowering target type to the lhs optional's child (`T`) before
lowering `nc.rhs`, so an untyped `.{ ... }` literal on the RHS resolves to `T`
the same way an assignment/return target would. Mirror however other contexts
push an expected type into an untyped struct-literal lowering.
Verify: the repro prints `9`; also test a present lhs (`mk` returns a value →
prints the value's field, default not taken) and a nested-field struct literal
default. Add an `examples/optionals/09xx-null-coalesce-struct-literal.sx`
regression.