fix: diagnose ?? with a non-optional lhs instead of codegen panic (issue 0172)

lowerNullCoalesce fed resolveOptionalInner's .unresolved (returned for a
non-optional lhs) into the merge-block params / optionalUnwrap / RHS
target type, reaching codegen and panicking 'unresolved type reached
LLVM emission'. Guard: when inferExprType(nc.lhs) is a resolved
non-optional type, emit a located diagnostic and bail; an .unresolved
lhs (prior error) is excluded to avoid double-report. ?? is optional-only
per specs.md (error unions use or/catch), so rejecting a failable lhs is
correct; comptime panic closed too.

Regression: examples/diagnostics/1200-diagnostics-null-coalesce-non-optional.sx.
Verified by 3 adversarial reviews, suite 790/0. Filed adjacent bug 0180
(?? lowering defects for generic/alias/tuple optional lhs).
This commit is contained in:
agra
2026-06-23 03:31:58 +03:00
parent e5b682e622
commit 58f97fff10
7 changed files with 113 additions and 1 deletions

View File

@@ -1952,7 +1952,24 @@ pub fn lowerForceUnwrap(self: *Lowering, fu: *const ast.ForceUnwrap) Ref {
pub fn lowerNullCoalesce(self: *Lowering, nc: *const ast.NullCoalesce) Ref {
const lhs = self.lowerExpr(nc.lhs);
const inner_ty = self.resolveOptionalInner(self.inferExprType(nc.lhs));
const lhs_ty = self.inferExprType(nc.lhs);
// `??` requires an optional left operand. A resolved non-optional lhs is
// malformed user input: diagnose it here instead of letting the
// `.unresolved` inner type (from `resolveOptionalInner`) flow into the
// merge-block params / `optionalUnwrap` / the RHS target type and reach
// emit_llvm's "unresolved type reached LLVM emission" panic with no source
// location (issue 0172). Skip an already-`.unresolved` lhs: that comes
// from a PRIOR error (e.g. an undefined name) which has already been
// diagnosed — re-reporting would be a confusing second message.
const lhs_is_optional = !lhs_ty.isBuiltin() and self.module.types.get(lhs_ty) == .optional;
if (lhs_ty != .unresolved and !lhs_is_optional) {
if (self.diagnostics) |d|
d.addFmt(.err, nc.lhs.span, "left operand of '??' must be an optional, but has type '{s}'", .{self.formatTypeName(lhs_ty)});
return lhs; // placeholder — hasErrors() aborts before codegen
}
const inner_ty = self.resolveOptionalInner(lhs_ty);
// Short-circuit: only evaluate RHS if LHS is null.
// IMPORTANT: optional_unwrap must be in the "has value" branch,