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).
2.1 KiB
0172 — ?? with a non-optional left-hand side panics instead of diagnosing
Symptom
Using ?? where the left operand is NOT an optional panics the compiler:
panic: unresolved type reached LLVM emission (in emitStructInit for a struct
default, or generally), exit 134. ?? is defined to operate on an optional lhs;
a non-optional lhs is malformed user input that must be a clean type error, not a
crash. Pre-existing (reproduces independent of the issue 0166 fix).
Reproduction
#import "modules/std.sx";
T :: struct { a: i64 = 0; }
main :: () {
x := 5 ?? .{ a = 1 }; // panic: unresolved type reached LLVM emission, exit 134
}
Also panics: 5 ?? 7 (scalar default), some_non_optional_struct ?? .{ a = 1 },
and nested mk() ?? (5 ?? .{ a = 3 }). Expected: a located diagnostic like
error: left operand of '??' must be an optional, but has type 'i64', exit 1.
Investigation prompt
src/ir/lower/expr.zig lowerNullCoalesce: resolveOptionalInner (~expr.zig:1900)
returns .unresolved when nc.lhs is not optional, and the function proceeds to
feed that .unresolved into the merge-block params, optionalUnwrap, and the
RHS target type — which then reaches codegen and panics. Add a guard: after
inferring the lhs type, if it is not an optional (or resolveOptionalInner
yields .unresolved for a resolved-but-non-optional lhs), emit
self.diagnostics.addFmt(.err, nc.lhs.span, "left operand of '??' must be an optional, but has type '{s}'", .{ formatTypeName(lhs_ty) }) and bail (return a
placeholder), mirroring the non-pointer .* deref diagnostic at
lowerDerefExpr (~expr.zig:1839). Be careful to still allow the legitimate
cases: optional lhs (incl. a?.b chains returning optional), and make sure an
already-.unresolved lhs from a PRIOR error (undefined name) doesn't
double-report (that path already diagnoses via name resolution).
Verify: 5 ?? .{a=1}, 5 ?? 7, non-optional-struct ?? ... all exit 1 with the
diagnostic and no panic; existing optional ?? cases still work. Add an
examples/diagnostics/11xx-null-coalesce-non-optional.sx negative regression.