Files
sx/issues/0168-array-of-optionals-element-load-broken.md
agra 3e8d003e3d fix: bindingless if/while/and/or over optional reads has_value (issue 0164)
lowerIfExpr emitted optional_has_value only for the binding form; a bare
'if opt' passed the raw {T,i1} aggregate to condBr, where emitCondBr's
catch-all struct arm silently folded it to 'i1 true' (structs always
truthy) — a silent miscompile that took the present-branch for null
optionals. while / and / or shared the same defect.

Reduce bindingless optional conditions to optional_has_value in
lowerIfExpr/lowerWhile and via a new lowerBoolCondition helper for and/or
operands. Replace the silent-true emitCondBr arm with a lowering-time
diagnostic (checkConditionType/isValidConditionType) rejecting conditions
whose type isn't bool/integer/pointer/optional; the backend @panic is now
an unreachable tripwire.

Regressions: examples/optionals/0908..0910 + diagnostics/1194 (negative).
Verified by 3+3 adversarial reviews.

Filed adjacent bugs found during review: 0168 (array-of-optionals element
load), 0169 (optional->bool coercion), 0170 (closure-optional layout).
2026-06-22 21:04:05 +03:00

46 lines
2.0 KiB
Markdown

# 0168 — indexing an array of optionals `[N]?T` produces a wrong/garbage element (segfault or wrong value)
## Symptom
Reading an element of an array whose element type is an optional (`[N]?T`) is
broken: depending on how the result is used it either SEGFAULTS or yields the
WRONG value (reads a present element as absent). Independent of issue 0164 (the
`if`-on-optional fix) — reproduces with a plain `??` and with a copy-to-local.
## Reproduction
```sx
#import "modules/std.sx";
main :: () {
arr : [2]?i64 = .{ null, 7 };
// (1) index result used directly → SEGFAULT (exit 134)
x := arr[1];
print("{}\n", x ?? -1); // expected: 7
// (2) copy element to a local then test → WRONG VALUE
e := arr[1]; // element 1 is present (7)
if e { print("present\n"); } else { print("absent\n"); } // prints "absent" — WRONG
}
```
Expected: element 1 is the present optional `7`. Observed: segfault in case
(1); `absent` (a wrong/absent optional) in case (2). The original surfacing form
`if arr[0] { ... }` also segfaults.
## Investigation prompt
The element load / addressing for an array of optionals appears to compute the
wrong element stride or mis-materialize the loaded `{T,i1}` optional aggregate.
Suspect the index/element-load lowering (`src/ir/lower/expr.zig` index-get path,
and `src/backend/llvm/ops.zig` `emitIndexGet`) when the element type is an
optional aggregate `{T,i1}` — check that the element size/alignment used for the
GEP matches the optional's real size (cf. the `size_of` vs `typeSizeBytes`
nuance for optionals), and that the loaded value is the full aggregate, not a
truncated/garbage read. Compare against a working `[N]T` (non-optional) array
load to isolate whether it's stride math or aggregate materialization.
Verify: case (1) prints `7`, case (2) prints `present`; also test `[N]?T` with a
struct payload (`[2]?Pt`) and writing elements then reading them back. Add an
`examples/optionals/09xx-array-of-optionals.sx` regression.