fix(ir): narrow non-integral const-float EXPRESSIONS at typed local/field/param; align const message [F0.11]
Completes issue 0095 (attempt 2). The attempt-1 coerce arm only caught a direct `const_float` literal, so a non-integral const-folded float EXPRESSION still truncated silently at a typed local / field default / param default: M :: 2; local : s64 = M + 0.5; // → 2 (silent truncation — BUG; now ERRORS) fld : s64 = M + 0.5; // field default — same take(x : s64 = M + 0.5) // param default — same while the typed-CONST site already errored. The integral expression (`M + 2.0` → 4) folded but the runtime/explicit-cast paths must stay untouched. Fix: - New `program_index.evalConstFloatExpr` — the f64 counterpart to `evalConstIntExpr`, delegating every integer subtree back to it (no parallel integer logic) and adding only the float literal / unary-negate / `+ - * /` arms. Pure (no diagnostics, no resolution side effects). - `Lowering.foldComptimeFloatInit` applies the unified rule to a typed-binding initializer EXPRESSION: an integral comptime float folds to its `constInt`, a non-integral one errors, a genuine runtime float / `xx`-cast falls through to the normal path. It runs `evalConstFloatExpr` FIRST (pure) so a `$pack[i]` argument is never spuriously type-resolved outside an active binding, then gates on `isFloat(inferExprType)` so a plain comptime int is left alone. Wired into the typed-local path, the three struct field-default sites (via a shared `lowerCoercedDefault`), and the call-argument loop (covers expanded param defaults). - One `Lowering.diagNonIntegralNarrow` now emits the narrowing wording at all five sites (coerce arm, global init, const-expr value, the typed-binding sites, and the typed-const path). The typed-CONST non-integral diagnostic therefore reads "cannot implicitly narrow non-integral float …" instead of the stale "initializer is a float literal / floating-point expression". Tests: examples/1146 (negative) extended with non-integral const-EXPRESSION cases at local/field/param; examples/0168 (positive) extended with integral const-EXPRESSION folds and `xx (M + 0.5)` truncation; examples/1143 reconciled to the aligned const message (G/BAD/BAD2 stay errors); unit test `evalConstFloatExpr folds comptime float expressions`. Full gate green (447).
This commit is contained in:
@@ -2,17 +2,23 @@
|
|||||||
// flowing into an integer-typed binding FOLDS to its integer — the same
|
// flowing into an integer-typed binding FOLDS to its integer — the same
|
||||||
// `floatToIntExact` rule an array dimension / `$K: Count` already uses — across
|
// `floatToIntExact` rule an array dimension / `$K: Count` already uses — across
|
||||||
// a typed LOCAL, a struct FIELD default, a typed module CONST, and a function
|
// a typed LOCAL, a struct FIELD default, a typed module CONST, and a function
|
||||||
// PARAM default. The escape hatch (`xx` / `cast`) still TRUNCATES any float,
|
// PARAM default. It folds whether written as a float LITERAL (`4.0`) or a
|
||||||
// integral or not.
|
// const-EXPRESSION (`M + 2.0`). The escape hatch (`xx` / `cast`) still TRUNCATES
|
||||||
|
// any float, integral or not — including a non-integral const expression.
|
||||||
//
|
//
|
||||||
// Companion to the negative example 1146 (non-integral floats error).
|
// Companion to the negative example 1146 (non-integral floats error).
|
||||||
// Regression (issue 0095): a typed local/param/field silently truncated a float
|
// Regression (issue 0095): a typed local/param/field silently truncated a float
|
||||||
// initializer (`y : s64 = 1.5` → 1) with no diagnostic; the rule now folds an
|
// initializer (`y : s64 = 1.5` → 1) with no diagnostic, and a non-integral const
|
||||||
// integral float and rejects a non-integral one.
|
// EXPRESSION (`M + 0.5`) truncated even when written through an int binding; the
|
||||||
|
// rule now folds an integral float (literal or expression) and rejects a
|
||||||
|
// non-integral one.
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
M :: 2; // module const, for the const-EXPRESSION cases
|
||||||
|
|
||||||
Box :: struct {
|
Box :: struct {
|
||||||
n : s64 = 4.0; // integral float field default → folds to 4
|
n : s64 = 4.0; // integral float field default → folds to 4
|
||||||
|
ne : s64 = M + 2.0; // integral float EXPRESSION field default → folds to 4
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefault :: (x : s64 = 6.0) -> s64 { return x; } // param default → 6
|
withDefault :: (x : s64 = 6.0) -> s64 { return x; } // param default → 6
|
||||||
@@ -20,17 +26,18 @@ withDefault :: (x : s64 = 6.0) -> s64 { return x; } // param default → 6
|
|||||||
K : s64 : 8.0; // integral float module const → folds to 8
|
K : s64 : 8.0; // integral float module const → folds to 8
|
||||||
|
|
||||||
main :: () {
|
main :: () {
|
||||||
// Typed local: integral float folds.
|
// Typed local: integral float folds (literal + expression).
|
||||||
z : s64 = 4.0;
|
z : s64 = 4.0;
|
||||||
print("local={}\n", z);
|
ze : s64 = M + 2.0;
|
||||||
|
print("local={} localExpr={}\n", z, ze);
|
||||||
|
|
||||||
// Negative integral float folds to its (negative) integer.
|
// Negative integral float folds to its (negative) integer.
|
||||||
neg : s64 = -2.0;
|
neg : s64 = -2.0;
|
||||||
print("neg={}\n", neg);
|
print("neg={}\n", neg);
|
||||||
|
|
||||||
// Struct field default folds.
|
// Struct field defaults fold (literal + expression).
|
||||||
b := Box.{};
|
b := Box.{};
|
||||||
print("field={}\n", b.n);
|
print("field={} fieldExpr={}\n", b.n, b.ne);
|
||||||
|
|
||||||
// Param default folds.
|
// Param default folds.
|
||||||
print("param={}\n", withDefault());
|
print("param={}\n", withDefault());
|
||||||
@@ -39,8 +46,10 @@ main :: () {
|
|||||||
a : [K]s64 = ---;
|
a : [K]s64 = ---;
|
||||||
print("const={} len={}\n", K, a.len);
|
print("const={} len={}\n", K, a.len);
|
||||||
|
|
||||||
// Explicit escape: `xx` / `cast` always truncate, integral or not.
|
// Explicit escape: `xx` / `cast` always truncate, integral or not —
|
||||||
|
// including a non-integral const EXPRESSION (`xx (M + 0.5)` → 2).
|
||||||
e : s64 = xx 4.9;
|
e : s64 = xx 4.9;
|
||||||
c : s64 = cast(s64) 1.5;
|
c : s64 = cast(s64) 1.5;
|
||||||
print("xx={} cast={}\n", e, c);
|
xc : s64 = xx (M + 0.5);
|
||||||
|
print("xx={} cast={} xxExpr={}\n", e, c, xc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,34 @@
|
|||||||
// implicitly narrowing to an integer-typed binding is a COMPILE ERROR — not a
|
// implicitly narrowing to an integer-typed binding is a COMPILE ERROR — not a
|
||||||
// silent truncation. The rule fires at a typed LOCAL initializer, a function
|
// silent truncation. The rule fires at a typed LOCAL initializer, a function
|
||||||
// PARAM default, and a struct FIELD default; each emits a narrowing diagnostic
|
// PARAM default, and a struct FIELD default; each emits a narrowing diagnostic
|
||||||
// at the offending float and aborts (exit 1). The fix is the integral-fold /
|
// at the offending float and aborts (exit 1). It fires whether the float is a
|
||||||
// non-integral-error rule shared with the array-dimension path.
|
// LITERAL (`1.5`) or a compile-time const EXPRESSION (`M + 0.5`) — the latter is
|
||||||
|
// the core of issue 0095, which previously slipped through and truncated to 2.
|
||||||
|
// The fix is the integral-fold / non-integral-error rule shared with the
|
||||||
|
// array-dimension path.
|
||||||
//
|
//
|
||||||
// The escape hatch stays open: `y : s64 = xx 1.5` (or `cast(s64) 1.5`)
|
// The escape hatch stays open: `y : s64 = xx 1.5` (or `cast(s64) 1.5`)
|
||||||
// truncates with no error — exercised on the POSITIVE side (example 0168).
|
// truncates with no error — exercised on the POSITIVE side (example 0168).
|
||||||
//
|
//
|
||||||
// Regression (issue 0095): `y : s64 = 1.5` silently truncated to 1.
|
// Regression (issue 0095): `y : s64 = 1.5` silently truncated to 1, and
|
||||||
|
// `y : s64 = M + 0.5` silently truncated to 2.
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
M :: 2; // module const, for the const-EXPRESSION cases
|
||||||
|
|
||||||
Bad :: struct {
|
Bad :: struct {
|
||||||
f : s64 = 3.5; // non-integral field default → error
|
f : s64 = 3.5; // non-integral float LITERAL field default → error
|
||||||
|
fe : s64 = M + 0.5; // non-integral const-EXPRESSION field default → error
|
||||||
}
|
}
|
||||||
|
|
||||||
badDefault :: (x : s64 = 2.5) -> s64 { return x; } // non-integral param default → error
|
badLit :: (x : s64 = 2.5) -> s64 { return x; } // non-integral LITERAL param default → error
|
||||||
|
badExpr :: (x : s64 = M + 0.5) -> s64 { return x; } // non-integral const-EXPR param default → error
|
||||||
|
|
||||||
main :: () {
|
main :: () {
|
||||||
y : s64 = 1.5; // non-integral local initializer → error
|
y : s64 = 1.5; // non-integral float LITERAL local → error
|
||||||
|
ye : s64 = M + 0.5; // non-integral const-EXPRESSION local → error
|
||||||
b := Bad.{};
|
b := Bad.{};
|
||||||
print("{}\n", b.f);
|
print("{} {}\n", b.f, b.fe);
|
||||||
print("{}\n", badDefault());
|
print("{} {}\n", badLit(), badExpr());
|
||||||
print("{}\n", y);
|
print("{} {}\n", y, ye);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local=4
|
local=4 localExpr=4
|
||||||
neg=-2
|
neg=-2
|
||||||
field=4
|
field=4 fieldExpr=4
|
||||||
param=6
|
param=6
|
||||||
const=8 len=8
|
const=8 len=8
|
||||||
xx=4 cast=1
|
xx=4 cast=1 xxExpr=2
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ error: type mismatch: constant 'B' is declared 's64' but its initializer is a bo
|
|||||||
26 | 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
|
error: cannot implicitly narrow non-integral float '1.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:27:15
|
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:27:15
|
||||||
|
|
|
|
||||||
27 | G : s64 : 1.5; // float literal where an integer is annotated
|
27 | G : s64 : 1.5; // float literal where an integer is annotated
|
||||||
@@ -34,13 +34,13 @@ error: type mismatch: constant 'V' is declared 'string' but its initializer is a
|
|||||||
29 | 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
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:30:15
|
--> 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
|
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
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:31:15
|
--> 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
|
31 | BAD2 : s64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs s64 — order-independent
|
||||||
|
|||||||
@@ -1,17 +1,35 @@
|
|||||||
error: cannot implicitly narrow non-integral float '1.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
error: cannot implicitly narrow non-integral float '1.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:21:15
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:29:16
|
||||||
|
|
|
|
||||||
21 | y : s64 = 1.5; // non-integral local initializer → error
|
29 | y : s64 = 1.5; // non-integral float LITERAL local → error
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
||||||
error: cannot implicitly narrow non-integral float '3.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
|
||||||
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:15:15
|
|
||||||
|
|
|
||||||
15 | f : s64 = 3.5; // non-integral field default → error
|
|
||||||
| ^^^
|
|
||||||
|
|
||||||
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:18:26
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:30:16
|
||||||
|
|
|
|
||||||
18 | badDefault :: (x : s64 = 2.5) -> s64 { return x; } // non-integral param default → error
|
30 | ye : s64 = M + 0.5; // non-integral const-EXPRESSION local → error
|
||||||
| ^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: cannot implicitly narrow non-integral float '3.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:21:16
|
||||||
|
|
|
||||||
|
21 | f : s64 = 3.5; // non-integral float LITERAL field default → error
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:22:16
|
||||||
|
|
|
||||||
|
22 | fe : s64 = M + 0.5; // non-integral const-EXPRESSION field default → error
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:25:23
|
||||||
|
|
|
||||||
|
25 | badLit :: (x : s64 = 2.5) -> s64 { return x; } // non-integral LITERAL param default → error
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: cannot implicitly narrow non-integral float '2.5' to 's64'; use an explicit cast (`xx`/`cast`)
|
||||||
|
--> examples/1146-diagnostics-nonintegral-float-to-int.sx:26:23
|
||||||
|
|
|
||||||
|
26 | badExpr :: (x : s64 = M + 0.5) -> s64 { return x; } // non-integral const-EXPR param default → error
|
||||||
|
| ^^^^^^^
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
> dim — all reusing the single `program_index.floatToIntExact` /
|
> dim — all reusing the single `program_index.floatToIntExact` /
|
||||||
> `evalConstIntExpr` facility (no second integral check).
|
> `evalConstIntExpr` facility (no second integral check).
|
||||||
>
|
>
|
||||||
> Fix (`src/ir/lower.zig`, `src/ir/module.zig`):
|
> Fix (`src/ir/lower.zig`, `src/ir/module.zig`, `src/ir/program_index.zig`):
|
||||||
> - `Builder.constFloatInfo` reads a compile-time `const_float` back from its
|
> - `Builder.constFloatInfo` reads a compile-time `const_float` back from its
|
||||||
> Ref (value + span).
|
> Ref (value + span).
|
||||||
> - `coerceToType` now means IMPLICIT coercion: its `.float_to_int` arm folds an
|
> - `coerceToType` now means IMPLICIT coercion: its `.float_to_int` arm folds an
|
||||||
@@ -24,11 +24,33 @@
|
|||||||
> / `constExprValue` / `globalInitValue` fold an integral float to its int and
|
> / `constExprValue` / `globalInitValue` fold an integral float to its int and
|
||||||
> reject a non-integral one.
|
> reject a non-integral one.
|
||||||
>
|
>
|
||||||
|
> **Completion (F0.11 attempt 2)** — the direct-`const_float` coerce arm only
|
||||||
|
> caught a float LITERAL; a non-integral const-folded float EXPRESSION
|
||||||
|
> (`local/field/param : s64 = M + 0.5`) still truncated silently. Closed by:
|
||||||
|
> - New `program_index.evalConstFloatExpr` — the f64 counterpart to
|
||||||
|
> `evalConstIntExpr`, delegating every integer subtree back to it (no parallel
|
||||||
|
> integer logic), adding only the float literal / negate / `+ - * /` arms.
|
||||||
|
> - `Lowering.foldComptimeFloatInit` routes the typed LOCAL, struct FIELD
|
||||||
|
> default, and call ARGUMENT (incl. an expanded param default) through
|
||||||
|
> `evalConstFloatExpr` + `floatToIntExact`: an integral comptime float folds,
|
||||||
|
> a non-integral one errors, a genuine runtime float / `xx` cast is left to the
|
||||||
|
> normal path. (Run pure `evalConstFloatExpr` FIRST so a `$pack[i]` arg isn't
|
||||||
|
> spuriously type-resolved out of binding.)
|
||||||
|
> - One `Lowering.diagNonIntegralNarrow` now emits the narrowing wording at all
|
||||||
|
> five sites (coerce arm, global init, const-expr value, the typed-binding
|
||||||
|
> sites, and the typed-const path), so the typed-CONST non-integral diagnostic
|
||||||
|
> reads `cannot implicitly narrow non-integral float …` instead of the stale
|
||||||
|
> `initializer is a float literal / floating-point expression`.
|
||||||
|
>
|
||||||
> Regression tests: `examples/0168-types-integral-float-to-int.sx` (positive —
|
> Regression tests: `examples/0168-types-integral-float-to-int.sx` (positive —
|
||||||
> local/field/param/const fold, `xx`/`cast` truncate), `examples/1146-diagnostics-
|
> local/field/param/const fold, integral const-EXPRESSION (`M + 2.0`) folds,
|
||||||
> nonintegral-float-to-int.sx` (negative — local/param/field error), plus the
|
> `xx`/`cast` truncate incl. `xx (M + 0.5)`), `examples/1146-diagnostics-
|
||||||
> integral-float const cases added to `examples/0162-types-typed-module-const-
|
> nonintegral-float-to-int.sx` (negative — non-integral LITERAL and const-
|
||||||
> roundtrip.sx`. Non-integral const cases in `examples/1143` stay errors.
|
> EXPRESSION error at local/param/field), the integral-float const cases in
|
||||||
|
> `examples/0162-types-typed-module-const-roundtrip.sx`, and the aligned const
|
||||||
|
> diagnostic in `examples/1143-diagnostics-typed-module-const-mismatch.sx`
|
||||||
|
> (G / BAD / BAD2 stay errors with the new wording). Unit:
|
||||||
|
> `program_index.test.zig` "evalConstFloatExpr folds comptime float expressions".
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
A typed LOCAL (and likely typed param/field) silently truncates a floating-point
|
A typed LOCAL (and likely typed param/field) silently truncates a floating-point
|
||||||
|
|||||||
16
readme.md
16
readme.md
@@ -125,13 +125,15 @@ while `F : f64 : M + 0.5` folds to `2.5`.
|
|||||||
|
|
||||||
**Float → integer narrowing (unified rule).** A float flowing into an
|
**Float → integer narrowing (unified rule).** A float flowing into an
|
||||||
integer-typed binding *without* a cast follows the same integral-fold rule an
|
integer-typed binding *without* a cast follows the same integral-fold rule an
|
||||||
array dimension uses: an **integral** float folds to its integer, a
|
array dimension uses: an **integral** compile-time float folds to its integer, a
|
||||||
**non-integral** float is a compile error. This is uniform across a typed local,
|
**non-integral** one is a compile error. It holds whether the value is a literal
|
||||||
a parameter default, a struct field default, and a typed constant —
|
or a const expression, and is uniform across a typed local, a parameter default,
|
||||||
`y : s64 = 4.0` and `K : s64 : 4.0` both give `4` (and `K : s64 : M + 2.0` folds
|
a struct field default, a call argument, and a typed constant — `y : s64 = 4.0`,
|
||||||
to `4`), while `y : s64 = 1.5` and `N : s64 : 1.5` both error. An explicit
|
`K : s64 : 4.0`, and `y : s64 = M + 2.0` all give `4`, while `y : s64 = 1.5`,
|
||||||
`xx` / `cast(s64)` is the escape hatch and always truncates (`y : s64 = xx 1.5`
|
`N : s64 : 1.5`, and `y : s64 = M + 0.5` all error (one wording everywhere:
|
||||||
→ `1`).
|
`cannot implicitly narrow non-integral float …`). An explicit `xx` / `cast(s64)`
|
||||||
|
is the escape hatch and always truncates (`y : s64 = xx 1.5` → `1`,
|
||||||
|
`y : s64 = xx (M + 0.5)` → `2`); a genuine runtime float is likewise unaffected.
|
||||||
|
|
||||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||||
spelling can't be used as an identifier at a **value-binding or declaration-name**
|
spelling can't be used as an identifier at a **value-binding or declaration-name**
|
||||||
|
|||||||
19
specs.md
19
specs.md
@@ -1426,14 +1426,19 @@ isReady : ValueListenable(bool) = map(
|
|||||||
an integer-typed binding without `xx`/`cast` is governed by the SAME rule an
|
an integer-typed binding without `xx`/`cast` is governed by the SAME rule an
|
||||||
array dimension / lane count uses (see "Array dimensions are integral", §2):
|
array dimension / lane count uses (see "Array dimensions are integral", §2):
|
||||||
|
|
||||||
- An **integral** compile-time float **folds** to its integer:
|
- An **integral** compile-time float **folds** to its integer, whether written
|
||||||
`y : s64 = 4.0` ≡ `y : s64 = 4`, `n : s64 = -2.0` ≡ `-2`.
|
as a literal or a const expression: `y : s64 = 4.0` ≡ `y : s64 = 4`,
|
||||||
- A **non-integral** compile-time float is a **compile error**:
|
`n : s64 = -2.0` ≡ `-2`, `y : s64 = M + 2.0` → 4 (`M :: 2`).
|
||||||
`y : s64 = 1.5` → "cannot implicitly narrow non-integral float '1.5' to 's64'".
|
- A **non-integral** compile-time float — literal OR const expression — is a
|
||||||
|
**compile error** with one uniform wording at every site:
|
||||||
|
`y : s64 = 1.5` and `y : s64 = M + 0.5` both →
|
||||||
|
"cannot implicitly narrow non-integral float '…' to 's64'; use an explicit
|
||||||
|
cast (`xx`/`cast`)".
|
||||||
- This applies uniformly to a typed **local**, a function **param default**, a
|
- This applies uniformly to a typed **local**, a function **param default**, a
|
||||||
struct **field default**, and a typed module **constant**
|
struct **field default**, a call **argument**, and a typed module **constant**
|
||||||
(`K : s64 : 4.0` → 4; `N : s64 : 1.5` → error; `K : s64 : M + 2.0` → 4 when
|
(`K : s64 : 4.0` → 4; `K : s64 : M + 2.0` → 4; `N : s64 : 1.5` and
|
||||||
the expression folds to an integer).
|
`N : s64 : M + 0.5` → error). A **runtime** float (one with no compile-time
|
||||||
|
value) is unaffected — narrow it explicitly with `xx`/`cast`.
|
||||||
|
|
||||||
**Explicit (narrowing)** — requires `xx` prefix (or `cast(T)`):
|
**Explicit (narrowing)** — requires `xx` prefix (or `cast(T)`):
|
||||||
- Integer to narrower integer (`s32` → `u8`)
|
- Integer to narrower integer (`s32` → `u8`)
|
||||||
|
|||||||
130
src/ir/lower.zig
130
src/ir/lower.zig
@@ -943,6 +943,18 @@ pub const Lowering = struct {
|
|||||||
// const → a bogus pointer that segfaults at the use site) and let the
|
// const → a bogus pointer that segfaults at the use site) and let the
|
||||||
// count path fold it (`[N]s64` → 4). Issue 0088.
|
// count path fold it (`[N]s64` → 4). Issue 0088.
|
||||||
if (!self.typedConstInitFits(cd.value, ty)) {
|
if (!self.typedConstInitFits(cd.value, ty)) {
|
||||||
|
// A non-integral compile-time float into an integer const is the
|
||||||
|
// same implicit-narrowing failure as a typed local/field/param —
|
||||||
|
// report it with the unified wording (integral floats now FOLD here,
|
||||||
|
// so the old generic "initializer is a float literal/expression"
|
||||||
|
// message is stale). Every other mismatch keeps the generic wording.
|
||||||
|
if (self.isIntEx(ty) and isFloat(self.inferExprType(cd.value))) {
|
||||||
|
if (program_index_mod.evalConstFloatExpr(cd.value, self)) |fv| {
|
||||||
|
self.diagNonIntegralNarrow(cd.value.span, fv, ty);
|
||||||
|
_ = self.program_index.module_const_map.remove(cd.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (self.diagnostics) |d| {
|
if (self.diagnostics) |d| {
|
||||||
d.addFmt(.err, cd.value.span, "type mismatch: constant '{s}' is declared '{s}' but its initializer is {s}", .{
|
d.addFmt(.err, cd.value.span, "type mismatch: constant '{s}' is declared '{s}' but its initializer is {s}", .{
|
||||||
cd.name, self.formatTypeName(ty), self.initializerDescription(cd.value),
|
cd.name, self.formatTypeName(ty), self.initializerDescription(cd.value),
|
||||||
@@ -1090,8 +1102,7 @@ pub const Lowering = struct {
|
|||||||
.float_literal => |fl| blk: {
|
.float_literal => |fl| blk: {
|
||||||
if (self.isIntEx(var_ty)) {
|
if (self.isIntEx(var_ty)) {
|
||||||
if (program_index_mod.floatToIntExact(fl.value)) |iv| break :blk inst_mod.ConstantValue{ .int = iv };
|
if (program_index_mod.floatToIntExact(fl.value)) |iv| break :blk inst_mod.ConstantValue{ .int = iv };
|
||||||
if (self.diagnostics) |d|
|
self.diagNonIntegralNarrow(v.span, fl.value, var_ty);
|
||||||
d.addFmt(.err, v.span, "cannot implicitly narrow non-integral float '{d}' to '{s}'; use an explicit cast (`xx`/`cast`)", .{ fl.value, self.formatTypeName(var_ty) });
|
|
||||||
break :blk null;
|
break :blk null;
|
||||||
}
|
}
|
||||||
break :blk inst_mod.ConstantValue{ .float = fl.value };
|
break :blk inst_mod.ConstantValue{ .float = fl.value };
|
||||||
@@ -1208,8 +1219,7 @@ pub const Lowering = struct {
|
|||||||
.float_literal => |fl| blk: {
|
.float_literal => |fl| blk: {
|
||||||
if (self.isIntEx(expected_ty)) {
|
if (self.isIntEx(expected_ty)) {
|
||||||
if (program_index_mod.floatToIntExact(fl.value)) |iv| break :blk inst_mod.ConstantValue{ .int = iv };
|
if (program_index_mod.floatToIntExact(fl.value)) |iv| break :blk inst_mod.ConstantValue{ .int = iv };
|
||||||
if (self.diagnostics) |d|
|
self.diagNonIntegralNarrow(expr.span, fl.value, expected_ty);
|
||||||
d.addFmt(.err, expr.span, "cannot implicitly narrow non-integral float '{d}' to '{s}'; use an explicit cast (`xx`/`cast`)", .{ fl.value, self.formatTypeName(expected_ty) });
|
|
||||||
break :blk null;
|
break :blk null;
|
||||||
}
|
}
|
||||||
break :blk inst_mod.ConstantValue{ .float = fl.value };
|
break :blk inst_mod.ConstantValue{ .float = fl.value };
|
||||||
@@ -2044,6 +2054,17 @@ pub const Lowering = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A compile-time float initializer narrowing into an integer
|
||||||
|
// local follows the unified rule (integral folds, non-integral
|
||||||
|
// errors); a runtime float / `xx` cast falls through to the
|
||||||
|
// normal lower+coerce below.
|
||||||
|
if (self.foldComptimeFloatInit(val, ty)) |folded| {
|
||||||
|
self.builder.store(slot, folded);
|
||||||
|
if (self.scope) |scope| {
|
||||||
|
scope.put(vd.name, .{ .ref = slot, .ty = ty, .is_alloca = true });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const saved_target = self.target_type;
|
const saved_target = self.target_type;
|
||||||
const saved_fbv = self.force_block_value;
|
const saved_fbv = self.force_block_value;
|
||||||
self.target_type = ty;
|
self.target_type = ty;
|
||||||
@@ -4649,16 +4670,11 @@ pub const Lowering = struct {
|
|||||||
// Field not specified — use default if available, else zero
|
// Field not specified — use default if available, else zero
|
||||||
if (fi < field_defaults.len) {
|
if (fi < field_defaults.len) {
|
||||||
if (field_defaults[fi]) |default_expr| {
|
if (field_defaults[fi]) |default_expr| {
|
||||||
const saved_tt = self.target_type;
|
|
||||||
self.target_type = sf.ty;
|
|
||||||
const raw = self.lowerExpr(default_expr);
|
|
||||||
self.target_type = saved_tt;
|
|
||||||
// Coerce the default to the field type at the IR
|
// Coerce the default to the field type at the IR
|
||||||
// level (the implicit narrowing rule) so a float
|
// level (the implicit narrowing rule) so a float
|
||||||
// default folds/errors here instead of being
|
// default folds/errors here instead of being
|
||||||
// silently bit-coerced by the backend.
|
// silently bit-coerced by the backend.
|
||||||
const val = self.coerceToType(raw, self.builder.getRefType(raw), sf.ty);
|
fields.append(self.alloc, self.lowerCoercedDefault(default_expr, sf.ty)) catch unreachable;
|
||||||
fields.append(self.alloc, val) catch unreachable;
|
|
||||||
} else {
|
} else {
|
||||||
fields.append(self.alloc, self.zeroValue(sf.ty)) catch unreachable;
|
fields.append(self.alloc, self.zeroValue(sf.ty)) catch unreachable;
|
||||||
}
|
}
|
||||||
@@ -4694,12 +4710,7 @@ pub const Lowering = struct {
|
|||||||
for (struct_fields[fields.items.len..], fields.items.len..) |sf, fi| {
|
for (struct_fields[fields.items.len..], fields.items.len..) |sf, fi| {
|
||||||
if (fi < field_defaults.len) {
|
if (fi < field_defaults.len) {
|
||||||
if (field_defaults[fi]) |default_expr| {
|
if (field_defaults[fi]) |default_expr| {
|
||||||
const saved_tt = self.target_type;
|
fields.append(self.alloc, self.lowerCoercedDefault(default_expr, sf.ty)) catch unreachable;
|
||||||
self.target_type = sf.ty;
|
|
||||||
const raw = self.lowerExpr(default_expr);
|
|
||||||
self.target_type = saved_tt;
|
|
||||||
const val = self.coerceToType(raw, self.builder.getRefType(raw), sf.ty);
|
|
||||||
fields.append(self.alloc, val) catch unreachable;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7030,6 +7041,17 @@ pub const Lowering = struct {
|
|||||||
if (enum_payload_ty) |ept| {
|
if (enum_payload_ty) |ept| {
|
||||||
if (ai == 0) self.target_type = ept;
|
if (ai == 0) self.target_type = ept;
|
||||||
}
|
}
|
||||||
|
// Implicit float→int narrowing of a compile-time float argument
|
||||||
|
// (incl. an expanded `param: T = expr` default) follows the unified
|
||||||
|
// rule: an integral comptime float folds, a non-integral one errors.
|
||||||
|
// A runtime float / `xx` cast is unaffected and coerces as before.
|
||||||
|
if (ai < param_types.len) {
|
||||||
|
if (self.foldComptimeFloatInit(arg, param_types[ai])) |folded| {
|
||||||
|
args.append(self.alloc, folded) catch unreachable;
|
||||||
|
self.target_type = saved_target;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Implicit address-of: when param expects *T and arg is an identifier
|
// Implicit address-of: when param expects *T and arg is an identifier
|
||||||
// with an alloca of type T, pass the alloca pointer directly (reference
|
// with an alloca of type T, pass the alloca pointer directly (reference
|
||||||
// semantics, so mutations through the pointer are visible to the caller).
|
// semantics, so mutations through the pointer are visible to the caller).
|
||||||
@@ -14401,12 +14423,7 @@ pub const Lowering = struct {
|
|||||||
for (fields, 0..) |f, i| {
|
for (fields, 0..) |f, i| {
|
||||||
if (i < field_defaults.len) {
|
if (i < field_defaults.len) {
|
||||||
if (field_defaults[i]) |default_expr| {
|
if (field_defaults[i]) |default_expr| {
|
||||||
const saved_tt = self.target_type;
|
field_vals.append(self.alloc, self.lowerCoercedDefault(default_expr, f.ty)) catch unreachable;
|
||||||
self.target_type = f.ty;
|
|
||||||
const raw = self.lowerExpr(default_expr);
|
|
||||||
self.target_type = saved_tt;
|
|
||||||
const val = self.coerceToType(raw, self.builder.getRefType(raw), f.ty);
|
|
||||||
field_vals.append(self.alloc, val) catch unreachable;
|
|
||||||
} else {
|
} else {
|
||||||
field_vals.append(self.alloc, self.zeroValue(f.ty)) catch unreachable;
|
field_vals.append(self.alloc, self.zeroValue(f.ty)) catch unreachable;
|
||||||
}
|
}
|
||||||
@@ -14551,6 +14568,65 @@ pub const Lowering = struct {
|
|||||||
return self.emitPlaceholder(field);
|
return self.emitPlaceholder(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit the unified non-integral float→int narrowing diagnostic (F0.11 /
|
||||||
|
/// issue 0095). ONE wording, ONE place: every site that rejects an implicit
|
||||||
|
/// narrowing of a non-integral compile-time float to an integer type calls
|
||||||
|
/// this, so the message + fix-it stay identical across the typed-binding
|
||||||
|
/// coerce arm, the field/param-default sites, the typed-const path, and the
|
||||||
|
/// global-initializer path.
|
||||||
|
fn diagNonIntegralNarrow(self: *Lowering, span: ast.Span, value: f64, dst_ty: TypeId) void {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "cannot implicitly narrow non-integral float '{d}' to '{s}'; use an explicit cast (`xx`/`cast`)", .{ value, self.formatTypeName(dst_ty) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the unified float→int narrowing rule to a typed-binding initializer
|
||||||
|
/// EXPRESSION `node` whose declared type is `dst` (a typed local, a struct
|
||||||
|
/// field default, or a call argument incl. an expanded param default). When
|
||||||
|
/// `node` is a COMPILE-TIME float narrowing into an integer type:
|
||||||
|
/// - an INTEGRAL value (`4.0`, `M + 2.0`) folds to its `constInt`;
|
||||||
|
/// - a NON-integral value (`1.5`, `M + 0.5`) emits the narrowing
|
||||||
|
/// diagnostic and returns a placeholder so lowering finishes.
|
||||||
|
/// Returns null — so the caller lowers `node` normally — when the rule does
|
||||||
|
/// not apply: `dst` is not an integer, `node` is not statically float-typed,
|
||||||
|
/// or `node` is not a compile-time constant (a genuine runtime float keeps
|
||||||
|
/// truncating, and `xx` / `cast` keep their explicit-truncation escape since
|
||||||
|
/// a cast node's inferred type is the destination integer, not a float).
|
||||||
|
/// Reuses `program_index.evalConstIntExpr` (exact integral fold) +
|
||||||
|
/// `evalConstFloatExpr` (non-integral detection) + `floatToIntExact`.
|
||||||
|
fn foldComptimeFloatInit(self: *Lowering, node: *const Node, dst: TypeId) ?Ref {
|
||||||
|
if (!self.isIntEx(dst)) return null;
|
||||||
|
// PURE & side-effect-free, so it runs FIRST: a runtime / non-comptime /
|
||||||
|
// non-numeric node — incl. a `$pack[i]` index expression — folds to null
|
||||||
|
// and is left to the normal path untouched. (Calling `inferExprType` on
|
||||||
|
// a pack-index value before this guard would spuriously resolve the
|
||||||
|
// enclosing pack type outside an active binding.)
|
||||||
|
const fv = program_index_mod.evalConstFloatExpr(node, self) orelse return null;
|
||||||
|
// Only a FLOAT-flavored initializer narrows here; a plain comptime int
|
||||||
|
// (`5`, `M + 2`) is left to the normal integer path. Safe to infer now —
|
||||||
|
// `evalConstFloatExpr` only succeeds for literal / const-arithmetic
|
||||||
|
// nodes, never an unbound pack index.
|
||||||
|
if (!isFloat(self.inferExprType(node))) return null;
|
||||||
|
// Integral comptime float folds to its int (`floatToIntExact`, the same
|
||||||
|
// facility the array-dim / `$K: Count` paths use); a non-integral one is
|
||||||
|
// the narrowing error.
|
||||||
|
if (program_index_mod.floatToIntExact(fv)) |iv| return self.builder.constInt(iv, dst);
|
||||||
|
self.diagNonIntegralNarrow(node.span, fv, dst);
|
||||||
|
return self.builder.constInt(0, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lower a struct field default `default_expr`, coerced to the field type
|
||||||
|
/// `field_ty`. A compile-time float default narrowing into an integer field
|
||||||
|
/// follows the unified rule via `foldComptimeFloatInit`; everything else
|
||||||
|
/// lowers under the field type as target and coerces at the IR level.
|
||||||
|
fn lowerCoercedDefault(self: *Lowering, default_expr: *const Node, field_ty: TypeId) Ref {
|
||||||
|
if (self.foldComptimeFloatInit(default_expr, field_ty)) |folded| return folded;
|
||||||
|
const saved_tt = self.target_type;
|
||||||
|
self.target_type = field_ty;
|
||||||
|
const raw = self.lowerExpr(default_expr);
|
||||||
|
self.target_type = saved_tt;
|
||||||
|
return self.coerceToType(raw, self.builder.getRefType(raw), field_ty);
|
||||||
|
}
|
||||||
|
|
||||||
/// How a float→int conversion is treated. An IMPLICIT coercion (a typed
|
/// How a float→int conversion is treated. An IMPLICIT coercion (a typed
|
||||||
/// binding initializer) folds an integral compile-time float to its int and
|
/// binding initializer) folds an integral compile-time float to its int and
|
||||||
/// REJECTS a non-integral one; an EXPLICIT `xx` / `cast` always truncates.
|
/// REJECTS a non-integral one; an EXPLICIT `xx` / `cast` always truncates.
|
||||||
@@ -14657,12 +14733,10 @@ pub const Lowering = struct {
|
|||||||
if (program_index_mod.floatToIntExact(info.value)) |iv| {
|
if (program_index_mod.floatToIntExact(info.value)) |iv| {
|
||||||
return self.builder.constInt(iv, dst_ty);
|
return self.builder.constInt(iv, dst_ty);
|
||||||
}
|
}
|
||||||
if (self.diagnostics) |d| {
|
// Non-integral: diagnose, then fall through to the
|
||||||
const sp = ast.Span{ .start = info.span.start, .end = info.span.end };
|
// truncating op below so lowering finishes and
|
||||||
d.addFmt(.err, sp, "cannot implicitly narrow non-integral float '{d}' to '{s}'; use an explicit cast (`xx`/`cast`)", .{ info.value, self.formatTypeName(dst_ty) });
|
// `hasErrors()` aborts the build.
|
||||||
}
|
self.diagNonIntegralNarrow(.{ .start = info.span.start, .end = info.span.end }, info.value, dst_ty);
|
||||||
// Error already emitted; emit the truncating op so
|
|
||||||
// lowering finishes and `hasErrors()` aborts the build.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.builder.emit(.{ .float_to_int = .{ .operand = val, .from = src_ty, .to = dst_ty } }, dst_ty);
|
return self.builder.emit(.{ .float_to_int = .{ .operand = val, .from = src_ty, .to = dst_ty } }, dst_ty);
|
||||||
|
|||||||
@@ -315,3 +315,43 @@ test "evalConstIntExpr folds an integral float literal, halts on a fractional on
|
|||||||
try std.testing.expectEqual(@as(?i64, 5), eval(&add, ctx));
|
try std.testing.expectEqual(@as(?i64, 5), eval(&add, ctx));
|
||||||
try std.testing.expect(eval(&addbad, ctx) == null);
|
try std.testing.expect(eval(&addbad, ctx) == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "evalConstFloatExpr folds comptime float expressions, halts on runtime leaves" {
|
||||||
|
const eval = pi.evalConstFloatExpr;
|
||||||
|
const ctx = DimCtx{}; // M = 4, N = 6
|
||||||
|
|
||||||
|
var half = nFloat(0.5);
|
||||||
|
var two_f = nFloat(2.0);
|
||||||
|
var m = nIdent("M");
|
||||||
|
var z = nIdent("Z"); // unbound — genuinely runtime
|
||||||
|
|
||||||
|
// Leaves: a float literal is itself; an int leaf delegates to the int folder
|
||||||
|
// and promotes (`M` → 4.0); an unbound name is not a compile-time float.
|
||||||
|
try std.testing.expectEqual(@as(?f64, 0.5), eval(&half, ctx));
|
||||||
|
try std.testing.expectEqual(@as(?f64, 4.0), eval(&m, ctx));
|
||||||
|
try std.testing.expect(eval(&z, ctx) == null);
|
||||||
|
|
||||||
|
// Mixed int+float arithmetic promotes to f64, order-independent
|
||||||
|
// (`M + 0.5` and `0.5 + M` → 4.5). `M + 2.0` is integral (6.0) but still a
|
||||||
|
// float value here — `floatToIntExact` is what the narrowing rule applies.
|
||||||
|
var mp = nBin(.add, &m, &half);
|
||||||
|
var pm = nBin(.add, &half, &m);
|
||||||
|
var mi = nBin(.add, &m, &two_f);
|
||||||
|
try std.testing.expectEqual(@as(?f64, 4.5), eval(&mp, ctx));
|
||||||
|
try std.testing.expectEqual(@as(?f64, 4.5), eval(&pm, ctx));
|
||||||
|
try std.testing.expectEqual(@as(?f64, 6.0), eval(&mi, ctx));
|
||||||
|
|
||||||
|
// Unary negate of a float expression.
|
||||||
|
var neg = nNeg(&mp);
|
||||||
|
try std.testing.expectEqual(@as(?f64, -4.5), eval(&neg, ctx));
|
||||||
|
|
||||||
|
// A runtime operand poisons the whole fold; a non-arithmetic operator and a
|
||||||
|
// float division by zero are not compile-time float leaves → null.
|
||||||
|
var zp = nBin(.add, &z, &half);
|
||||||
|
var cmp = nBin(.lt, &m, &half);
|
||||||
|
var zero_f = nFloat(0.0);
|
||||||
|
var divz = nBin(.div, &half, &zero_f);
|
||||||
|
try std.testing.expect(eval(&zp, ctx) == null);
|
||||||
|
try std.testing.expect(eval(&cmp, ctx) == null);
|
||||||
|
try std.testing.expect(eval(&divz, ctx) == null);
|
||||||
|
}
|
||||||
|
|||||||
@@ -218,6 +218,48 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile-time FLOAT value of a numeric expression, or null when it is not a
|
||||||
|
/// compile-time constant (some leaf is a runtime value) or is not a numeric
|
||||||
|
/// shape. THE float counterpart to `evalConstIntExpr`, used by the unified
|
||||||
|
/// float→int narrowing rule to (1) tell a compile-time float initializer apart
|
||||||
|
/// from a runtime one and (2) recover its value for `floatToIntExact` (integral
|
||||||
|
/// → fold) / the non-integral diagnostic.
|
||||||
|
///
|
||||||
|
/// An all-integer-foldable subtree is delegated to `evalConstIntExpr` (so module
|
||||||
|
/// / comptime consts, `<IntType>.min`/`.max`, and integer arithmetic resolve
|
||||||
|
/// through the SINGLE int folder — no parallel integer logic here); only the
|
||||||
|
/// genuinely float-producing shapes — a float literal, a unary negate, and
|
||||||
|
/// `+ - * /` arithmetic involving a float — are evaluated here in `f64`. A `%`,
|
||||||
|
/// comparison, or any other shape is not a compile-time float leaf → null.
|
||||||
|
pub fn evalConstFloatExpr(node: *const Node, ctx: anytype) ?f64 {
|
||||||
|
// Delegate any integer-foldable subtree (incl. an INTEGRAL float like `4.0`
|
||||||
|
// / `M + 2.0`) to the single int folder, then promote — keeps named consts
|
||||||
|
// and `.min`/`.max` resolution in one place.
|
||||||
|
if (evalConstIntExpr(node, ctx)) |iv| return @floatFromInt(iv);
|
||||||
|
return switch (node.data) {
|
||||||
|
.float_literal => |lit| lit.value,
|
||||||
|
.unary_op => |u| switch (u.op) {
|
||||||
|
.negate => {
|
||||||
|
const v = evalConstFloatExpr(u.operand, ctx) orelse return null;
|
||||||
|
return -v;
|
||||||
|
},
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
.binary_op => |b| {
|
||||||
|
const l = evalConstFloatExpr(b.lhs, ctx) orelse return null;
|
||||||
|
const r = evalConstFloatExpr(b.rhs, ctx) orelse return null;
|
||||||
|
return switch (b.op) {
|
||||||
|
.add => l + r,
|
||||||
|
.sub => l - r,
|
||||||
|
.mul => l * r,
|
||||||
|
.div => if (r == 0.0) null else l / r,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The outcome of folding a comptime-int and narrowing it to a `u32` count
|
/// The outcome of folding a comptime-int and narrowing it to a `u32` count
|
||||||
/// (array dimension / Vector lane / value-param count). `foldDimU32` is the
|
/// (array dimension / Vector lane / value-param count). `foldDimU32` is the
|
||||||
/// SINGLE place a folded integer becomes a `u32`, so the i64→u32 narrowing is
|
/// SINGLE place a folded integer becomes a `u32`, so the i64→u32 narrowing is
|
||||||
|
|||||||
Reference in New Issue
Block a user