Merge branch 'flow/sx-foundation/F0.4' into dist-foundation

This commit is contained in:
agra
2026-06-04 15:42:43 +03:00
134 changed files with 2553 additions and 169 deletions

View File

@@ -0,0 +1,69 @@
// A fixed array whose dimension is a module-global named constant
// (`N :: 16; [N]T`) has the same layout as a literal-dimension array
// (`[16]T`): correct length and element stride for scalar, slice/pointer
// (string), and struct element types — on EVERY type-resolution path:
// direct local decls, type aliases (`Arr :: [N]T`), nested fixed arrays
// (`[N][M]T`), and inline union fields. The named dim must resolve to the
// same length whether it flows through the stateful body-lowering resolver
// or the stateless registration-time resolver (type_bridge).
// Regression (issue 0083): a named-const dim resolved to length 0, giving a
// 0-byte alloca — scalar reads returned garbage and string/struct elements
// bus-errored. The alias and union-field paths went through the stateless
// resolver, which had no const table and silently fabricated a 0 length.
#import "modules/std.sx";
N :: 4;
M :: 3;
P :: struct { x: s64; y: s64; }
// Type aliases whose dimension is the named const N (stateless registration).
Arr :: [N]s64;
SArr :: [N]string;
// Inline union field with a named-const dimension (stateless registration).
U :: union { a: [N]s64; tag: s64; }
main :: () {
// Scalar elements (direct local): store then read back.
a : [N]s64 = ---;
a[0] = 7;
a[3] = 42;
print("scalar a0={} a3={}\n", a[0], a[3]);
// Slice/pointer elements (string, direct local): used to bus-error.
s : [N]string = ---;
s[0] = "hi";
s[1] = "yo";
print("string s0={} s1={}\n", s[0], s[1]);
// Struct elements (direct local).
ps : [N]P = ---;
ps[0] = P.{ x = 1, y = 2 };
ps[2] = P.{ x = 5, y = 6 };
print("struct p0x={} p0y={} p2x={}\n", ps[0].x, ps[0].y, ps[2].x);
// Type-alias dimension (scalar): same layout as the direct `[N]s64`.
aa : Arr = ---;
aa[0] = 11;
aa[3] = 99;
print("alias a0={} a3={}\n", aa[0], aa[3]);
// Type-alias dimension (string): no bus error, correct reads.
sa : SArr = ---;
sa[0] = "al";
sa[2] = "ok";
print("alias s0={} s2={}\n", sa[0], sa[2]);
// Nested fixed array `[N][M]s64`: both dimensions are named consts.
grid : [N][M]s64 = ---;
grid[0][0] = 1;
grid[3][2] = 8;
print("nested g00={} g32={}\n", grid[0][0], grid[3][2]);
// Inline union field with a named-const dimension.
u : U = ---;
u.a[0] = 70;
u.a[3] = 7;
print("union u0={} u3={}\n", u.a[0], u.a[3]);
}

View File

@@ -0,0 +1,34 @@
// A `.[...]` array/slice literal passed DIRECTLY as a call argument behaves
// identically to binding it to a typed local first: the literal is
// materialized into addressable storage and a {ptr,len} slice header is built
// over it, so the callee reads the element CONTENTS correctly.
// Regression (issue 0084): a direct literal arg passed the raw array value
// where a slice was expected, so the callee read its header off the wrong
// bytes and returned garbage (0).
#import "modules/std.sx";
count_nope :: (xs: []string) -> s64 {
n := 0;
i := 0;
while i < xs.len { if xs[i] == "nope" { n += 1; } i += 1; }
return n;
}
sum :: (xs: []s64) -> s64 {
s := 0;
i := 0;
while i < xs.len { s += xs[i]; i += 1; }
return s;
}
main :: () {
// string slice: direct literal vs local-bound — both see 2 "nope"s.
print("str direct={}\n", count_nope(.["a", "nope", "b", "nope"]));
local : []string = .["a", "nope", "b", "nope"];
print("str local={}\n", count_nope(local));
// numeric slice: direct literal vs local-bound — both sum to 100.
print("num direct={}\n", sum(.[10, 20, 30, 40]));
nums : []s64 = .[10, 20, 30, 40];
print("num local={}\n", sum(nums));
}

View File

@@ -0,0 +1,44 @@
// A nested array/slice literal (`.[.[1, 2], .[3, 4]]`) at an expected slice-of-
// slices type (`[][]s64`) materializes each inner `[N]T` literal as a real `[]T`
// slice, so indexing the inner slice in the callee reads element contents
// correctly — for both the local-bound form and the direct-call-argument form.
// Regression (issue 0085): inner literals were appended as raw `[N]T` arrays
// under an element type of `[]T`, so the outer aggregate's elements were arrays
// where slice {ptr,len} headers were expected; indexing the inner slice read a
// garbage pointer and segfaulted. The per-element array->slice materialization
// recurses with the nesting, so every level coerces.
#import "modules/std.sx";
sum_nested :: (xss: [][]s64) -> s64 {
total := 0;
i := 0;
while i < xss.len {
j := 0;
while j < xss[i].len { total += xss[i][j]; j += 1; }
i += 1;
}
return total;
}
count_x :: (xss: [][]string) -> s64 {
n := 0;
i := 0;
while i < xss.len {
j := 0;
while j < xss[i].len { if xss[i][j] == "x" { n += 1; } j += 1; }
i += 1;
}
return n;
}
main :: () {
// numeric [][]s64 — local-bound vs direct-arg both sum to 10.
local : [][]s64 = .[.[1, 2], .[3, 4]];
print("num local={}\n", sum_nested(local));
print("num direct={}\n", sum_nested(.[.[1, 2], .[3, 4]]));
// string [][]string — local-bound vs direct-arg both count 4 "x"s.
slocal : [][]string = .[.["x", "a"], .["b", "x"], .["x", "x"]];
print("str local={}\n", count_x(slocal));
print("str direct={}\n", count_x(.[.["x", "a"], .["b", "x"], .["x", "x"]]));
}

