Files
sx/issues/0066-match-arm-negative-literal-phi-width.md
agra 80abaf1e7d issues/0066: RESOLVED — match-value arms lowered against result type
A value-position match's arms are now lowered with `target_type` set to
the merge's `result_type`, so positive and negated integer literals pick
the same width. Fixes the `PHI node operands are not the same type as the
result` failure for `if n == { case 0: 100; else: -1; }`-style returns.

Regression: examples/0043-basic-match-value-mixed-width.sx.
Gates: zig build, zig build test, run_examples.sh -> 345 passed.
2026-06-02 09:35:41 +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: s32) -> s32 {
    if n == {
        case 0: 100;
        case 1: 10;
        else:  -1;        // ← negated literal arm → phi width mismatch
    }
}
main :: () -> s32 { classify(1) }

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

Workaround: give the arm an explicitly-typed value (else: { x : s32 = -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 (s64 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.