fix(ir): value-param type functions + range-checked dim/lane fold (0083, 0087)
Two remaining siblings in F0.4's comptime-int path.
1. Type-returning function with a value param used as a TYPE annotation
(`b : Make(N, s64)` where `Make :: ($K: u32, $T: Type) -> Type`):
- `isValueParamPosition` (semantic_diagnostics) now also skips a value
param of a `fn_ast_map` type-returning function, so `N` is not walked
as the type name "N" ("unknown type 'N'").
- `resolveParameterizedWithBindings` routes a type-returning-function
name to `instantiateTypeFunction` (the `.call` path already did).
- `instantiateTypeFunction` resolves a general return-type expression
(`return [K]T`) with bindings active — not just struct/union returns.
`Make(N, s64)`, `Make(M + 1, s64)`, `Make(3, s64)` all resolve to one
`[3]s64`.
2. Oversized dim/lane fold panicked the compiler (0087): an array dim /
Vector lane folded to a valid i64 (5e9) then narrowed to u32 with an
unchecked `@intCast`. New single gate `program_index.foldDimU32` folds
via `evalConstIntExpr` then range-checks `[min, maxInt(u32)]`; the three
narrowing sites (resolveArrayLen stateful + stateless, resolveVectorLane)
all route through it and emit a clean diagnostic + halt instead of
panicking. Value-param args stay i64 until used as a dim/lane, where the
same gate checks them.
Regressions: examples/0208 (value-param type function), examples/1130
(oversized array dim clean halt), examples/1503 (oversized Vector lane
clean halt). Marks issue 0087 RESOLVED.
Gate: zig build, zig build test, bash tests/run_examples.sh — 398 passed,
0 failed, 0 timed out.
This commit is contained in:
32
examples/0208-generics-value-param-type-function.sx
Normal file
32
examples/0208-generics-value-param-type-function.sx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// A type-RETURNING function with a value parameter (`$K: u32`) used as a TYPE
|
||||||
|
// annotation: `b : Make(N, s64)` where `Make :: ($K, $T) -> Type { return [K]T; }`.
|
||||||
|
// A named-const value arg (`Make(N, s64)`), a const-expression value arg
|
||||||
|
// (`Make(M + 1, s64)`), and the literal form (`Make(3, s64)`) all instantiate to
|
||||||
|
// the SAME type — the array copy `b : Make(3, s64) = a` type-checks only because
|
||||||
|
// the three spellings name one `[3]s64`.
|
||||||
|
//
|
||||||
|
// Regression (issue 0083 / F0.4 attempt 6): the unknown-type checker walked the
|
||||||
|
// value-param position as a type name ("unknown type 'N'"), and the
|
||||||
|
// parameterized-type-annotation path never routed to `instantiateTypeFunction`,
|
||||||
|
// nor did that binder resolve a non-struct/union return shape (`return [K]T`).
|
||||||
|
// The value arg now folds through the shared const-int evaluator and the type
|
||||||
|
// function resolves its general return-type expression with bindings active.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
N :: 3;
|
||||||
|
M :: 2;
|
||||||
|
|
||||||
|
Make :: ($K: u32, $T: Type) -> Type { return [K]T; }
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
a : Make(N, s64) = ---; // named-const value param
|
||||||
|
a[0] = 10; a[1] = 20; a[2] = 30;
|
||||||
|
print("named: len={} a0={} a2={}\n", a.len, a[0], a[2]);
|
||||||
|
|
||||||
|
e : Make(M + 1, s64) = ---; // const-expr value param (M + 1 == 3)
|
||||||
|
e[0] = 1; e[2] = 9;
|
||||||
|
print("expr: len={} e2={}\n", e.len, e[2]);
|
||||||
|
|
||||||
|
b : Make(3, s64) = a; // same instantiation → array copy type-checks
|
||||||
|
print("copy: len={} b2={}\n", b.len, b[2]);
|
||||||
|
}
|
||||||
15
examples/1130-diagnostics-array-dim-oversized-u32.sx
Normal file
15
examples/1130-diagnostics-array-dim-oversized-u32.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// An array dimension that folds to a valid compile-time integer but exceeds a
|
||||||
|
// `u32` (`[5_000_000_000]s64`) is a hard error — a clean sx diagnostic with a
|
||||||
|
// non-zero exit, NOT a compiler panic.
|
||||||
|
//
|
||||||
|
// Regression (issue 0087 / F0.4 attempt 6): the dimension folded to a valid i64
|
||||||
|
// (5e9) and was then narrowed with an unchecked `@intCast` to u32, aborting the
|
||||||
|
// COMPILER with "integer does not fit in destination type". Every dim/lane fold
|
||||||
|
// now narrows through the single range-checked `program_index.foldDimU32`, which
|
||||||
|
// reports an out-of-u32-range dimension as this diagnostic and halts the build.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
a : [5000000000]s64 = ---;
|
||||||
|
print("unreachable: {}\n", a.len);
|
||||||
|
}
|
||||||
15
examples/1503-vectors-oversized-lane-not-u32.sx
Normal file
15
examples/1503-vectors-oversized-lane-not-u32.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// A `Vector` lane count that folds to a valid compile-time integer but exceeds a
|
||||||
|
// `u32` (`Vector(5_000_000_000, f32)`) is a hard error — a clean sx diagnostic
|
||||||
|
// with a non-zero exit, NOT a compiler panic.
|
||||||
|
//
|
||||||
|
// Regression (issue 0087 / F0.4 attempt 6): the lane folded to a valid i64 (5e9)
|
||||||
|
// and was then narrowed with an unchecked `@intCast` to u32, aborting the
|
||||||
|
// COMPILER with "integer does not fit in destination type". The lane now narrows
|
||||||
|
// through the single range-checked `program_index.foldDimU32`, which reports an
|
||||||
|
// out-of-u32-range lane as this diagnostic and halts the build.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
v : Vector(5000000000, f32) = ---;
|
||||||
|
print("unreachable: {}\n", v.x);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
named: len=3 a0=10 a2=30
|
||||||
|
expr: len=3 e2=9
|
||||||
|
copy: len=3 b2=30
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: array dimension 5000000000 does not fit in u32
|
||||||
|
--> examples/1130-diagnostics-array-dim-oversized-u32.sx:13:10
|
||||||
|
|
|
||||||
|
13 | a : [5000000000]s64 = ---;
|
||||||
|
| ^^^^^^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
11
examples/expected/1503-vectors-oversized-lane-not-u32.stderr
Normal file
11
examples/expected/1503-vectors-oversized-lane-not-u32.stderr
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: Vector lane count 5000000000 does not fit in u32
|
||||||
|
--> examples/1503-vectors-oversized-lane-not-u32.sx:13:16
|
||||||
|
|
|
||||||
|
13 | v : Vector(5000000000, f32) = ---;
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
error: field 'x' not found on type 'unresolved'
|
||||||
|
--> examples/1503-vectors-oversized-lane-not-u32.sx:14:32
|
||||||
|
|
|
||||||
|
14 | print("unreachable: {}\n", v.x);
|
||||||
|
| ^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -103,6 +103,25 @@
|
|||||||
> resolve to the same instantiation as `Vec(3,f32)`),
|
> resolve to the same instantiation as `Vec(3,f32)`),
|
||||||
> `examples/0610-comptime-inline-for-const-bound.sx` (`inline for 0..M` and
|
> `examples/0610-comptime-inline-for-const-bound.sx` (`inline for 0..M` and
|
||||||
> `0..(M+1)` unroll).
|
> `0..(M+1)` unroll).
|
||||||
|
>
|
||||||
|
> **Value-param type functions + oversized guard (attempt 6).** Two remaining
|
||||||
|
> siblings in the comptime-int path. (1) A type-RETURNING function with a value
|
||||||
|
> param used as a TYPE annotation (`b : Make(N, s64)` where `Make :: ($K: u32,
|
||||||
|
> $T: Type) -> Type { return [K]T; }`) was rejected "unknown type 'N'" because
|
||||||
|
> the unknown-type checker walked the value-param position as a type name, AND the
|
||||||
|
> parameterized-type-annotation path never routed to `instantiateTypeFunction`
|
||||||
|
> (only the `.call` path did), nor did that binder resolve a non-struct/union
|
||||||
|
> return shape. Fix: `isValueParamPosition` (semantic_diagnostics.zig) now also
|
||||||
|
> skips a value param of a `fn_ast_map` type-returning function (mirroring the
|
||||||
|
> binder's value/type classification); `resolveParameterizedWithBindings` routes
|
||||||
|
> a type-returning-function name to `instantiateTypeFunction`; and that binder
|
||||||
|
> resolves a general return-type expression (`return [K]T`) with bindings active.
|
||||||
|
> `Make(N, s64)`, `Make(M + 1, s64)`, and `Make(3, s64)` now resolve to one
|
||||||
|
> `[3]s64`. (2) Oversized dim/lane folds (`[5_000_000_000]`) panicked the
|
||||||
|
> compiler — fixed under issue 0087 via the shared range-checked
|
||||||
|
> `program_index.foldDimU32` gate. Files: `src/ir/semantic_diagnostics.zig`,
|
||||||
|
> `src/ir/lower.zig`, `src/ir/program_index.zig`, `src/ir/type_bridge.zig`.
|
||||||
|
> Regression: `examples/0208-generics-value-param-type-function.sx`.
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
A fixed array whose dimension is a module-global integer constant (`N :: 16;
|
A fixed array whose dimension is a module-global integer constant (`N :: 16;
|
||||||
|
|||||||
64
issues/0087-oversized-comptime-int-dimension-panics.md
Normal file
64
issues/0087-oversized-comptime-int-dimension-panics.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# 0087 - oversized compile-time integer in type dimension/lane panics
|
||||||
|
|
||||||
|
> **RESOLVED.** Root cause: an array dimension / Vector lane folded to a valid
|
||||||
|
> `i64` (e.g. `5_000_000_000`) was then narrowed to `u32` with an unchecked
|
||||||
|
> `@intCast` at three sites (`Lowering.resolveArrayLen` lower.zig:11656,
|
||||||
|
> `Lowering.resolveVectorLane` lower.zig:11836, `StatelessInner.resolveArrayLen`
|
||||||
|
> type_bridge.zig:58), aborting the COMPILER with "integer does not fit in
|
||||||
|
> destination type" — the fold was correct, the narrowing was not. Fix: a single
|
||||||
|
> range-checked fold-to-u32 gate, `program_index.foldDimU32(node, ctx, min)`,
|
||||||
|
> folds via `evalConstIntExpr` then checks `[min, maxInt(u32)]` and returns a
|
||||||
|
> tagged `DimU32` (`.ok` / `.not_const` / `.below_min` / `.too_large`). Every
|
||||||
|
> dim/lane narrowing now routes through it — no call site does a bare `@intCast`,
|
||||||
|
> so an out-of-u32-range dim/lane surfaces a clean diagnostic ("array dimension N
|
||||||
|
> does not fit in u32" / "Vector lane count N does not fit in u32") and halts the
|
||||||
|
> build (exit 1) instead of panicking. Value-param args stay `i64` until used as
|
||||||
|
> a dim/lane, where the same gate checks them. Files: `src/ir/program_index.zig`
|
||||||
|
> (`DimU32` + `foldDimU32`), `src/ir/lower.zig`, `src/ir/type_bridge.zig`.
|
||||||
|
> Regressions: `examples/1130-diagnostics-array-dim-oversized-u32.sx` (oversized
|
||||||
|
> array dim → clean halt) and `examples/1503-vectors-oversized-lane-not-u32.sx`
|
||||||
|
> (oversized Vector lane → clean halt).
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
An oversized compile-time integer used where the compiler expects a `u32`
|
||||||
|
array dimension or Vector lane count panics inside the compiler instead of
|
||||||
|
emitting a source diagnostic.
|
||||||
|
|
||||||
|
Observed: `@intCast` panics with "integer does not fit in destination type" in
|
||||||
|
`Lowering.resolveArrayLen` / `Lowering.resolveVectorLane`.
|
||||||
|
|
||||||
|
Expected: compile halts with a normal diagnostic such as "array dimension does
|
||||||
|
not fit in u32" / "Vector lane count must fit in u32", and no compiler panic.
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
a : [5000000000]s64 = ---;
|
||||||
|
print("{}\n", a.len);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Vector lane sibling:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
v : Vector(5000000000, f32) = .[];
|
||||||
|
print("{}\n", v);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Investigation prompt
|
||||||
|
Fix oversized compile-time integer handling for fixed-array dimensions and
|
||||||
|
Vector lane counts. Suspected area: `src/ir/lower.zig`
|
||||||
|
`Lowering.resolveArrayLen` and `Lowering.resolveVectorLane`, plus the stateless
|
||||||
|
adapter in `src/ir/type_bridge.zig` `StatelessInner.resolveArrayLen`. These
|
||||||
|
functions fold to `i64` through `program_index.evalConstIntExpr`, then cast to
|
||||||
|
`u32` with `@intCast`; values greater than `std.math.maxInt(u32)` panic in the
|
||||||
|
compiler. The fix likely needs an explicit range check before every `u32` cast,
|
||||||
|
with a diagnostic on the stateful path and null / `.unresolved` propagation on
|
||||||
|
the stateless path. Verify both repros exit 1 with source diagnostics and no
|
||||||
|
panic, then run `zig build && zig build test && bash tests/run_examples.sh`.
|
||||||
126
src/ir/lower.zig
126
src/ir/lower.zig
@@ -11639,37 +11639,36 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
/// Fixed-array dimension hook for `TypeResolver.resolveCompound`. A literal
|
/// Fixed-array dimension hook for `TypeResolver.resolveCompound`. A literal
|
||||||
/// `[16]T` and a named-const `N :: 16; [N]T` must resolve to the SAME length:
|
/// `[16]T` and a named-const `N :: 16; [N]T` must resolve to the SAME length:
|
||||||
/// the dimension is a compile-time integer, looked up in the comptime / value
|
/// the dimension folds to a compile-time integer (looked up in the comptime /
|
||||||
/// / module-const tables the stateful lowering owns. A dimension that isn't a
|
/// value / module-const tables the stateful lowering owns) and is narrowed to
|
||||||
/// compile-time integer is a hard error: emit a diagnostic so the driver
|
/// `u32` through the single range-checked `program_index.foldDimU32` — never a
|
||||||
/// aborts (`hasErrors()`), then return a harmless `0` so body lowering
|
/// bare `@intCast`, so an oversized-but-valid `i64` dim (`[5_000_000_000]`)
|
||||||
/// finishes without touching the `.unresolved` sentinel (which would `@panic`
|
/// diagnoses instead of panicking the compiler (issue 0087). A dimension that
|
||||||
/// in `sizeOf` mid-lowering, before the diagnostic surfaces). The diagnostic —
|
/// isn't a compile-time integer (or doesn't fit a `u32`) is a hard error:
|
||||||
/// not the returned length — is what guarantees no garbage ships (issue 0083).
|
/// emit a diagnostic so the driver aborts (`hasErrors()`), then return a
|
||||||
|
/// harmless `0` so body lowering finishes without touching the `.unresolved`
|
||||||
|
/// sentinel (which would `@panic` in `sizeOf` mid-lowering, before the
|
||||||
|
/// diagnostic surfaces). The diagnostic — not the returned length — is what
|
||||||
|
/// guarantees no garbage ships (issue 0083).
|
||||||
pub fn resolveArrayLen(self: *Lowering, len_node: *const Node) ?u32 {
|
pub fn resolveArrayLen(self: *Lowering, len_node: *const Node) ?u32 {
|
||||||
if (self.comptimeArrayDim(len_node)) |n| {
|
switch (program_index_mod.foldDimU32(len_node, self, 0)) {
|
||||||
if (n < 0) {
|
.ok => |n| return n,
|
||||||
|
.below_min => |v| {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, len_node.span, "array dimension must be non-negative, got {}", .{n});
|
d.addFmt(.err, len_node.span, "array dimension must be non-negative, got {}", .{v});
|
||||||
return 0;
|
return 0;
|
||||||
}
|
},
|
||||||
return @intCast(n);
|
.too_large => |v| {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, len_node.span, "array dimension {} does not fit in u32", .{v});
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
.not_const => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, len_node.span, "array dimension must be a compile-time integer constant", .{});
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (self.diagnostics) |d|
|
|
||||||
d.addFmt(.err, len_node.span, "array dimension must be a compile-time integer constant", .{});
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate a fixed-array dimension to a compile-time integer: a literal, a
|
|
||||||
/// name bound to an integer (comptime-constant `OS`/loop cursors, generic
|
|
||||||
/// `$N` value, or module-global const `N :: 16`), or a constant-foldable
|
|
||||||
/// expression over those (`[M + 1]`, `[(M + 1) * 2]`). Delegates the
|
|
||||||
/// expression folding to the shared `program_index.evalConstIntExpr` so this
|
|
||||||
/// body-lowering path and the stateless registration path cannot diverge on
|
|
||||||
/// a dimension's value. Returns null when the dimension isn't a compile-time
|
|
||||||
/// integer.
|
|
||||||
fn comptimeArrayDim(self: *Lowering, node: *const Node) ?i64 {
|
|
||||||
return program_index_mod.evalConstIntExpr(node, self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Leaf-name lookup for the shared dimension evaluator: a name bound to a
|
/// Leaf-name lookup for the shared dimension evaluator: a name bound to a
|
||||||
@@ -11820,20 +11819,28 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a `Vector(N, T)` lane count to a positive compile-time integer
|
/// Resolve a `Vector(N, T)` lane count to a positive compile-time integer
|
||||||
/// through the shared `evalConstIntExpr` folder — so a literal (`Vector(4,
|
/// through the shared `program_index.foldDimU32` folder (min 1) — so a literal
|
||||||
/// f32)`), a module/generic const (`Vector(N, f32)`), and a const expression
|
/// (`Vector(4, f32)`), a module/generic const (`Vector(N, f32)`), and a const
|
||||||
/// (`Vector(M + 1, f32)`) all resolve identically. A non-const lane
|
/// expression (`Vector(M + 1, f32)`) all resolve identically, and the i64→u32
|
||||||
/// (`Vector(get(), f32)`) or a non-positive one emits a clean diagnostic and
|
/// narrowing is range-checked (an oversized lane diagnoses instead of
|
||||||
/// returns null; the caller yields `.unresolved` rather than fabricating a
|
/// panicking — issue 0087). A non-const lane (`Vector(get(), f32)`) or a
|
||||||
/// `<0 x float>` lane count that crashes LLVM verification.
|
/// non-positive one emits a clean diagnostic and returns null; the caller
|
||||||
|
/// yields `.unresolved` rather than fabricating a `<0 x float>` lane count
|
||||||
|
/// that crashes LLVM verification.
|
||||||
fn resolveVectorLane(self: *Lowering, lane_node: *const Node) ?u32 {
|
fn resolveVectorLane(self: *Lowering, lane_node: *const Node) ?u32 {
|
||||||
const v = program_index_mod.evalConstIntExpr(lane_node, self);
|
switch (program_index_mod.foldDimU32(lane_node, self, 1)) {
|
||||||
if (v == null or v.? < 1) {
|
.ok => |n| return n,
|
||||||
if (self.diagnostics) |d|
|
.too_large => |v| {
|
||||||
d.addFmt(.err, lane_node.span, "Vector lane count must be a positive compile-time integer constant", .{});
|
if (self.diagnostics) |d|
|
||||||
return null;
|
d.addFmt(.err, lane_node.span, "Vector lane count {} does not fit in u32", .{v});
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
.not_const, .below_min => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, lane_node.span, "Vector lane count must be a positive compile-time integer constant", .{});
|
||||||
|
return null;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return @intCast(v.?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a generic value-param argument (`$N: u32`) to its compile-time
|
/// Resolve a generic value-param argument (`$N: u32`) to its compile-time
|
||||||
@@ -11909,6 +11916,20 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User-defined type-returning function used as a TYPE annotation
|
||||||
|
// (`b : Make(N, s64)` where `Make :: ($K: u32, $T: Type) -> Type`). The
|
||||||
|
// `.call`-node path (`resolveTypeCallWithBindings`) already routes here;
|
||||||
|
// a `parameterized_type_expr` must too, or the function name falls through
|
||||||
|
// to the empty-struct stub below and `b.field` / `b.len` fails.
|
||||||
|
const resolved_name = if (self.scope) |scope| (scope.lookupFn(base_name) orelse base_name) else base_name;
|
||||||
|
if (self.program_index.fn_ast_map.get(resolved_name)) |fd| {
|
||||||
|
if (fd.type_params.len > 0) {
|
||||||
|
if (self.instantiateTypeFunction(base_name, base_name, fd, pt.args)) |ty| {
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: register as named type placeholder
|
// Fallback: register as named type placeholder
|
||||||
const name_id = table.internString(pt.name);
|
const name_id = table.internString(pt.name);
|
||||||
return table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
return table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
||||||
@@ -12141,9 +12162,36 @@ pub const Lowering = struct {
|
|||||||
return self.instantiateTypeUnion(if (has_alias) alias_name else mangled_name, mangled_name, &enum_decl);
|
return self.instantiateTypeUnion(if (has_alias) alias_name else mangled_name, mangled_name, &enum_decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// General case: the body returns a TYPE EXPRESSION that is not an inline
|
||||||
|
// struct/union/enum — `return [K]T`, `Vector(K, T)`, `*T`, an alias, etc.
|
||||||
|
// Resolve it with the value/type bindings active (so `[K]T` folds K to a
|
||||||
|
// compile-time integer). The result is interned structurally, so
|
||||||
|
// `Make(N, s64)`, `Make(3, s64)`, and `Make(M + 1, s64)` all yield the
|
||||||
|
// same TypeId. `.unresolved` means the return wasn't a type expression
|
||||||
|
// (e.g. a value-returning function in a type position) → fall through to
|
||||||
|
// the caller's fallback rather than fabricating a type.
|
||||||
|
if (findReturnTypeExpr(fd.body)) |ret_node| {
|
||||||
|
const ty = self.resolveTypeWithBindings(ret_node);
|
||||||
|
if (ty != .unresolved) return ty;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type expression a type-returning function yields: the value of its
|
||||||
|
/// `return` (block body) or the bare expression (arrow body / `=> [K]T`).
|
||||||
|
/// Used for a non-struct/union return shape, which the struct/union body
|
||||||
|
/// walkers above don't match.
|
||||||
|
fn findReturnTypeExpr(body: *const Node) ?*const Node {
|
||||||
|
if (body.data == .block) {
|
||||||
|
for (body.data.block.stmts) |stmt| {
|
||||||
|
if (stmt.data == .return_stmt) return stmt.data.return_stmt.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
/// Instantiate a tagged enum from a type function body.
|
/// Instantiate a tagged enum from a type function body.
|
||||||
fn instantiateTypeUnion(self: *Lowering, alias_name: []const u8, mangled_name: []const u8, ed: *const ast.EnumDecl) ?TypeId {
|
fn instantiateTypeUnion(self: *Lowering, alias_name: []const u8, mangled_name: []const u8, ed: *const ast.EnumDecl) ?TypeId {
|
||||||
const table = &self.module.types;
|
const table = &self.module.types;
|
||||||
|
|||||||
@@ -116,6 +116,37 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The outcome of folding a comptime-int and narrowing it to a `u32` count
|
||||||
|
/// (array dimension / Vector lane / value-param count). `foldDimU32` is the
|
||||||
|
/// SINGLE place a folded integer becomes a `u32`, so the i64→u32 narrowing is
|
||||||
|
/// range-checked exactly once and no call site does a bare `@intCast` that could
|
||||||
|
/// panic the compiler on a valid-but-oversized fold (a literal `5_000_000_000`
|
||||||
|
/// is a valid `i64` yet `> maxInt(u32)` — issue 0087). Each call site maps a
|
||||||
|
/// non-`.ok` variant onto its own clean diagnostic + `.unresolved` / abort.
|
||||||
|
pub const DimU32 = union(enum) {
|
||||||
|
/// Folded to a `u32` in `[min, maxInt(u32)]`.
|
||||||
|
ok: u32,
|
||||||
|
/// Not a compile-time integer (runtime value, unbound name, or overflow).
|
||||||
|
not_const,
|
||||||
|
/// Folded, but below the required minimum (a negative dim, a non-positive lane).
|
||||||
|
below_min: i64,
|
||||||
|
/// Folded, but greater than `maxInt(u32)` — too large for a `u32` count.
|
||||||
|
too_large: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Fold `node` to a `u32` count through `evalConstIntExpr`, then range-check
|
||||||
|
/// against `[min, maxInt(u32)]`. THE single fold-to-u32 for every array
|
||||||
|
/// dimension, Vector lane, and value-param count — routing all of them here
|
||||||
|
/// guarantees the narrowing is checked once and can never abort the compiler
|
||||||
|
/// (issue 0087). The fold itself stays in `i64`; only this one conversion is the
|
||||||
|
/// `u32` gate.
|
||||||
|
pub fn foldDimU32(node: *const Node, ctx: anytype, min: u32) DimU32 {
|
||||||
|
const v = evalConstIntExpr(node, ctx) orelse return .not_const;
|
||||||
|
if (v < @as(i64, min)) return .{ .below_min = v };
|
||||||
|
if (v > std.math.maxInt(u32)) return .{ .too_large = v };
|
||||||
|
return .{ .ok = @intCast(v) };
|
||||||
|
}
|
||||||
|
|
||||||
pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
|
pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
|
||||||
|
|
||||||
/// Single lowering access point for declaration-name / import / visibility
|
/// Single lowering access point for declaration-name / import / visibility
|
||||||
|
|||||||
@@ -617,12 +617,28 @@ pub const UnknownTypeChecker = struct {
|
|||||||
/// generic `$N: u32` arg), not a type. Such a position must be skipped by
|
/// generic `$N: u32` arg), not a type. Such a position must be skipped by
|
||||||
/// the unknown-type walk: a module-const arg (`Vector(N, f32)`) is a value,
|
/// the unknown-type walk: a module-const arg (`Vector(N, f32)`) is a value,
|
||||||
/// not a type name. `Vector`'s arg 0 is always its lane count; a generic
|
/// not a type name. `Vector`'s arg 0 is always its lane count; a generic
|
||||||
/// struct template's value-param positions come from its declared params.
|
/// struct template's value-param positions come from its declared params; a
|
||||||
|
/// type-RETURNING function (`Make :: ($K: u32, $T: Type) -> Type`) classifies
|
||||||
|
/// each param from its constraint, mirroring `instantiateTypeFunction` — so
|
||||||
|
/// `Make(N, s64)` (N a module const) is not walked as the type name "N".
|
||||||
fn isValueParamPosition(self: UnknownTypeChecker, base: []const u8, i: usize) bool {
|
fn isValueParamPosition(self: UnknownTypeChecker, base: []const u8, i: usize) bool {
|
||||||
if (std.mem.eql(u8, base, "Vector")) return i == 0;
|
if (std.mem.eql(u8, base, "Vector")) return i == 0;
|
||||||
if (self.index.struct_template_map.get(base)) |tmpl| {
|
if (self.index.struct_template_map.get(base)) |tmpl| {
|
||||||
if (i < tmpl.type_params.len) return !tmpl.type_params[i].is_type_param;
|
if (i < tmpl.type_params.len) return !tmpl.type_params[i].is_type_param;
|
||||||
}
|
}
|
||||||
|
if (self.index.fn_ast_map.get(base)) |fd| {
|
||||||
|
if (i < fd.type_params.len) {
|
||||||
|
const tp = fd.type_params[i];
|
||||||
|
// A value param is one whose constraint is a non-`Type` type
|
||||||
|
// expr (`$K: u32`); a `$T: Type` (or any non-type-expr
|
||||||
|
// constraint) is a type param — identical rule to the binder.
|
||||||
|
const is_type_param = if (tp.constraint.data == .type_expr)
|
||||||
|
std.mem.eql(u8, tp.constraint.data.type_expr.name, "Type")
|
||||||
|
else
|
||||||
|
true;
|
||||||
|
return !is_type_param;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,19 +43,23 @@ const StatelessInner = struct {
|
|||||||
/// Fixed-array dimension at registration time: a literal `[16]T`, a named
|
/// Fixed-array dimension at registration time: a literal `[16]T`, a named
|
||||||
/// module-global const `N :: 16; [N]T` (typed `N : s64 : 16` too), or a
|
/// module-global const `N :: 16; [N]T` (typed `N : s64 : 16` too), or a
|
||||||
/// constant-foldable expression over those (`[M + 1]`, `[(M + 1) * 2]`).
|
/// constant-foldable expression over those (`[M + 1]`, `[(M + 1) * 2]`).
|
||||||
/// Folds through the shared `program_index.evalConstIntExpr` — the SAME
|
/// Folds and narrows through the shared `program_index.foldDimU32` (min 0) —
|
||||||
/// evaluator the stateful body-lowering path uses — so a dimension resolves
|
/// the SAME range-checked fold-to-u32 the stateful body-lowering path uses —
|
||||||
/// to one length on every registration-time path (aliases, inline union/enum
|
/// so a dimension resolves to one length on every registration-time path
|
||||||
/// fields) and matches the direct form (issue 0083). Returns null when the
|
/// (aliases, inline union/enum fields) and matches the direct form (issue
|
||||||
/// dimension isn't a compile-time integer (a runtime value / non-comptime
|
/// 0083), and an oversized-but-valid `i64` dim returns null instead of
|
||||||
/// call, or a name not bound to an integer const). Null propagates to
|
/// panicking the `@intCast` (issue 0087). Returns null when the dimension
|
||||||
/// `resolveCompound`, which yields the `.unresolved` sentinel rather than
|
/// isn't a compile-time integer (a runtime value / non-comptime call, or a
|
||||||
/// fabricating a 0 length that silently gives a 0-byte array and
|
/// name not bound to an integer const), is negative, or doesn't fit a `u32`.
|
||||||
/// out-of-bounds element access; the registration caller surfaces the
|
/// Null propagates to `resolveCompound`, which yields the `.unresolved`
|
||||||
/// unresolved alias/type as a clean diagnostic.
|
/// sentinel rather than fabricating a 0 length that silently gives a 0-byte
|
||||||
|
/// array and out-of-bounds element access; the registration caller surfaces
|
||||||
|
/// the unresolved alias/type as a clean diagnostic.
|
||||||
pub fn resolveArrayLen(self: StatelessInner, len_node: *const Node) ?u32 {
|
pub fn resolveArrayLen(self: StatelessInner, len_node: *const Node) ?u32 {
|
||||||
const v = program_index_mod.evalConstIntExpr(len_node, self) orelse return null;
|
return switch (program_index_mod.foldDimU32(len_node, self, 0)) {
|
||||||
return if (v >= 0) @intCast(v) else null;
|
.ok => |n| n,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/// Leaf-name lookup for the shared dimension evaluator: a name that resolves
|
/// Leaf-name lookup for the shared dimension evaluator: a name that resolves
|
||||||
/// to a module-global integer constant → its value. Shares
|
/// to a module-global integer constant → its value. Shares
|
||||||
|
|||||||
Reference in New Issue
Block a user