Files
sx/issues/0066-match-arm-negative-literal-phi-width.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

2.8 KiB

0066 — match-as-value with a negated-literal arm builds a mismatched phi

RESOLVED. lowerMatch's value path (has_value_merge) now lowers each arm body with target_type = result_type, so literals and negated literals in the arms pick the merge's width instead of leaking a narrower one. The phi operands are uniform; coerceToType still runs afterward as a backstop. Regression: examples/0043-basic-match-value-mixed-width.sx.

Symptom

A value-position match (the if subject == { case ... } sugar) returning a small integer type, where one arm is a negated integer literal (-1) and others are plain positive literals, fails LLVM verification:

LLVM verification failed: PHI node operands are not the same type as the result!
  %bp = phi i64 [ 100, %match.arm.1 ], [ 10, %match.arm.2 ], [ -1, %match.arm.3 ]

The negated arm lowers its value at a different integer width (it emits an i32 that is then sign-extended) than the positive-literal arms (i64), so the merge phi's operands disagree with its result type. Positive literals in every arm work; if/else with -1 works. So it is specific to a negated literal in a match arm value.

This is orthogonal to the trailing-; block-value rule — the repro uses the exempt case …: expr; arm form (unchanged by that migration) and reproduces with ordinary semicolon-terminated arms. Filed while writing the block-value regression example (0040); the example sidesteps it with positive arm values.

Reproduction

classify :: (n: i32) -> i32 {
    if n == {
        case 0: 100;
        case 1: 10;
        else:  -1;        // ← negated literal arm → phi width mismatch
    }
}
main :: () -> i32 { classify(1) }

Expected: compiles, classify(1) returns 10. Actual: LLVM verification failure.

Workaround: give the arm an explicitly-typed value (else: { x : i32 = -1; x }) or avoid the negation.

Investigation prompt

Look at the match-as-value lowering in src/ir/lower.zig (lowerMatch, the has_value_merge path ~line 4500, and the arm result_type inference ~line 11698). The arm value is lowered via lowerBlockValue(arm.body) then coerceToType(v, v_ty, result_type). For a negated-literal arm the value's type comes out narrower than result_type (i64 here), and the coercion path that should widen it before the br merge_bb, {v} either runs against the wrong target or is skipped — so the phi gets an i32 operand under an i64 result. Make the arm value coerce to the merge's result_type consistently (mirror how the positive-literal arms are handled), or set target_type = result_type while lowering each arm body so the negate picks the right width. Verify with the repro above (expect exit 10) and add a regression example.

Status

OPEN. Pre-existing match-value codegen quirk, surfaced (not caused) by the block-value work.