fix: diagnose ?(?T) tuple-payload mismatch instead of malformed IR (issue 0165)

In type position (T) is a 1-tuple (specs.md:843), so ?(?i64) is
optional(tuple(?i64)); assigning a bare ?i64 had coerceToType classify
.none and pass the value through, then optionalWrap built a corrupt
insertvalue that aborted the LLVM verifier. After coercing toward an
optional's child, verify the coerced type equals the child type
(stmt.zig decl-init + coerce.zig .optional_wrap); on mismatch emit a
located diagnostic (tuple-specific note only when the child is a tuple).
formatTypeName now renders tuples as (x: i64, y: i64).

Regressions: optionals/0911 (nested optional via alias, round-trip),
diagnostics/1195 (the mismatch diagnostic). Updated diagnostics/1101 +
protocols/0414 goldens for the improved tuple type-name rendering.
Verified by 3 adversarial reviews. Filed adjacent bug 0171 (?any child
not canonicalized).
This commit is contained in:
agra
2026-06-22 21:54:12 +03:00
parent 3e8d003e3d
commit 0bc8005b99
16 changed files with 198 additions and 4 deletions

View File

@@ -1,5 +1,22 @@
# 0165 — parenthesized nested optional `?(?T)` resolves to a malformed double-wrapped type
> **RESOLVED.** The issue's premise was partly wrong: per `specs.md:843`, in
> TYPE position `(T)` is a single-field TUPLE, not a grouping — so `?(?i64)` is
> `optional(tuple(?i64))` and the compiler's `{ {{i64,i1}}, i1 }` layout was
> CORRECT. The real bug was a silent malformed-IR path: assigning a bare `?i64`
> to it had `coerceToType` classify `.none` and pass the value through unchanged,
> then `optionalWrap` built a corrupt `insertvalue` that aborted the LLVM
> verifier. Fix: after coercing toward an optional's child, verify the coerced
> value's type equals the child type (`src/ir/lower/stmt.zig` decl-init +
> `src/ir/lower/coerce.zig` `.optional_wrap`); on mismatch emit a located
> diagnostic (with a tuple-specific note only when the child is a tuple) instead
> of corrupt IR. `formatTypeName` now renders tuples as `(x: i64, y: i64)`.
> Genuine nested optionals via alias (`Opt :: ?i64; ?Opt`) work and round-trip.
> Regressions: `examples/optionals/0911-nested-optional-via-alias.sx`,
> `examples/diagnostics/1195-diagnostics-err-parenthesized-optional-tuple.sx`.
> Verified by 3 adversarial reviews. (Adjacent pre-existing bug found + filed:
> 0171 `?any` child not canonicalized.)
## Symptom
A nested optional written `?(?i64)` resolves to a spurious extra struct wrapper: