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).
30 lines
871 B
Plaintext
30 lines
871 B
Plaintext
// Bindingless `if <optional>` must test the optional's has_value flag, not
|
|
// fold the `{T,i1}` aggregate truthy. Covers struct-form optionals (`?i64`,
|
|
// `?P`) in both states, exercising BOTH branches — without any `|x|` binding.
|
|
//
|
|
// Regression (issue 0164): a bare `if opt { }` over a struct-repr optional
|
|
// emitted `br i1 true`, so a null optional took the present branch.
|
|
#import "modules/std.sx";
|
|
|
|
P :: struct { x: i64; }
|
|
|
|
check_i64 :: (n: ?i64) {
|
|
if n { print("i64 present\n"); } else { print("i64 absent\n"); }
|
|
}
|
|
|
|
check_p :: (p: ?P) {
|
|
if p { print("P present\n"); } else { print("P absent\n"); }
|
|
}
|
|
|
|
main :: () {
|
|
a : ?i64 = null;
|
|
b : ?i64 = 42;
|
|
check_i64(a); // i64 absent
|
|
check_i64(b); // i64 present
|
|
|
|
p_null : ?P = null;
|
|
p_some : ?P = P.{ x = 9 };
|
|
check_p(p_null); // P absent
|
|
check_p(p_some); // P present
|
|
}
|