View File

@@ -0,0 +1,65 @@
// A named-const array dimension lays out identically whether the const is
// TYPED (`N : s64 : 16`) or untyped (`N :: 16`), used DIRECTLY (`a : [N]T`) or
// through a type alias (`Arr :: [N]T`), and regardless of whether the const is
// declared before or after the alias that consumes it.
//
// Regression (issue 0083): the stateless registration-time resolver
// (type_bridge) only saw module consts that were already in `module_const_map`
// when a type alias resolved its dimension. Typed consts register in a later
// pass, and a forward-declared untyped const had not registered yet — so the
// alias dimension fabricated length 0 (a 0-byte alloca), and element access
// returned garbage (scalars) or bus-errored (slice/struct elements). Module
// consts are now pre-registered before any alias resolves, and both the
// stateful and stateless paths share one dimension resolver.
#import "modules/std.sx";
NT : s64 : 8; // typed const used as a dimension
P :: struct { x: s64; y: s64; }
// Type aliases whose dimension is the TYPED const NT (stateless registration).
TArr :: [NT]s64;
TSArr :: [NT]string;
TPArr :: [NT]P;
// Forward reference: this alias is declared BEFORE its dimension const NF.
FArr :: [NF]s64;
NF :: 5;
main :: () {
// Typed-const dimension, DIRECT local decl.
d : [NT]s64 = ---;
d[0] = 3;
d[7] = 21;
print("direct d0={} d7={} len={}\n", d[0], d[7], d.len);
// Typed-const dimension via ALIAS (scalar): same layout as the direct form.
a : TArr = ---;
a[0] = 7;
a[7] = 99;
print("alias a0={} a7={} len={}\n", a[0], a[7], a.len);
// Typed-const dimension via ALIAS (string elements): no bus error.
s : TSArr = ---;
s[0] = "hi";
s[7] = "yo";
print("alias s0={} s7={}\n", s[0], s[7]);
// Typed-const dimension via ALIAS (struct elements).
ps : TPArr = ---;
ps[0] = P.{ x = 1, y = 2 };
ps[7] = P.{ x = 5, y = 6 };
print("alias p0x={} p0y={} p7x={}\n", ps[0].x, ps[0].y, ps[7].x);
// Nested fixed array whose both dimensions are the typed const NT.
grid : [NT][NT]s64 = ---;
grid[0][0] = 1;
grid[7][7] = 10;
print("nested g00={} g77={}\n", grid[0][0], grid[7][7]);
// Forward-referenced alias dimension (untyped const declared after it).
f : FArr = ---;
f[0] = 4;
f[4] = 40;
print("fwd f0={} f4={} len={}\n", f[0], f[4], f.len);
}

View File

