Files
sx/examples/0163-types-mixed-numeric-promotion.sx
agra b69ec43ba3 fix(ir): infer mixed int+float arithmetic as the promoted float [F0.7]
`ExprTyper.inferType`'s binary-op arm inferred every non-comparison op
from the LHS alone, so `M + 0.5` (s64 + f64) statically typed as s64
while `0.5 + M` typed as f64 — operand-order-dependent. The value path
(`lowerBinaryOp`) already promoted int×float → float, so static
inference disagreed with the value: `M + 0.5` formatted as a truncated
int and a typed const `BAD : s64 : M + 0.5` was accepted+truncated
(issue 0088 mixed-numeric escape).

Extract the value path's inline promotion into a shared
`Lowering.arithResultType(lhs, rhs)` and reuse it at both sites, so
arithmetic / bitwise / shift inference reports exactly the type the
lowered value carries — int LHS × float RHS → the float, order-
independent. The value-path behavior is unchanged (the block is moved
verbatim into the helper), so no IR shifts; the suite stays green. The
typed-const validation reuses `inferExprType`, so this auto-closes the
escape with no change to the validation logic.

- examples/1143: BAD/BAD2 (`s64 : M + 0.5`, `s64 : 0.5 + M`) rejected
  in both operand orders.
- examples/0162: MF/MFR (`f64 : M + 0.5`, `f64 : 0.5 + M`) fold to 2.5.
- examples/0163 (new): pins the inference fix in a value context
  (`print("{}", n + 0.5)` formats the float, both orders, +-*/, f32).
- expr_typer.test.zig: arithResultType + mixed-arithmetic inference.
- specs.md / readme.md: document the numeric-promotion rule.
- issues/0088: RESOLVED banner notes the inferExprType root fix.
2026-06-05 08:23:59 +03:00

36 lines
1.5 KiB
Plaintext

// Mixed int+float arithmetic infers as the FLOAT result, operand-order-independent.
//
// `print("{}", expr)` selects integer- vs float-formatting from the STATIC type
// `inferExprType` reports for the argument (not the lowered value's type), so it
// exercises the binary-op inference arm directly — distinct from the typed-const
// validation path. Before the attempt-3 fix, binary-op inference was LHS-biased:
// `n + 0.5` (int LHS) inferred `s64` and printed a truncated `2`, while `0.5 + n`
// (float LHS) inferred `f64` and printed `2.5`. The fix routes both through the
// shared promotion rule (`Lowering.arithResultType`, the same one `lowerBinaryOp`
// applies for the value), so an int operand with a float operand promotes to the
// float in either order.
//
// Regression (issue 0088, attempt 3 — the inferExprType numeric-promotion root fix).
#import "modules/std.sx";
main :: () {
n := 2; // runtime s64
// Addition, both operand orders — both promote to f64 → 2.5.
print("add: {} {}\n", n + 0.5, 0.5 + n);
// Multiplication, both orders — both promote → 3.0.
print("mul: {} {}\n", n * 1.5, 1.5 * n);
// Subtraction / division with the int on the left.
print("sub: {} div: {}\n", n - 0.5, n / 4.0);
// f32 operand promotes too (int LHS, f32 RHS).
half : f32 = 0.5;
print("f32: {}\n", n + half);
// A pure-int expression is unaffected — stays s64, prints as an integer.
print("int: {}\n", n + 3);
}