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).
This commit is contained in:
agra
2026-06-22 22:17:01 +03:00
parent 0bc8005b99
commit 2ea25e84ec
7 changed files with 98 additions and 0 deletions

View File

@@ -1871,7 +1871,16 @@ pub fn lowerNullCoalesce(self: *Lowering, nc: *const ast.NullCoalesce) Ref {
// RHS block: evaluate fallback and branch to merge
self.builder.switchToBlock(rhs_bb);
// Thread the optional's child type as the expected/target type so an
// untyped struct literal default (`?? .{ ... }`) resolves to `T` rather
// than staying `.unresolved` and reaching codegen as a malformed
// struct_init (issue 0166). Scalar/pointer/typed defaults are unaffected:
// they ignore `target_type` or coerce identically. Restore afterwards so
// a `??` nested inside a larger expression doesn't leak this target type.
const saved_tt = self.target_type;
self.target_type = inner_ty;
var rhs = self.lowerExpr(nc.rhs);
self.target_type = saved_tt;
const rhs_ty = self.builder.getRefType(rhs);
if (rhs_ty != inner_ty and rhs_ty != .void and inner_ty != .void) {
rhs = self.coerceToType(rhs, rhs_ty, inner_ty);