@@ -0,0 +1,77 @@
// A constant-FOLDABLE expression array dimension (`[M + 1]`, `[M * N]`,
// `[N - M]`, nested `[M + N - 1]`, parenthesised `[(M + 1) * 2]`, and an
// expression mixing an untyped and a typed module const) resolves to its
// evaluated length — IDENTICALLY whether used DIRECTLY (`a : [M + 1]T`) or
// through a type alias (`A :: [M + 1]T`), and for scalar, string (slice/pointer
// class), and struct element types.
//
// Regression (issue 0083): the shared array-dimension resolver only looked up a
// bare named const or a literal; any const-foldable EXPRESSION dimension was
// rejected as "not a compile-time integer constant". It now routes the
// dimension through the shared comptime integer-expression evaluator
// (`program_index.evalConstIntExpr`), so integer `+ - * /` and parenthesisation
// over literals and module consts fold on BOTH the stateful (direct) and
// stateless (alias) paths — they share the one evaluator and cannot diverge.
#import "modules/std.sx";
M :: 4;
N :: 6;
TK : s64 : 2; // typed const, used inside an expression dimension
P :: struct { x: s64; y: s64; }
AddAlias :: [M + 1]s64; // 5
MulAlias :: [M * N]s64; // 24
SubAlias :: [N - M]s64; // 2
NestAlias :: [M + N - 1]s64; // 9
ParenAlias :: [(M + 1) * 2]s64; // 10
TypedAlias :: [M + TK]s64; // 6
StrAlias :: [M + 1]string; // 5, slice/pointer elements
StructAlias :: [M + 1]P; // 5, struct elements
main :: () {
// const + literal: direct and via alias resolve to the same length.
add_d : [M + 1]s64 = ---;
add_a : AddAlias = ---;
add_d[4] = 7;
add_a[4] = 7;
print("add direct.len={} alias.len={} d4={} a4={}\n", add_d.len, add_a.len, add_d[4], add_a[4]);
// const * const.
mul_d : [M * N]s64 = ---;
mul_a : MulAlias = ---;
mul_d[23] = 230;
mul_a[23] = 230;
print("mul direct.len={} alias.len={} d23={} a23={}\n", mul_d.len, mul_a.len, mul_d[23], mul_a[23]);
// const - const.
sub_d : [N - M]s64 = ---;
sub_a : SubAlias = ---;
sub_d[1] = 9;
sub_a[1] = 9;
print("sub direct.len={} alias.len={} d1={} a1={}\n", sub_d.len, sub_a.len, sub_d[1], sub_a[1]);
// nested and parenthesised forms (direct vs alias).
nest_d : [M + N - 1]s64 = ---;
nest_a : NestAlias = ---;
paren_d : [(M + 1) * 2]s64 = ---;
paren_a : ParenAlias = ---;
print("nest direct.len={} alias.len={} paren direct.len={} alias.len={}\n", nest_d.len, nest_a.len, paren_d.len, paren_a.len);
// typed const inside the expression dimension.
typ_d : [M + TK]s64 = ---;
typ_a : TypedAlias = ---;
print("typed direct.len={} alias.len={}\n", typ_d.len, typ_a.len);
// string elements (slice/pointer class) — no bus error, correct reads.
str_a : StrAlias = ---;
str_a[0] = "hi";
str_a[4] = "yo";
print("str alias.len={} s0={} s4={}\n", str_a.len, str_a[0], str_a[4]);
// struct elements.
ps : StructAlias = ---;
ps[0] = P.{ x = 1, y = 2 };
ps[4] = P.{ x = 5, y = 6 };
print("struct alias.len={} p0x={} p4y={}\n", ps.len, ps[0].x, ps[4].y);
}

View File

@@ -0,0 +1,29 @@
// An array dimension accepts any compile-time numeric constant whose value is a
// positive INTEGRAL number — an integral float (`4.0`) folds to its integer just
// like `4`. A float-typed const (`N : f64 : 4.0`), an untyped-float const
// (`M :: 4.0`), and a direct float literal (`[4.0]s64`) all lay out the same
// `[4]s64` as the integer spelling, so element store/read is in bounds.
//
// Regression (issue 0083 / F0.4 attempt 8, Agra ruling): an integral float used
// as a dimension was wrongly rejected "must be a compile-time integer constant".
// The shared const-int evaluator now folds an integral float literal (and a
// float-typed module const) via `program_index.floatToIntExact`; a non-integral
// float (`4.5`) is still rejected (see 1132).
#import "modules/std.sx";
N : f64 : 4.0; // float-typed const
M :: 4.0; // untyped float const
main :: () {
a : [N]s64 = ---; // dim from a float-typed const
a[0] = 10; a[3] = 40;
print("a len={} a0={} a3={}\n", a.len, a[0], a[3]);
b : [M]s64 = ---; // dim from an untyped float const
b[1] = 21;
print("b len={} b1={}\n", b.len, b[1]);
c : [4.0]s64 = ---; // direct integral-float-literal dim
c[2] = 32;
print("c len={} c2={}\n", c.len, c[2]);
}

View File

@@ -0,0 +1,68 @@
// The comptime-int COUNT surface is uniform: every count consumer — array
// dimension (direct `[N]T` and via type alias), `Vector` lane, generic
// value-param (struct AND type-fn binder), and `inline for 0..N` — folds the
// SAME leaf forms to the SAME value through one shared evaluator
// (`program_index.evalConstIntExpr` / `moduleConstInt`). The leaf forms
// exercised here: untyped int const (`M`), a named const with an EXPRESSION RHS
// (`N :: M + 1`), a typed-int const (`S : s64 : 5`), an integral float const
// (`F :: 4.0` ≡ 4), and an ALIASED integer constraint (`Count :: u32`,
// `Small :: s8`) on a value-param.
//
// Regression (issue 0083): two cells of this surface diverged from the rest.
// (1) A named const whose RHS is an expression (`N :: M + 1`) did not fold as a
// count ("not a compile-time integer constant") — `moduleConstInt` read only a
// literal RHS; it now folds the RHS through the shared `evalConstIntExpr`. (2) An
// aliased integer constraint (`$K: Count`) bypassed the value-param range gate,
// which only matched builtin constraint names; the constraint now resolves to
// its underlying builtin before range-checking, so `$K: Count` behaves exactly
// like `$K: u32`.
#import "modules/std.sx";
M :: 2; // untyped int const
N :: M + 1; // named const, EXPRESSION RHS (== 3)
S : s64 : 5; // typed-int const
KU : u32 : 3; // typed-u32 const
F :: 4.0; // integral float const (== 4)
Count :: u32; // integer ALIAS — value-param constraint
Small :: s8; // integer ALIAS — value-param constraint
ArrN :: [N]s64; // array dim via alias: expression const (3)
ArrF :: [F]s64; // array dim via alias: integral float (4)
ArrS :: [S]s64; // array dim via alias: typed const (5)
Buf :: struct ($K: u32, $T: Type) { data: [K]T; }
BufC :: struct ($K: Count, $T: Type) { data: [K]T; } // ALIASED u32 constraint
BufS :: struct ($K: Small, $T: Type) { data: [K]T; } // ALIASED s8 constraint
Make :: ($K: u32, $T: Type) -> Type { return [K]T; } // type-fn value-param
main :: () {
// array dimension — DIRECT
a : [N]s64 = ---; a[0] = 7; a[2] = 9;
print("dim.direct.expr: len={} a0={} a2={}\n", a.len, a[0], a[2]);
f : [F]s64 = ---; f[3] = 40;
print("dim.direct.float: len={} f3={}\n", f.len, f[3]);
// array dimension — via type ALIAS
aa : ArrN = ---; aa[2] = 99; print("dim.alias.expr: len={} aa2={}\n", aa.len, aa[2]);
af : ArrF = ---; print("dim.alias.float: len={}\n", af.len);
az : ArrS = ---; print("dim.alias.typed: len={}\n", az.len);
// Vector lane — expression const (3) and integral float (4)
v3 : Vector(N, f32) = .[1.0, 2.0, 3.0];
print("lane.expr3: {} {} {}\n", v3.x, v3.y, v3.z);
v4 : Vector(F, f32) = .[1.0, 2.0, 3.0, 4.0];
print("lane.float4: {}\n", v4.w);
// generic value-param — struct binder: expr const, aliased u32, aliased s8
bn : Buf(N, s64) = ---; bn.data[2] = 30; print("vp.struct.expr: len={} v={}\n", bn.data.len, bn.data[2]);
bc : BufC(KU, s64) = ---; bc.data[2] = 31; print("vp.struct.alias.u32: len={} v={}\n", bc.data.len, bc.data[2]);
bs : BufS(4, s64) = ---; bs.data[3] = 32; print("vp.struct.alias.s8: len={} v={}\n", bs.data.len, bs.data[3]);
// generic value-param — type-fn binder: expr const
mk : Make(N, s64) = ---; mk[2] = 33; print("vp.typefn.expr: len={} v={}\n", mk.len, mk[2]);
// inline-for bound — expr const (3) and integral float (4)
s := 0; inline for 0..N: (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3
t := 0; inline for 0..F: (i) { t += i; } print("for.float: {}\n", t); // 0+1+2+3 = 6
}

View File

@@ -0,0 +1,19 @@
// Zero is a context-dependent count. An array dimension and a generic
// value-param count both ACCEPT zero — `[0]T` is a valid empty (zero-length)
// array, and `Box(0)` is a length-0 instantiation. (A `Vector` lane count
// rejects zero — see 1505.) This pins the zero-accepting half of the
// context-dependent count rule documented in specs.md (Array Types).
//
// Regression (F0.4 attempt 12): the spec previously claimed every count must be
// "positive integral", which wrongly implied `[0]T` / `Box(0)` are illegal.
#import "modules/std.sx";
Box :: struct($N: u32) { items: [N]s64; }
main :: () {
a : [0]s64 = ---;
print("array_dim={}\n", a.len);
b : Box(0) = ---;
print("value_param={}\n", b.items.len);
}

View File

@@ -0,0 +1,29 @@
// A generic value parameter (`$K: u32`) bound from a named const or a
// constant-foldable expression resolves to the SAME monomorphised instantiation
// as the literal form: `Vec(N, f32)` (N a module const) and `Vec(M + 1, f32)`
// (a const expression) are both `Vec(3, f32)`. The struct-copy assignment is the
// proof — it type-checks only because the two spellings name one instantiation.
//
// Regression (issue 0083): the value-param binder hand-rolled an `else => 0`
// switch, so a named-const value arg either fabricated a 0 binding under a wrong
// mangled name or was rejected outright as "unknown type 'N'". It now folds
// through the shared const-int evaluator (`program_index.evalConstIntExpr`).
#import "modules/std.sx";
N :: 3;
M :: 2;
Vec :: struct ($K: u32, $T: Type) { data: [K]T; }
main :: () {
a : Vec(N, f32) = ---; // named-const value param
a.data[0] = 10.0; a.data[1] = 20.0; a.data[2] = 30.0;
print("named: len={} a0={} a2={}\n", a.data.len, a.data[0], a.data[2]);
e : Vec(M + 1, f32) = ---; // const-expr value param (M + 1 == 3)
e.data[0] = 1.0; e.data[2] = 9.0;
print("expr: len={} e2={}\n", e.data.len, e.data[2]);
b : Vec(3, f32) = a; // same instantiation → struct copy type-checks
print("copy: len={} b2={}\n", b.data.len, b.data[2]);
}

View 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]);
}

View File

