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.
This commit is contained in:
@@ -7,23 +7,30 @@
|
||||
// - null → pointer (`P : *void : null`)
|
||||
// - integer EXPRESSION → integer (`KE : s64 : M + 2`) — usable as a count too
|
||||
// - integer EXPRESSION → float (`WE : f32 : M + 2`)
|
||||
// - MIXED int+float EXPRESSION → float (`MF : f64 : M + 0.5`, both operand orders)
|
||||
//
|
||||
// Companion to the negative example 1143: the issue-0088 fix rejects a typed
|
||||
// const whose initializer mismatches its annotation, and these correctly-typed
|
||||
// consts must keep working (no over-rejection) — including const-EXPRESSION
|
||||
// initializers, whose type-based validation (attempt 2) must accept a correctly
|
||||
// typed expression even though it isn't a literal.
|
||||
//
|
||||
// `MF`/`MFR` pin the attempt-3 inferExprType promotion fix: a mixed int+float
|
||||
// arithmetic expression infers as the float result regardless of operand order,
|
||||
// so it matches an `f64` annotation (and folds to 2.5, not a truncated 2).
|
||||
#import "modules/std.sx";
|
||||
|
||||
M :: 2;
|
||||
|
||||
K : s64 : 4;
|
||||
W : f32 : 800;
|
||||
PI : f32 : 3.14159;
|
||||
S : string : "hi";
|
||||
P : *void : null;
|
||||
KE : s64 : M + 2;
|
||||
WE : f32 : M + 2;
|
||||
K : s64 : 4;
|
||||
W : f32 : 800;
|
||||
PI : f32 : 3.14159;
|
||||
S : string : "hi";
|
||||
P : *void : null;
|
||||
KE : s64 : M + 2;
|
||||
WE : f32 : M + 2;
|
||||
MF : f64 : M + 0.5;
|
||||
MFR : f64 : 0.5 + M;
|
||||
|
||||
main :: () {
|
||||
// Integer const: prints AND drives an array dimension (len 4).
|
||||
@@ -44,4 +51,8 @@ main :: () {
|
||||
// Integer const-EXPRESSION: prints AND drives an array dimension (len 4).
|
||||
b : [KE]s64 = ---;
|
||||
print("KE={} len={} WE={}\n", KE, b.len, WE);
|
||||
|
||||
// Mixed int+float const-EXPRESSION folds to the promoted float (2.5),
|
||||
// operand-order-independent.
|
||||
print("MF={} MFR={}\n", MF, MFR);
|
||||
}
|
||||
|
||||
35
examples/0163-types-mixed-numeric-promotion.sx
Normal file
35
examples/0163-types-mixed-numeric-promotion.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -10,17 +10,25 @@
|
||||
// validation is type-based, so a const-EXPRESSION initializer (`E : string :
|
||||
// M + 2`, `V : string : -M`) is rejected just like a literal — not skipped
|
||||
// because its node kind isn't a literal (issue 0088, attempt 2).
|
||||
//
|
||||
// The mixed-numeric pair (`s64 : M + 0.5`, `s64 : 0.5 + M`) is rejected in BOTH
|
||||
// operand orders: arithmetic binary-op inference now promotes int+float to the
|
||||
// float result (`Lowering.arithResultType`), so an s64 annotation no longer
|
||||
// matches a float-producing initializer regardless of which operand is the
|
||||
// float (issue 0088, attempt 3 — the inferExprType promotion root fix).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
M :: 2;
|
||||
|
||||
N : string : 4; // integer literal where a string is annotated
|
||||
F : s64 : "x"; // string literal where an integer is annotated
|
||||
B : s64 : true; // boolean literal where an integer is annotated
|
||||
G : s64 : 1.5; // float literal where an integer is annotated
|
||||
E : string : M + 2; // integer EXPRESSION where a string is annotated
|
||||
V : string : -M; // integer (unary) expression where a string is annotated
|
||||
N : string : 4; // integer literal where a string is annotated
|
||||
F : s64 : "x"; // string literal where an integer is annotated
|
||||
B : s64 : true; // boolean literal where an integer is annotated
|
||||
G : s64 : 1.5; // float literal where an integer is annotated
|
||||
E : string : M + 2; // integer EXPRESSION where a string is annotated
|
||||
V : string : -M; // integer (unary) expression where a string is annotated
|
||||
BAD : s64 : M + 0.5; // mixed int+float (int LHS) → f64, rejected vs s64
|
||||
BAD2 : s64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs s64 — order-independent
|
||||
|
||||
main :: () {
|
||||
print("unreachable\n");
|
||||
|
||||
@@ -3,3 +3,4 @@ W=800.000000 PI=3.141590
|
||||
S=hi
|
||||
P_is_null=true
|
||||
KE=4 len=4 WE=4.000000
|
||||
MF=2.500000 MFR=2.500000
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
add: 2.500000 2.500000
|
||||
mul: 3.000000 3.000000
|
||||
sub: 1.500000 div: 0.500000
|
||||
f32: 2.500000
|
||||
int: 5
|
||||
@@ -1,35 +1,47 @@
|
||||
error: type mismatch: constant 'N' is declared 'string' but its initializer is an integer literal
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:18:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:24:15
|
||||
|
|
||||
18 | N : string : 4; // integer literal where a string is annotated
|
||||
| ^
|
||||
24 | N : string : 4; // integer literal where a string is annotated
|
||||
| ^
|
||||
|
||||
error: type mismatch: constant 'F' is declared 's64' but its initializer is a string literal
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:19:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:25:15
|
||||
|
|
||||
19 | F : s64 : "x"; // string literal where an integer is annotated
|
||||
| ^^^
|
||||
25 | F : s64 : "x"; // string literal where an integer is annotated
|
||||
| ^^^
|
||||
|
||||
error: type mismatch: constant 'B' is declared 's64' but its initializer is a boolean literal
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:20:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:26:15
|
||||
|
|
||||
20 | B : s64 : true; // boolean literal where an integer is annotated
|
||||
| ^^^^
|
||||
26 | B : s64 : true; // boolean literal where an integer is annotated
|
||||
| ^^^^
|
||||
|
||||
error: type mismatch: constant 'G' is declared 's64' but its initializer is a float literal
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:21:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:27:15
|
||||
|
|
||||
21 | G : s64 : 1.5; // float literal where an integer is annotated
|
||||
| ^^^
|
||||
27 | G : s64 : 1.5; // float literal where an integer is annotated
|
||||
| ^^^
|
||||
|
||||
error: type mismatch: constant 'E' is declared 'string' but its initializer is an integer expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:22:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:28:15
|
||||
|
|
||||
22 | E : string : M + 2; // integer EXPRESSION where a string is annotated
|
||||
| ^^^^^
|
||||
28 | E : string : M + 2; // integer EXPRESSION where a string is annotated
|
||||
| ^^^^^
|
||||
|
||||
error: type mismatch: constant 'V' is declared 'string' but its initializer is an integer expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:23:14
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:29:15
|
||||
|
|
||||
23 | V : string : -M; // integer (unary) expression where a string is annotated
|
||||
| ^^
|
||||
29 | V : string : -M; // integer (unary) expression where a string is annotated
|
||||
| ^^
|
||||
|
||||
error: type mismatch: constant 'BAD' is declared 's64' but its initializer is a floating-point expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:30:15
|
||||
|
|
||||
30 | BAD : s64 : M + 0.5; // mixed int+float (int LHS) → f64, rejected vs s64
|
||||
| ^^^^^^^
|
||||
|
||||
error: type mismatch: constant 'BAD2' is declared 's64' but its initializer is a floating-point expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:31:15
|
||||
|
|
||||
31 | BAD2 : s64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs s64 — order-independent
|
||||
| ^^^^^^^
|
||||
|
||||
Reference in New Issue
Block a user