Type-checking gaps (segfault/corruption → compile errors): - 0197: reject a store into an annotated slot whose value has no modeled coercion AND a different byte width (a 16-byte string into a 4-byte i32 overran the slot and segfaulted). New checkAssignable / noneReinterpretIsUnsafe (coerce.zig, width via the LLVM-accurate typeSizeBytes) wired into every store site: var/const-decl, single + multi assignment (identifier/field/index/ element/deref), named-return defaults. Same-width reinterpretations (*T→[*]T, i64→isize, fn-ref) and explicit xx/cast stay allowed; cascades suppressed via externalErrorsExist. Examples 1205, 1206. - 0198: an implicit `Any → T` unbox is now a compile error (it blindly reinterpreted the boxed payload — silent garbage for a wrong scalar, a segfault for an aggregate). xx and compiler-generated match/pack unboxes are unaffected. Example 1207. - 0199: `Any == <concrete>` (one operand Any) aborted the LLVM verifier — the comparison arm now fires when either operand is Any, boxing the concrete side first. Example 0654. Multi-return deferrals (PLAN-MULTIRET #6 + named-order + D3 + generic): - Reorder named return elements by name instead of requiring slot order; error on unknown/duplicate/missing (value-only AND full-failable-tuple forms). Examples 0210, 0214. - Reject a bare-paren (A, B) multi-return signature in generic-arg position (return-position-only). Example 0215. - Multi-return closure types / lambda literals work via the reused tuple machinery (destructure, single-bind+field, lambda arg). Example 0216. - Generic multi-return: positional works (0217); 0200: the named-slot implicit-return form now works for generic free fns + struct methods — monomorphizeFunction now calls bindNamedReturnSlots. Example 0218. readme.md documents the annotated-store coercion rule; CHECKPOINT-MULTIRET.md updated. Full corpus green (850/0).
3.1 KiB
RESOLVED (2026-06-27). Fix: the
Any-shaped==/!=arm insrc/ir/lower/expr.zignow fires when EITHER operand is.any(was both). A concrete operand is boxed toAny(builder.boxAny) first, so both sides are 16-byte boxes; then both unbox to their.i64value words and compare — the same value-identity the both-Anypath uses (tags not compared). An already-errored.unresolved/.voidoperand falls through (no cascade). Verified:x == 5,x == 6,x != 6,5 == x(reversed), boolAny, and the both-Anyform all work; no verifier abort. Regression test:examples/comptime/0654-comptime-any-eq-concrete.sx. (Aggregate-Anycomparison still uses value-word identity — the same limitation the both-Anypath always had; orthogonal to this verifier fix.)
0199 — Any == <concrete> (one operand Any) fails LLVM verification
Symptom — An equality / inequality comparison where exactly ONE operand is
Any and the other is a concrete type is not handled: it falls through to a
plain icmp on a 16-byte {tag, value} aggregate vs a scalar and aborts the
LLVM verifier.
- Observed:
x : Any = 5; if x == 5 { ... }→error: Both operands to ICmp are not of the same type! {i64,i64} vs i64,LLVM verification failed, exit 1 (loud — not a segfault / silent miscompile). - Expected: either box the concrete operand to
Any(then compare asAny == Any, the path that already works) consulting the tag, OR a clean located compile diagnostic (e.g. "compare an 'Any' against a value of its boxed type, orxxthe Any first"). Not an LLVM verifier abort.
Distinct from issue 0198 (the implicit Any → T unbox). Surfaced by the
adversarial review of the 0198 fix. Any == Any works correctly.
Reproduction
#import "modules/std.sx";
main :: () -> i64 {
x : Any = 5;
if x == 5 { return 1; } // error: ICmp operand type mismatch {i64,i64} vs i64
return 0;
}
./zig-out/bin/sx run repro.sx → LLVM verification failed, exit 1.
Investigation prompt
The Any equality path is in src/ir/lower/expr.zig (~3201-3215), gated on
lhs_ty == .any and rhs_ty == .any — it unbox_anys both sides to .i64 and
cmp_eqs the value words. When only ONE side is .any, that guard is false and
the comparison falls through to the generic numeric/icmp path, which emits an
icmp between the 16-byte Any aggregate and the scalar → verifier abort.
The fix likely adds a mixed-operand arm: when exactly one operand is .any and
the other is a concrete type T, box the concrete operand to Any
(self.builder.boxAny(concrete, T)) and reuse the existing Any == Any
value-word comparison — OR, if comparing only the payload word is unsound across
types (a 5:i64 and a 5.0:f64 would compare equal by bits), gate on the tag
too / emit a diagnostic. Decide whether Any == concrete should compare by
(tag AND value) or be disallowed; mirror whatever Any == Any semantics are
documented. Verify: the repro compiles and x == 5 is true, OR a clean
diagnostic is emitted — never an LLVM verifier abort.