@@ -0,0 +1,19 @@
// A generic value parameter (`$K: u32`) binds a literal (`Vec(3, s64)`) and an
// integral-float named const (`Vec(L, s64)` with `L : f64 : 4.0`) to the same
// integer a plain `4` would — the value-param arg folds through the shared
// const-int evaluator, so the integral-float rule (F0.4 attempt 8, Agra ruling)
// reaches value params too. The folded value is the array length `[K]s64`.
//
// The bind is range-checked against the declared `u32` (an out-of-range arg is a
// clean compile error — see 1134); a valid in-range value binds normally.
#import "modules/std.sx";
Vec :: struct ($K: u32, $T: Type) { data: [K]T; }
L : f64 : 4.0;
main :: () {
a : Vec(3, s64) = ---; // literal value param
b : Vec(L, s64) = ---; // integral-float named-const value param → 4
print("a.len={} b.len={}\n", a.data.len, b.data.len); // 3 and 4
}

View File

@@ -0,0 +1,23 @@
// `inline for 0..K` with a named-const or constant-foldable bound unrolls at
// compile time, just like a literal bound.
//
// Regression (issue 0083): the inline-for bound folder (`evalComptimeInt`) only
// handled literals, comptime cursors, and `<pack>.len`, so `inline for 0..M`
// (M a module const) and `inline for 0..(M + 1)` (a const expression) both
// failed with "range end is not a compile-time integer". `evalComptimeInt` now
// delegates to the single shared const-int evaluator
// (`program_index.evalConstIntExpr`), so the inline-for bound and an array
// dimension fold the same shapes to the same value.
#import "modules/std.sx";
M :: 3;
main :: () {
s := 0;
inline for 0..M: (i) { s += i; }
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
t := 0;
inline for 0..(M + 1): (i) { t += i; }
print("sum 0..(M+1) = {}\n", t); // 0 + 1 + 2 + 3 = 6
}

View File

@@ -0,0 +1,14 @@
// An `inline for 0..M` bound accepts an integral float constant — `M :: 3.0`
// unrolls the same three iterations as `M :: 3`. The inline-for bound folder
// (`evalComptimeInt`) delegates to the shared const-int evaluator, so the
// integral-float rule (issue 0083 / F0.4 attempt 8, Agra ruling) applies here
// too.
#import "modules/std.sx";
M :: 3.0;
main :: () {
s := 0;
inline for 0..M: (i) { s += i; }
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
}

View File

@@ -0,0 +1,24 @@
// An `inline for` / `for` range bound is a range ENDPOINT, not a count, so the
// count negative-rejection rule does NOT apply to it: negative endpoints are
// valid and an empty/inverted range simply runs zero iterations.
//
// Regression (F0.4 attempt 11, Agra ruling): the spec wrongly lumped inline-for
// bounds with counts (array dim / Vector lane / value-param), which reject
// negatives. Bounds are exempt — `inline for -2..1` iterates -2,-1,0 and an
// integral-float empty range `0..(-2.0)` runs zero iterations. Comptime and
// runtime loops must agree.
#import "modules/std.sx";
main :: () {
s := 0;
inline for -2..1: (i) { s += i; }
print("inline for -2..1 sum = {}\n", s); // -2 + -1 + 0 = -3
r := 0;
for -2..1: (i) { r += i; }
print("for -2..1 sum = {}\n", r); // -2 + -1 + 0 = -3 (runtime parity)
e := 0;
inline for 0..(-2.0): (i) { e += i; }
print("inline for 0..(-2.0) sum = {}\n", e); // empty range -> 0 iterations
}

View File

@@ -0,0 +1,24 @@
// An array dimension that is not a compile-time integer constant is a hard
// error, not a silently-fabricated 0-length array. Here a type alias's
// dimension is a runtime function call (`get()`), which is genuinely not
// compile-time-known — the registration-time resolver cannot evaluate it.
//
// (A const-FOLDABLE expression dimension such as `[M + 1]` is NOT an error — it
// folds; see examples/0144-types-const-expr-array-dim.sx. Only a dimension with
// a genuinely runtime operand halts here.)
//
// Regression (issue 0083): the stateless resolver printed a non-fatal warning
// and fabricated length 0, then let compilation continue — producing a 0-byte
// alloca and corrupt element access. It now yields the `.unresolved` sentinel,
// which the alias registration surfaces as this diagnostic, aborting the build
// with a non-zero exit.
#import "modules/std.sx";
get :: () -> s64 { return 5; }
BadArr :: [get()]s64;
main :: () {
a : BadArr = ---;
a[0] = 7;
print("a0={}\n", a[0]);
}

View 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);
}

View File

@@ -0,0 +1,24 @@
// An array dimension that folds to a valid compile-time integer but exceeds a
// `u32` is a hard error — and it must report the SAME precise diagnostic whether
// the array is written directly (`a : [5_000_000_000]s64`, see example 1130) or
// behind a type ALIAS (`Big :: [5_000_000_000]s64`, here). Both forms now route
// the dimension through one shared folder + one shared message map, so they
// cannot diverge.
//
// Regression (issue 0083 / F0.4 attempt 7): the stateless alias-registration
// path collapsed `foldDimU32`'s distinct `.too_large` outcome into `null` and
// emitted ONE generic "an array dimension is not a compile-time integer
// constant" message — FALSE, since 5_000_000_000 IS a compile-time integer
// constant; it merely doesn't fit a `u32`. The alias path now consults the
// shared fold and emits the precise "does not fit in u32" message, matching the
// direct form. (A genuinely non-const alias dim still gets the generic message —
// see example 1129.)
#import "modules/std.sx";
Big :: [5000000000]s64;
main :: () {
a : Big = ---;
a[0] = 7;
print("unreachable: {}\n", a[0]);
}

