Files
sx/issues/0165-parenthesized-nested-optional-malformed.md
agra 2637ae98a5 docs: file issues 0164-0167 (optional/comptime bugs found during 0162 review)
0164 if <optional> no-binding folds has_value to true (silent miscompile)
0165 parenthesized nested optional ?(?T) malformed double-wrap (crash)
0166 ?? .{ } struct-literal default unresolved type (crash)
0167 comptime regToValue array-in-aggregate gap + unclean recovery
2026-06-22 19:43:55 +03:00

51 lines
2.1 KiB
Markdown

# 0165 — parenthesized nested optional `?(?T)` resolves to a malformed double-wrapped type
## Symptom
A nested optional written `?(?i64)` resolves to a spurious extra struct wrapper:
the destination type lowers to `{ { {i64,i1} }, i1 }` (triple-wrapped) instead of
the correct `{ {i64,i1}, i1 }`. Assigning an inner `?i64` then fails the LLVM
verifier:
```
LLVM verification failed: Invalid InsertValueInst operands!
%ow.val = insertvalue { { { i64, i1 } }, i1 } undef, { i64, i1 } %load, 0
```
Crash (non-zero exit). `??i64` (unparenthesized) is a separate parse error
(`expected type name`) and is NOT this bug — only the parenthesized `?(?T)` form
reaches type resolution and produces the malformed layout.
## Reproduction
```sx
#import "modules/std.sx";
main :: () {
inner : ?i64 = 5;
outer : ?(?i64) = inner;
print("ok\n");
}
```
Expected: `ok` (a well-formed `{ {i64,i1}, i1 }` outer optional wrapping the
inner `?i64`). Observed: LLVM verifier abort.
## Investigation prompt
The type-table interning (`src/ir/types.zig` `optionalOf` / the optional
lowering near `types.zig:87`) produces the CORRECT `{ {i64,i1}, i1 }` for a
real `optional(optional(i64))`. So the malformed layout comes from
`instruction.ty` itself: the parenthesized `(?i64)` inner type expression is
resolved as a single-field STRUCT wrapping `?i64`, not as the optional type
directly. Suspected area: resolution of a parenthesized type expression
(`src/ir/type_resolver.zig` and/or the parser's handling of `(?T)` as a type) —
a parenthesized type should resolve to the inner type unchanged, not introduce a
tuple/struct wrapper. `src/backend/llvm/ops.zig` `emitOptionalWrap` is the
faithful victim (it uses `toLLVMType(instruction.ty)`), not the cause.
Verify: the repro prints `ok`; `?(?i64)` round-trips (unwrap inner, read value);
confirm the IR type is `{ {i64,i1}, i1 }`. Add an
`examples/optionals/09xx-nested-parenthesized-optional.sx` regression. (The
`unresolved type` panic seen when an `!` unwrap is added is downstream recovery
fallout of the same root cause — should disappear once the layout is correct.)