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

2.1 KiB

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

#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.)