View File

@@ -0,0 +1,14 @@
// A NON-integral float constant (`4.5`) used as an array dimension is a hard
// error — only an integral float (`4.0`) folds to a count. Clean diagnostic +
// non-zero exit, NOT a fabricated length.
//
// Regression (F0.4 attempt 8, Agra ruling): the integral-float rule accepts
// `4.0` as a dimension but must keep rejecting `4.5` (it is not an integer).
#import "modules/std.sx";
N : f64 : 4.5;
main :: () {
a : [N]s64 = ---;
print("unreachable: {}\n", a.len);
}

View File

@@ -0,0 +1,12 @@
// A NEGATIVE integral float (`-2.0`) used as an array dimension is a hard error.
// The integral-float rule folds and negates it to `-2`, then the shared u32 dim
// gate rejects a below-minimum dimension — a clean diagnostic + non-zero exit.
//
// Regression (F0.4 attempt 8, Agra ruling): integral floats fold, but a negative
// result is still rejected (a dimension must be non-negative).
#import "modules/std.sx";
main :: () {
a : [-2.0]s64 = ---;
print("unreachable: {}\n", a.len);
}

View File

@@ -0,0 +1,17 @@
// A generic value-param arg that does not fit the param's declared integer type
// (`Box(5_000_000_000)` for `$K: u32`) is a hard error — a clean diagnostic +
// non-zero exit, NOT a silent truncating bind.
//
// Regression (F0.4 attempt 8, item 1): `resolveValueParamArg` bound the folded
// i64 without range-checking the declared type, so an out-of-u32 arg compiled
// and ran. The bind now routes a `u32` count through the shared
// `program_index.foldDimU32` gate (the same one array dims / Vector lanes use),
// so an oversized value is rejected before instantiation.
#import "modules/std.sx";
Box :: struct ($K: u32) { value: s64; }
main :: () {
b : Box(5000000000) = ---;
print("unreachable\n");
}

View File

@@ -0,0 +1,23 @@
// A generic value-param arg that does not fit the param's declared integer type
// is a hard error even when that type is reached through a type ALIAS
// (`$K: Count` where `Count :: u32`, `$K: Small` where `Small :: s8`) — a clean
// diagnostic + non-zero exit, NOT a silent truncating bind.
//
// Regression (issue 0083): the value-param range gate matched only BUILTIN
// constraint names, so an aliased constraint slipped past `intTypeRange` and
// `Box(5_000_000_000)` with `$K: Count` compiled and bound a truncated value.
// The constraint now resolves to its underlying builtin (`Count` → u32,
// `Small` → s8) before range-checking, so an aliased integer constraint behaves
// exactly like the builtin it names — at both the struct and type-fn binders.
#import "modules/std.sx";
Count :: u32;
Small :: s8;
Box :: struct ($K: Count) { value: s64; }
Tiny :: struct ($K: Small) { value: s64; }
main :: () {
b : Box(5000000000) = ---;
t : Tiny(300) = ---;
print("unreachable {} {}\n", b.value, t.value);
}

View File

@@ -0,0 +1,22 @@
// A DIRECT array dimension that is genuinely not a compile-time integer (a
// runtime call) is a hard error — ONE clean diagnostic + non-zero exit. Crucially
// it must NOT fabricate a length and must NOT crash later in lowering: the bad
// var is used downstream (element store + read, `.len`), and lowering has to bail
// gracefully on the `.unresolved` type rather than `@panic` in `sizeOf` or pile
// on cascade errors.
//
// Regression (issue 0083): the stateful `resolveArrayLen` emitted the diagnostic
// then `return 0` — fabricating a 0-length array (0-byte alloca, OOB access) to
// dodge the `sizeOf` panic. It now returns null → the `.unresolved` sentinel; the
// binding's lowering bails on it (a field access on an already-diagnosed
// `.unresolved` value stays silent), so the single real diagnostic aborts the
// build with no fabrication and no panic.
#import "modules/std.sx";
get :: () -> s64 { return 5; }
main :: () {
a : [get()]s64 = ---;
a[0] = 7;
print("unreachable: {} {}\n", a.len, a[0]);
}

View File

@@ -0,0 +1,27 @@
// A FAILED value-param bind on a type-RETURNING FUNCTION must emit exactly its
// own diagnostic and NOT cascade a bogus `field '…' not found on '<fn>'` when the
// binding is later field-accessed. The type-fn binder must poison the binding to
// `.unresolved` (the diagnosed-poison sentinel) — exactly like the struct binder —
// so the downstream `.len` is suppressed, not reported as a second error.
//
// Regression (issue 0083): the type-fn path (`instantiateTypeFunction`) fell
// through to an empty-struct placeholder named after the function on a failed
// value-param bind, so `a.len` produced a second `field 'len' not found on
// 'MakeC'` error. The struct binder already returned `.unresolved` here; the
// type-fn binder now matches it. Three failure modes, three clean diagnostics,
// zero field cascades:
// - value-param overflow via an aliased integer constraint (`$K: Count`),
// - a non-const value-param arg (`get()`),
// - an unknown TYPE arg (`NoSuchType`) — must still report the unknown type.
#import "modules/std.sx";
Count :: u32;
MakeC :: ($K: Count, $T: Type) -> Type { return [K]T; }
get :: () -> u32 { return 4; }
main :: () {
a : MakeC(5000000000, s64) = ---;
b : MakeC(get(), s64) = ---;
c : MakeC(3, NoSuchType) = ---;
print("unreachable {} {} {}\n", a.len, b.len, c.len);
}

View File

@@ -0,0 +1,14 @@
// A NON-integral float (`4.5`) as an `inline for` range bound is a hard error:
// the loop cursor must be a compile-time integer, so only an integral float
// (`4.0`, `-2.0`) folds. Clean diagnostic + non-zero exit.
//
// Regression (F0.4 attempt 11, Agra ruling): range bounds are exempt from the
// count negative-rejection (negatives are valid endpoints), but the
// must-be-integer requirement still applies — `4.5` has no integer value.
#import "modules/std.sx";
main :: () {
s := 0;
inline for 0..4.5: (i) { s += i; }
print("unreachable: {}\n", s);
}

View File

@@ -0,0 +1,35 @@
// A `Vector` lane count from a named const or a constant-foldable expression
// resolves to the SAME layout as a literal lane — DIRECT (param / return type)
// and via a type ALIAS. A 3-lane (named const `N`) and a 4-lane (const expr
// `M + 1`) prove the lane VALUE is folded, not fabricated: reading `.w` requires
// the 4-lane vector to actually have four lanes.
//
// Regression (issue 0083): the stateful Vector lane resolvers hand-rolled an
// `else => 0` switch, so a module-const lane (`Vector(N, f32)`) lowered a 0-lane
// `<0 x float>` and died in LLVM verification ("huge alignment values are
// unsupported"); a const-expr lane (`Vector(M + 1, f32)`) was rejected at parse.
// Both now fold through the single shared const-int evaluator
// (`program_index.evalConstIntExpr`) — the same one the array-dimension path
// uses — so a named-const / const-expr lane is identical to a literal lane.
#import "modules/std.sx";
N :: 3;
M :: 3;
LaneAlias :: Vector(N, f32); // ALIAS: 3-lane via named const.
ExprAlias :: Vector(M + 1, f32); // ALIAS: 4-lane via const expression.
mk3 :: () -> Vector(N, f32) { .[1.0, 2.0, 3.0] } // DIRECT named-const lane.
mk4 :: () -> Vector(M + 1, f32) { .[1.0, 2.0, 3.0, 4.0] } // DIRECT const-expr lane.
main :: () {
a := mk3();
print("direct3: {} {} {}\n", a.x, a.y, a.z);
b := mk4();
print("direct4: {} {} {} {}\n", b.x, b.y, b.z, b.w);
c : LaneAlias = .[5.0, 6.0, 7.0];
print("alias3: {}\n", c.z);
d : ExprAlias = .[5.0, 6.0, 7.0, 8.0];
print("alias4: {}\n", d.w);
}

View File

@@ -0,0 +1,16 @@
// A `Vector` lane count that is not a compile-time integer (here a runtime
// function call) is a hard error — a clean sx diagnostic with a non-zero exit,
// NOT a fabricated `<0 x float>` lane that crashes LLVM verification.
//
// Regression (issue 0083): the Vector lane resolver hand-rolled an `else => 0`
// switch that silently fabricated a 0-lane vector for a non-const lane. It now
// folds the lane through the shared const-int evaluator and, when that yields no
// compile-time integer, emits this diagnostic and halts the build.
#import "modules/std.sx";
lanes :: () -> u32 { return 3; }
main :: () {
v : Vector(lanes(), f32) = ---;
print("unreachable: {}\n", v.x);
}

View 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);
}

View File

@@ -0,0 +1,12 @@
// A Vector lane count accepts an integral float constant — `L : f64 : 4.0` lays
// out the same `Vector(4, f32)` as the literal `4`. The lane resolver shares the
// const-int evaluator with the array-dim path, so the integral-float rule
// (issue 0083 / F0.4 attempt 8, Agra ruling) applies uniformly.
#import "modules/std.sx";
L : f64 : 4.0;
main :: () {
v : Vector(L, f32) = .[1.0, 2.0, 3.0, 4.0];
print("v0={} v2={} v3={}\n", v[0], v[2], v[3]);
}

View File

@@ -0,0 +1,15 @@
// A zero `Vector` lane count is rejected — a vector must have at least one lane
// (strictly positive). Contrast with an array dimension / value-param count,
// where zero is a valid length-0 instantiation (see 0147). This pins the
// zero-rejecting half of the context-dependent count rule (specs.md, Array
// Types).
//
// Regression (F0.4 attempt 12): the spec now states the zero rule per consumer;
// the `Vector` lane count stays strictly positive while array dims / value-param
// counts accept zero.
#import "modules/std.sx";
main :: () {
v : Vector(0, f32) = ---;
print("unreachable: {}\n", v.x);
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
scalar a0=7 a3=42
string s0=hi s1=yo
struct p0x=1 p0y=2 p2x=5
alias a0=11 a3=99
alias s0=al s2=ok
nested g00=1 g32=8
union u0=70 u3=7

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,4 @@
str direct=2
str local=2
num direct=100
num local=100

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,4 @@
num local=10
num direct=10
str local=4
str direct=4

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
direct d0=3 d7=21 len=8
alias a0=7 a7=99 len=8
alias s0=hi s7=yo
alias p0x=1 p0y=2 p7x=5
nested g00=1 g77=10
fwd f0=4 f4=40 len=5

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
add direct.len=5 alias.len=5 d4=7 a4=7
mul direct.len=24 alias.len=24 d23=230 a23=230
sub direct.len=2 alias.len=2 d1=9 a1=9
nest direct.len=9 alias.len=9 paren direct.len=10 alias.len=10
typed direct.len=6 alias.len=6
str alias.len=5 s0=hi s4=yo
struct alias.len=5 p0x=1 p4y=6

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
a len=4 a0=10 a3=40
b len=4 b1=21
c len=4 c2=32

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,13 @@
dim.direct.expr: len=3 a0=7 a2=9
dim.direct.float: len=4 f3=40
dim.alias.expr: len=3 aa2=99
dim.alias.float: len=4
dim.alias.typed: len=5
lane.expr3: 1.000000 2.000000 3.000000
lane.float4: 4.000000
vp.struct.expr: len=3 v=30
vp.struct.alias.u32: len=3 v=31
vp.struct.alias.s8: len=4 v=32
vp.typefn.expr: len=3 v=33
for.expr: 3
for.float: 6

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,2 @@
array_dim=0
value_param=0

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
named: len=3 a0=10.000000 a2=30.000000
expr: len=3 e2=9.000000
copy: len=3 b2=30.000000

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,3 @@
named: len=3 a0=10 a2=30
expr: len=3 e2=9
copy: len=3 b2=30

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
a.len=3 b.len=4

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,2 @@
sum 0..M = 3
sum 0..(M+1) = 6

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
sum 0..M = 3

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,3 @@
inline for -2..1 sum = -3
for -2..1 sum = -3
inline for 0..(-2.0) sum = 0

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: type alias 'BadArr' could not be resolved: an array dimension is not a compile-time integer constant
--> examples/1129-diagnostics-array-dim-not-const.sx:18:11
|
18 | BadArr :: [get()]s64;
| ^^^^^^^^^^

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
1

View File

@@ -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 = ---;
| ^^^^^^^^^^

View File

@@ -0,0 +1,5 @@
error: array dimension 5000000000 does not fit in u32
--> examples/1131-diagnostics-array-dim-oversized-u32-alias.sx:18:9
|
18 | Big :: [5000000000]s64;
| ^^^^^^^^^^

View File

@@ -0,0 +1,5 @@
error: array dimension must be a compile-time integer constant
--> examples/1132-diagnostics-array-dim-non-integral-float.sx:12:10
|
12 | a : [N]s64 = ---;
| ^

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: array dimension must be non-negative, got -2
--> examples/1133-diagnostics-array-dim-negative-float.sx:10:10
|
10 | a : [-2.0]s64 = ---;
| ^^^^

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: value 5000000000 does not fit in u32 parameter K
--> examples/1134-diagnostics-value-param-u32-overflow.sx:15:13
|
15 | b : Box(5000000000) = ---;
| ^^^^^^^^^^

View File

@@ -0,0 +1,11 @@
error: value 5000000000 does not fit in u32 parameter K
--> examples/1135-diagnostics-value-param-alias-constraint-overflow.sx:20:13
|
20 | b : Box(5000000000) = ---;
| ^^^^^^^^^^
error: value 300 does not fit in s8 parameter K
--> examples/1135-diagnostics-value-param-alias-constraint-overflow.sx:21:14
|
21 | t : Tiny(300) = ---;
| ^^^

View File

@@ -0,0 +1,5 @@
error: array dimension must be a compile-time integer constant
--> examples/1136-diagnostics-array-dim-nonconst-direct-no-crash.sx:19:10
|
19 | a : [get()]s64 = ---;
| ^^^^^

View File

@@ -0,0 +1,17 @@
error: unknown type 'NoSuchType'
--> examples/1137-diagnostics-value-param-type-fn-no-cascade.sx:25:18
|
25 | c : MakeC(3, NoSuchType) = ---;
| ^^^^^^^^^^
error: value 5000000000 does not fit in u32 parameter K
--> examples/1137-diagnostics-value-param-type-fn-no-cascade.sx:23:15
|
23 | a : MakeC(5000000000, s64) = ---;
| ^^^^^^^^^^
error: generic value parameter 'K' must be a compile-time integer constant
--> examples/1137-diagnostics-value-param-type-fn-no-cascade.sx:24:15
|
24 | b : MakeC(get(), s64) = ---;
| ^^^^^

View File

@@ -0,0 +1,5 @@
error: inline for: range end is not a compile-time integer
--> examples/1138-diagnostics-inline-for-non-integral-bound.sx:12:19
|
12 | inline for 0..4.5: (i) { s += i; }
| ^^^

Some files were not shown because too many files have changed in this diff Show More