test: group examples into per-category folders

Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
agra
2026-06-21 14:41:34 +03:00
parent 6d1409bc1f
commit 66bdc70bf1
3357 changed files with 456 additions and 363 deletions

View File

@@ -0,0 +1,36 @@
#import "modules/std.sx";
Vec4 :: struct {
x, y, z, w: f32;
}
Complex :: struct {
foo : enum {
S: i32;
B: struct {
val: string;
};
} = .B.{"hello"};
}
main :: () {
v1 : Vec4 = .{ 1, 2, 3, 0};
v2 := Vec4.{ 4, 1, 1, 3};
v3 := Vec4.{ w=0, x=2, y=3, z=4};
z := 5.0; // z is f32
w := 6.0; // w is f32
v4 := Vec4.{ y=3, x=9, w, z};
v4.y = 0;
print("v1: {}\nv2: {}\nv3: {}\nv4: {}\n", v1, v2, v3, v4);
complex : Complex = .{};
print("\n{}\n", complex);
}
// ** stdout **
//v1: Vec4{x:1.0, y:2.0, z:3.0, w:0.0}
//v2: Vec4{x:4.0, y:1.0, z:1.0, w:3.0}
//v3: Vec4{x:2.0, y:3.0, z:4.0, w:0.0}
//v4: Vec4{x:9.0, y:3.0, z:5.0, w:6.0}
//
//Complex{foo: .B(Complex.foo.B{val: hello})}

View File

@@ -0,0 +1,49 @@
#import "modules/std.sx";
SPECIAL_VALUE :u8: 42;
resolve :: (x: u8) -> i32 {
return 12 + x;
}
Foo :: struct {
a : u2; // this will have 0 as default
b : u8 = SPECIAL_VALUE;
c : u8 = ---; // default for c is undefined
d : u8 = #run xx resolve(5); // converts i32 to u8
}
main :: () {
a : Foo; // a=0, b=42, d=17 from defaults; c is `---` (undefined)
// `c` was declared `---`, so its contents are undefined here. Pin it
// before printing so the snapshot is deterministic — printing the raw
// undefined byte yields whatever garbage the stack/codegen leaves.
a.c = 0;
print("a 0 : {}\n", a);
a.a = 1;
a.c = 8;
print("a 1 : {}\n", a);
large: f64 = 5989.5;
b : Foo = ---; // EVERY field undefined — `= ---` skips all defaults
// Assign each field explicitly; nothing here may be read before it is
// written, since `= ---` left the whole struct undefined.
b.a = 1;
b.b = SPECIAL_VALUE; // re-supply the default `= ---` skipped
b.c = xx large; // converts f64 to u8 (5989.5 -> 101)
b.d = xx resolve(5); // converts i32 to u8 (17)
print("b: {}", b);
print("\n");
f := Pack.{1,0,3,5,9,100,3.5};
print("{}\n", f);
}
Pack :: struct {
a: u1;
b: u2;
c: u8;
d: u32;
f: u64;
v: i32;
x: f32;
}

View File

@@ -0,0 +1,48 @@
#import "modules/std.sx";
Shape :: enum {
circle: f32;
rect: struct { w, h: f32;};
none;
}
main :: () {
// Construction with .variant(payload)
s :Shape = .circle(3.14);
print("circle: {}\n", s);
// Payload access
r := s.circle;
print("radius: {}\n", r);
// Void variant via enum literal
s = .none;
print("none: {}\n", s);
// Reassign with payload
s = .rect(.{4, 2});
print("rect: {}\n", s);
// Explicit prefix construction
sh :Shape = Shape.circle(2.71);
print("sh: {}\n", sh);
// Field access on second variable
sh2 :Shape = .rect(.{2,4});
val := sh2.rect;
print("rect val: {}\n", val);
// Match on enum
if sh2 == {
case .circle: print("matched circle\n");
case .rect: print("matched rect\n");
case .none: print("matched none\n");
}
cs := if sh2 == {
case .circle: 1;
case .rect: 2;
case .none: 3;
}
print("case : {}", cs);
}

View File

@@ -0,0 +1,22 @@
#import "modules/std.sx";
Point :: struct {
x, y: i32;
}
Color :: struct {
r, g, b: i32;
}
main :: () {
p := Point.{10, 20};
c := Color.{255, 128, 0};
pc := @p;
print("p: {}\n", p);
print("c: {}\n", c);
print("n: {}\n", 42);
print("s: {}\n", "hello");
print("b: {}\n", true);
print("&p: {}\n", pc);
print("&p: {}\n", @p);
}

View File

@@ -0,0 +1,28 @@
#import "modules/std.sx";
Overlay :: union {
f: f32;
i: i32;
}
Vec2 :: union {
data: [2]f32;
struct { x, y: f32; };
}
main :: () {
// Basic union: type punning
o :Overlay = ---;
o.f = 3.14;
print("f={}\n", o.f);
print("i={}\n", o.i);
// Union with anonymous struct: member promotion
v :Vec2 = ---;
v.x = 1.0;
v.y = 2.0;
print("x={}\n", v.x);
print("y={}\n", v.y);
print("data[0]={}\n", v.data[0]);
print("data[1]={}\n", v.data[1]);
}

View File

@@ -0,0 +1,69 @@
#import "modules/std.sx";
// Auto power-of-2 values: read=1, write=2, execute=4
Perms :: enum flags {
read;
write;
execute;
}
// Explicit values with u32 backing type (e.g. for C interop)
WindowFlags :: enum flags u32 {
vsync :: 64;
resizable :: 4;
hidden :: 128;
}
// Backing type on plain enums too
Color :: enum u8 { red; green; blue; }
check_perms :: (p: Perms) {
print(" checking: {}\n", p);
if p & .read { print(" - can read\n"); }
if p & .write { print(" - can write\n"); }
if p & .execute { print(" - can execute\n"); }
}
main :: () {
// Combine flags with |
p :Perms = .read | .write;
print("perms: {}\n", p);
// Test individual flags with &
check_perms(p);
// All flags
all :Perms = .read | .write | .execute;
print("\nall: {}\n", all);
check_perms(all);
// Single flag
r :Perms = .read;
print("\nread only: {}\n", r);
check_perms(r);
// Pass flags to functions, match on them
print("\nmatch on flags:\n");
f :Perms = .execute;
if f == {
case .read: print(" read\n");
case .write: print(" write\n");
case .execute: print(" execute\n");
}
// Explicit values
w :WindowFlags = .vsync | .resizable;
print("\nwindow: {}\n", w);
print("raw value: {}\n", cast(i64) w);
// Backing type on plain enums
c :Color = .blue;
print("\ncolor: {}\n", c);
print("raw: {}\n", cast(i64) c);
// Bitwise ops work on plain integers too
x := 0xFF & 0x0F;
y := 1 | 2 | 4;
print("\n0xFF & 0x0F = {}\n", x);
print("1 | 2 | 4 = {}\n", y);
}

View File

@@ -0,0 +1,55 @@
// Test: compound assignment operators on global variables
// Ensures += -= *= /= %= &= |= ^= <<= >>= all do load-modify-store
#import "modules/std.sx";
g_add : i64 = 10;
g_sub : i64 = 10;
g_mul : i64 = 10;
g_div : i64 = 100;
g_mod : i64 = 10;
g_and : i64 = 0xff;
g_or : i64 = 0x0f;
g_xor : i64 = 0xff;
g_shl : i64 = 1;
g_shr : i64 = 256;
main :: () -> void {
// += repeated: should accumulate, not reset
g_add += 1;
g_add += 1;
g_add += 1;
print("add: {}\n", g_add); // 13
g_sub -= 3;
g_sub -= 2;
print("sub: {}\n", g_sub); // 5
g_mul *= 2;
g_mul *= 3;
print("mul: {}\n", g_mul); // 60
g_div /= 5;
g_div /= 4;
print("div: {}\n", g_div); // 5
g_mod %= 3;
print("mod: {}\n", g_mod); // 1
g_and &= 0x0f;
print("and: {}\n", g_and); // 15
g_or |= 0xf0;
print("or: {}\n", g_or); // 255
g_xor ^= 0x0f;
print("xor: {}\n", g_xor); // 240
g_shl <<= 4;
g_shl <<= 2;
print("shl: {}\n", g_shl); // 64
g_shr >>= 3;
g_shr >>= 2;
print("shr: {}\n", g_shr); // 8
}

View File

@@ -0,0 +1,16 @@
// Integer literal `0` on the RHS of an integer comparison stays integer-typed
// even when the comparison is the condition of an `if-then-else` whose result
// type is `f32`. The comparison must not pick up the outer ternary's type.
#import "modules/std.sx";
main :: () -> void {
x : i64 = 42;
// OK: comparison in statement context
if x != 0 { out("ok\n"); }
// BUG: comparison as condition of f32 ternary — `0` inferred as f32
result : f32 = if x != 0 then 1.0 else 2.0;
print("result = {}\n", result);
}

View File

@@ -0,0 +1,43 @@
// Assigning to `list.items` writes exactly the items-pointer field even when
// the element type `T` is larger than the pointer (40-byte BigNode here), so
// a sibling field of the enclosing struct is not corrupted.
#import "modules/std.sx";
// 40-byte struct — triggers the bug.
// Shrink to [4]i64 (32 bytes) and the bug goes away.
BigNode :: struct {
data: [5]i64; // 40 bytes
}
Tree :: struct {
nodes: List(BigNode); // items(8) + len(8) + cap(8) = 24 bytes
generation: i64; // 8 bytes — total 32 bytes
}
Container :: struct {
tree: Tree;
sentinel: i64;
do_work :: (self: *Container) {
self.tree.nodes.items = xx 0; // BUG: corrupts self.sentinel
}
}
main :: () -> void {
obj : *Container = xx context.allocator.alloc_bytes(size_of(Container));
memset(obj, 0, size_of(Container));
obj.sentinel = 0xDEADBEEF;
print("size_of BigNode = {}\n", size_of(BigNode));
print("before: sentinel = {}\n", obj.sentinel);
obj.do_work();
print("after: sentinel = {}\n", obj.sentinel);
if obj.sentinel != 0xDEADBEEF {
print("BUG: sentinel was corrupted!\n");
} else {
out("OK\n");
}
}

View File

@@ -0,0 +1,32 @@
// `+=` on a global variable loads the current value (not the initializer)
// before storing — same semantics as the explicit `g = g + 1` form.
#import "modules/std.sx";
g_counter : i64 = 0;
tick :: () {
g_counter += 1;
print("counter={}\n", g_counter);
}
main :: () -> void {
// Test 1: += always produces 1 (BUG)
out("--- Test 1: += (broken) ---\n");
out("Expected: 1, 2, 3\n");
i : i64 = 0;
while i < 3 {
tick();
i += 1;
}
// Test 2: manual read-modify-write works correctly
out("--- Test 2: = x + 1 (works) ---\n");
out("Expected: 2, 3, 4\n");
g_counter = g_counter + 1;
print("counter={}\n", g_counter);
g_counter = g_counter + 1;
print("counter={}\n", g_counter);
g_counter = g_counter + 1;
print("counter={}\n", g_counter);
}

View File

@@ -0,0 +1,23 @@
// Global array declared with `: [N]T = .[...]` keeps its initializer values
// (signed-int element type covers negative-literal handling too).
#import "modules/std.sx";
VALS : [4]i32 = .[-2, -1, 42, 99];
main :: () {
out("VALS: ");
i := 0;
while i < 4 {
out(int_to_string(xx VALS[i]));
out(" ");
i = i + 1;
}
out("\n");
if VALS[0] == -2 and VALS[1] == -1 and VALS[2] == 42 and VALS[3] == 99 {
out("PASS\n");
} else {
out("FAIL: global array not initialized\n");
}
}

View File

@@ -0,0 +1,26 @@
// Global struct initialized with `.{}` (or a partial struct literal) honors
// the struct's per-field defaults, matching the function-local behavior.
#import "modules/std.sx";
Foo :: struct {
running: bool = true;
x: i32 = 42;
name: string = "default";
}
g_empty : Foo = .{};
g_partial : Foo = .{ x = 99 };
g_override : Foo = .{ running = false };
g_reorder : Foo = .{ x = 7, running = false, name = "hi" };
g_positional : Foo = .{ false, 13, "pos" };
main :: () -> void {
l_empty : Foo = .{};
print("local running={} x={} name={}\n", l_empty.running, l_empty.x, l_empty.name);
print("g_empty running={} x={} name={}\n", g_empty.running, g_empty.x, g_empty.name);
print("g_partial running={} x={} name={}\n", g_partial.running, g_partial.x, g_partial.name);
print("g_override running={} x={} name={}\n", g_override.running, g_override.x, g_override.name);
print("g_reorder running={} x={} name={}\n", g_reorder.running, g_reorder.x, g_reorder.name);
print("g_positional running={} x={} name={}\n", g_positional.running, g_positional.x, g_positional.name);
}

View File

@@ -0,0 +1,16 @@
// Top-level type alias `Handle :: u32;` resolves to its target type in every
// position — function signatures, type annotations on globals, and initializer
// literal coercion.
#import "modules/std.sx";
Handle :: u32;
ok :: () -> Handle { 0 }
g : Handle = 0;
main :: () -> i32 {
g = ok();
if g == 0 then 0 else 1
}

View File

@@ -0,0 +1,21 @@
// Pre-fix: `resolveType(null)` silently returned `.i64`, so a top-level
// var without a type annotation got typed as `i64` regardless of what
// the initializer was. For `g_pi := 3.14;` this meant the float literal
// was assigned to an i64 slot, producing a wrong value at runtime or
// the wrong codegen shape.
//
// After the fix `lowerVarDecl` at the top level mirrors the local-scope
// path: explicit annotation → resolveType; no annotation → infer from
// the initializer's type. Mirrors how `:=` already worked for locals.
#import "modules/std.sx";
g_count := 42; // inferred i64
g_pi := 3.14; // inferred f64 — used to silently become i64
g_flag := true; // inferred bool
main :: () -> i32 {
print("count = {}\n", g_count);
print("pi = {}\n", g_pi);
print("flag = {}\n", g_flag);
return 0;
}

View File

@@ -0,0 +1,35 @@
// FFI plan step 5.1 — `build_block_convert(args: []Type, $ret: Type)
// -> string` emits the per-shape source body for the generic
// `Into(Block) for Closure(..$args) -> $R` impl that lands in step
// 5.2. Per-call-shape monomorphisation of the impl body re-runs the
// builder with concrete types bound, so each closure shape gets its
// own dedicated `__invoke` trampoline + Block literal.
//
// This test exercises the builder directly (no `#insert`, no impl
// wiring) — three pack shapes through the same `void`-returning
// wrapper plus one non-void `i32`-returning wrapper to pin the
// `return typed_fn(...)` branch. The expected output captures the
// generated source verbatim so any formatting drift surfaces here
// rather than as a downstream compile error inside the eventual
// step-5.2 impl.
#import "modules/std.sx";
#import "modules/ffi/objc_block.sx";
preview_void :: (..$args) -> string {
return build_block_convert($args, void);
}
preview_i32 :: (..$args) -> string {
return build_block_convert($args, i32);
}
run_all :: () {
print("--- void / 0 args ---\n{}\n", preview_void());
print("--- void / bool ---\n{}\n", preview_void(true));
print("--- void / i64, string ---\n{}\n", preview_void(42, "hi"));
print("--- i32 / f64 ---\n{}\n", preview_i32(3.14));
}
#run run_all();
main :: () { print("rt\n"); }

View File

@@ -0,0 +1,34 @@
// Compound type literals in expression position — `size_of` /
// `align_of` accept pointer (`*T`), optional (`?T`), array (`[N]T`),
// function (`(A) -> B`), and tuple (`(A, B)`) types directly. Also
// const-decl RHS aliases through the same forms (`Ptr :: *u8;` etc).
// Same shape as the existing `size_of(i32)` baseline path.
#import "modules/std.sx";
// Unambiguous type-form const-decl aliases.
Ptr :: *u8;
Maybe :: ?u8;
Arr :: [3]u8;
Cb :: (i32) -> i32;
main :: () -> i32 {
// Direct: parser fix for *T, ?T + existing [N]T path.
print("size_of(*u8) = {}\n", size_of(*u8));
print("align_of(*u8) = {}\n", align_of(*u8));
print("size_of(?u8) = {}\n", size_of(?u8));
print("size_of([3]u8) = {}\n", size_of([3]u8));
// Function-type literal in expression position.
print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
// Tuple literal reinterpreted as tuple type at the type-demanding site.
print("size_of((i32, i32)) = {}\n", size_of((i32, i32)));
// Aliases.
print("size_of(Ptr) = {}\n", size_of(Ptr));
print("size_of(Maybe) = {}\n", size_of(Maybe));
print("size_of(Arr) = {}\n", size_of(Arr));
print("size_of(Cb) = {}\n", size_of(Cb));
0
}

View File

@@ -0,0 +1,22 @@
// Type alias resolution through `size_of` / `align_of` — a const-decl
// alias (`MyInt :: i32;`, `MyChain :: MyInt;`, `WideAlias :: Wide;`)
// resolves through `type_alias_map` when used as a `$T: Type` argument.
// Covers chains and struct-name aliases, not just structural-type
// aliases (those land in `examples/182-compound-type-in-expression.sx`).
#import "modules/std.sx";
MyInt :: i32;
MyChain :: MyInt;
Wide :: struct { a: i64; b: i64; }
WideAlias :: Wide;
main :: () -> i32 {
print("direct i32: {}\n", size_of(i32));
print("alias i32: {}\n", size_of(MyInt));
print("chain i32: {}\n", size_of(MyChain));
print("align alias: {}\n", align_of(MyInt));
print("align chain: {}\n", align_of(MyChain));
print("size struct-alias: {}\n", size_of(WideAlias));
print("align struct-alias:{}\n", align_of(WideAlias));
0
}

View File

@@ -0,0 +1,28 @@
// Generic `Into(Block)` impl with a `string`-typed arg in the
// closure signature. The block trampoline declares the param with
// abi(.c); without the abi-collapse fix, sx `string` got
// silently collapsed to `ptr` (the libc `char *` heuristic) and
// the caller's 16-byte `{ptr, len}` value mismatched the
// trampoline's 8-byte `ptr` slot. Result: segfault inside the
// trampoline's first read.
//
// The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` →
// `ptr` collapse only applies to `is_extern` extern decls (libc
// interop). sx-internal `abi(.c)` keeps the full slice
// shape, which lands as `[2 x i64]` at the LLVM signature site
// and matches the caller's two-register pass on AArch64.
#import "modules/std.sx";
#import "modules/ffi/objc_block.sx";
g_s: string = "";
main :: () -> i32 {
cl := (s: string) => { g_s = s; };
b : Block = xx cl;
invoke_fn : (*Block, string) -> void abi(.c) = xx b.invoke;
invoke_fn(@b, "hello");
if g_s.len == 0 { print("FAIL: empty\n"); return 1; }
print("got: <{}>\n", g_s);
0
}

View File

@@ -0,0 +1,206 @@
// Type as a first-class value — comprehensive interaction smoke.
//
// Every way a user can plausibly touch a `Type` value, in one
// place. Pre-fix: some sections fail (`<?>`, false, garbage, or
// compile errors). Post-fix: every section's output matches the
// header line.
//
// Sections labelled `--- N. <thing> ---` so the diff localises
// which interaction regressed.
#import "modules/std.sx";
// ── Test fixtures ─────────────────────────────────────────────
Point :: struct { x: i32; y: i32; }
Color :: enum { red; green; blue; }
Wrap :: struct ($T: Type) { v: T; }
identity :: ($T: Type, val: T) -> T => val;
// Generic comptime fn that dispatches on type identity (compile-
// time fold via type_eq → const_bool → inline-if folds the
// branch away).
describe :: ($T: Type) -> string {
inline if type_eq(T, i64) { return "int64"; }
inline if type_eq(T, string) { return "text"; }
inline if type_eq(T, bool) { return "boolean"; }
return "other";
}
// Pack-fn collecting per-position types (step 4A.bare path).
type_list :: (..$args) -> string {
list := $args;
s := "[";
i : i64 = 0;
while i < list.len {
if i > 0 { s = concat(s, ", "); }
s = concat(s, type_name(list[i]));
i = i + 1;
}
return concat(s, "]");
}
// Type stored in a struct field.
TypeHolder :: struct { t: Type; }
main :: () -> i32 {
// ── 1. Type literal equality ────────────────────────────
print("=== 1. literal == ===\n");
print("i64 == i64: {}\n", i64 == i64);
print("i64 == string: {}\n", i64 == string);
print("*u8 == *u8: {}\n", *u8 == *u8);
print("?i64 == ?i64: {}\n", ?i64 == ?i64);
print("?i64 == ?i32: {}\n", ?i64 == ?i32);
// ── 2. type_of(value) ───────────────────────────────────
print("=== 2. type_of(value) == T ===\n");
a : i64 = 42;
b : f64 = 3.14;
s : string = "hi";
print("type_of(a) == i64: {}\n", type_of(a) == i64);
print("type_of(b) == f64: {}\n", type_of(b) == f64);
print("type_of(s) == string: {}\n", type_of(s) == string);
print("type_of(a) == f64: {}\n", type_of(a) == f64);
// ── 3. Type variable storage + readback ─────────────────
print("=== 3. Type variable storage ===\n");
t : Type = f64;
print("t == f64: {}\n", t == f64);
print("t == string: {}\n", t == string);
t = string;
print("after reassign t == string: {}\n", t == string);
t = bool;
print("t == bool: {}\n", t == bool);
// ── 4. type_name on literals + variables ────────────────
print("=== 4. type_name ===\n");
print("type_name(i64): {}\n", type_name(i64));
print("type_name(*u8): {}\n", type_name(*u8));
print("type_name(Point): {}\n", type_name(Point));
print("type_name(Color): {}\n", type_name(Color));
t = f64;
print("type_name(t): {}\n", type_name(t));
// ── 5. Print Type values directly ───────────────────────
print("=== 5. print Type values ===\n");
print("literal: {}\n", i64);
t = string;
print("var: {}\n", t);
print("type_of(b): {}\n", type_of(b));
// ── 6. Generic dispatch via $T: Type ────────────────────
print("=== 6. generic dispatch ===\n");
print("describe(i64): {}\n", describe(i64));
print("describe(string): {}\n", describe(string));
print("describe(bool): {}\n", describe(bool));
print("describe(f64): {}\n", describe(f64));
// ── 7. identity(T, val) ─────────────────────────────────
print("=== 7. identity($T, val) ===\n");
print("identity(i64, 7): {}\n", identity(i64, 7));
print("identity(string, hi): {}\n", identity(string, "hi"));
print("identity(bool, true): {}\n", identity(bool, true));
// ── 8. Comptime-generated struct (Wrap($T)) ─────────────
print("=== 8. Wrap($T) ===\n");
w_int := Wrap(i64).{ v = 42 };
w_str := Wrap(string).{ v = "wrapped" };
print("Wrap(i64).v: {}\n", w_int.v);
print("Wrap(string).v: {}\n", w_str.v);
// ── 9. Reflection builtins on Types ─────────────────────
print("=== 9. reflection on Type ===\n");
print("size_of(i64): {}\n", size_of(i64));
print("size_of(*u8): {}\n", size_of(*u8));
print("align_of(f64): {}\n", align_of(f64));
print("field_count(Point): {}\n", field_count(Point));
print("type_eq(i64, i64): {}\n", type_eq(i64, i64));
print("type_eq(i64, string): {}\n", type_eq(i64, string));
// ── 10. Type pack (..$args) walking ─────────────────────
print("=== 10. ..$args walking ===\n");
print("type_list(): {}\n", type_list());
print("type_list(1): {}\n", type_list(42));
print("type_list(1, \"x\"): {}\n", type_list(42, "x"));
print("type_list(true, 3.14): {}\n", type_list(true, 3.14));
// ── 11. Type in struct field ────────────────────────────
print("=== 11. Type in struct field ===\n");
h := TypeHolder.{ t = i64 };
print("h.t == i64: {}\n", h.t == i64);
print("h.t == string: {}\n", h.t == string);
print("type_name(h.t): {}\n", type_name(h.t));
// ── 12. Compound type literals ──────────────────────────
print("=== 12. compound literals ===\n");
print("type_name(*Point): {}\n", type_name(*Point));
print("type_name([4]i32): {}\n", type_name([4]i32));
print("type_name([]bool): {}\n", type_name([]bool));
print("type_name(?f64): {}\n", type_name(?f64));
return 0;
}
// ** stdout **
// === 1. literal == ===
// i64 == i64: true
// i64 == string: false
// *u8 == *u8: true
// ?i64 == ?i64: true
// ?i64 == ?i32: false
// === 2. type_of(value) == T ===
// type_of(a) == i64: true
// type_of(b) == f64: true
// type_of(s) == string: true
// type_of(a) == f64: false
// === 3. Type variable storage ===
// t == f64: true
// t == string: false
// after reassign t == string: true
// t == bool: true
// === 4. type_name ===
// type_name(i64): i64
// type_name(*u8): *u8
// type_name(Point): Point
// type_name(Color): Color
// type_name(t): f64
// === 5. print Type values ===
// literal: i64
// var: string
// type_of(b): f64
// === 6. generic dispatch ===
// describe(i64): int64
// describe(string): text
// describe(bool): boolean
// describe(f64): other
// === 7. identity($T, val) ===
// identity(i64, 7): 7
// identity(string, hi): hi
// identity(bool, true): true
// === 8. Wrap($T) ===
// Wrap(i64).v: 42
// Wrap(string).v: wrapped
// === 9. reflection on Type ===
// size_of(i64): 8
// size_of(*u8): 8
// align_of(f64): 8
// field_count(Point): 2
// type_eq(i64, i64): true
// type_eq(i64, string): false
// === 10. ..$args walking ===
// type_list(): []
// type_list(1): [i64]
// type_list(1, "x"): [i64, string]
// type_list(true, 3.14): [bool, f64]
// === 11. Type in struct field ===
// h.t == i64: true
// h.t == string: false
// type_name(h.t): i64
// === 12. compound literals ===
// type_name(*Point): *Point
// type_name([4]i32): [4]i32
// type_name([]bool): []bool
// type_name(?f64): ?f64

View File

@@ -0,0 +1,55 @@
// Tuple values: construction, element access, struct-field storage,
// return, and operators. Regression for the tuple-construction bug where
// an inferred `:=` tuple literal lowered its element values under the
// enclosing fn's (narrower) return `target_type`, mismatching the
// independently-inferred i64 field types and yielding garbage on read.
#import "modules/std.sx";
Box :: struct { xs: (i32, i32); }
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
fst :: (t: (i64, i64)) -> i64 { t.0 }
main :: () -> i32 {
// Inferred positional tuple + numeric field access.
pair := (40, 2);
print("pair {} {}\n", pair.0, pair.1);
// Named tuple: named + numeric access.
named := (x: 10, y: 20);
print("named {} {} {}\n", named.x, named.0, named.1);
// Element into a typed local (access path, not just print).
a : i64 = pair.0;
b : i64 = pair.1;
print("locals {} {}\n", a, b);
// Tuple-typed struct field: store a tuple value, read both elements.
box : Box = ---;
box.xs = (7, 9);
print("field {} {}\n", box.xs.0, box.xs.1);
// Return a tuple from a function.
s := swap(1, 2);
print("ret {} {}\n", s.0, s.1);
// Pass a tuple by value.
print("pass {}\n", fst((11, 22)));
// Operators: equality, concatenation, repetition, membership, lex.
print("eq {}\n", (1, 2) == (1, 2));
c := (1, 2) + (3, 4);
print("concat {} {}\n", c.0, c.3);
r := (1, 2) * 3;
print("rep {} {}\n", r.0, r.5);
print("mem {}\n", 3 in (1, 2, 3));
print("lex {}\n", (1, 2) < (1, 3));
// Mixed-size fields: a tuple with both an i64 and a string (16-byte fat
// pointer). Field types are tracked per-position, so reading each back is
// typed correctly (i64 prints as a number, string as text).
mixed := (42, "hi");
print("mixed {} {}\n", mixed.0, mixed.1);
0
}

View File

@@ -0,0 +1,25 @@
// Tuple element assignment + named tuples.
// - `t.0 = v` writes one element in place (was a known gap: the lvalue path
// looked the element up by name via getStructFields and left the pointee
// `.unresolved`; now it indexes the tuple positionally like the read path).
// - Named tuples `(x: T, y: U)` keep their field names through parsing and
// type resolution, so `t.x` reads/writes by name (and `.0` by position).
#import "modules/std.sx";
main :: () -> i32 {
// Positional element assignment.
a : (i32, string) = ---;
a.0 = 11;
a.1 = "x";
print("a: {} {}\n", a.0, a.1);
// Named tuple: write + read by name, and read by position.
p : (x: i32, y: string) = ---;
p.x = 22;
p.y = "y";
print("p: x={} y={} .0={}\n", p.x, p.y, p.0);
p.0 = 33; // position write reaches the same slot as .x
print("p.x after .0=33: {}\n", p.x);
0
}

View File

@@ -0,0 +1,342 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
Overlay :: union {
f: f32;
i: i32;
}
Vec2 :: union {
data: [2]f32;
struct { x, y: f32; };
}
Defaults :: struct {
a: i32;
b: i32 = 99;
c: i32 = ---;
}
MyFloat :: f64;
Status :: enum u8 { ok; err; timeout; }
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: i32, b: i32) -> i32 { a * b }
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
.[x, y, z]
}
// Global variable for address-of test
g_smoke_val : i32 = 42;
write_to_ptr :: (p: *i32) {
p.* = 99;
}
main :: () {
// ========================================================
// 3. TYPE SYSTEM
// ========================================================
print("=== 3. Types ===\n");
// Primitive types
v_i8 : i8 = 127;
v_i16 : i16 = 32000;
v_i32 : i32 = 100000;
v_u8 : u8 = 255;
v_u16 : u16 = 65000;
v_u32 : u32 = 4000000;
print("i8: {}\n", v_i8);
print("i16: {}\n", v_i16);
print("i32: {}\n", v_i32);
print("u8: {}\n", v_u8);
print("u16: {}\n", v_u16);
print("u32: {}\n", v_u32);
// Type alias
mf : MyFloat = 1.5;
print("alias: {}\n", mf);
// --- Structs ---
// Positional literal
p1 : Point = .{ 1, 2 };
print("struct-pos: {}\n", p1);
// Type-prefix literal
p2 := Point.{ 3, 4 };
print("struct-prefix: {}\n", p2);
// Named fields
p3 := Point.{ y=10, x=20 };
print("struct-named: {}\n", p3);
// Shorthand (variable name = field name)
x : i32 = 5;
y : i32 = 6;
p4 := Point.{ x, y };
print("struct-shorthand: {}\n", p4);
// Field defaults
d1 : Defaults;
print("defaults: a={} b={}\n", d1.a, d1.b);
// Field access and assignment
p5 := Point.{ 0, 0 };
p5.x = 42;
p5.y = 99;
print("field-assign: {}\n", p5);
// --- Enum (payload-less) ---
ec : Color = .red;
print("enum: {}\n", ec);
// Enum comparison
ce1 : Color = .red;
ce2 : Color = .red;
ce3 : Color = .blue;
print("enum-eq: {}\n", ce1 == ce2);
print("enum-neq: {}\n", ce1 != ce3);
// Backing type
st : Status = .err;
print("backing: {}\n", st);
// --- Enum (tagged union) ---
sh : Shape = .circle(3.14);
print("tagged: {}\n", sh);
// Payload access
radius := sh.circle;
print("payload: {}\n", radius);
// Void variant
sh = .none;
print("void-variant: {}\n", sh);
// Variant reassignment
sh = .circle(1.0);
print("reassign: {}\n", sh);
sh = .rect(.{ 5, 3 });
print("reassign2: {}\n", sh);
// Type-prefix construction
tp := Shape.circle(2.5);
print("enum-prefix: {}\n", tp);
// Pattern matching
sh2 : Shape = .rect(.{ 5, 3 });
if sh2 == {
case .circle: print("match: circle\n");
case .rect: print("match: rect\n");
case .none: print("match: none\n");
}
// Match as expression
sh3 : Shape = .circle(1.0);
ms := if sh3 == {
case .circle: 10;
case .rect: 20;
case .none: 30;
}
print("match-expr: {}\n", ms);
// Match expression with else
me_val := 42;
me_res := if me_val == {
case 1: 10;
case 2: 20;
else: 99;
}
print("match-expr-else: {}\n", me_res);
// Payload capture (block form)
sh4 : Shape = .circle(9.5);
if sh4 == {
case .circle: (r) { print("capture: {}\n", r); }
case .rect: (sz) { print("capture: {}\n", sz); }
case .none: print("capture: none\n");
}
// Payload capture (arrow form)
sh_ca : Shape = .circle(7.5);
if sh_ca == {
case .circle: (r) => print("capture-arrow: {}\n", r);
case .rect: (sz) => print("capture-arrow: rect\n");
case .none: print("capture-arrow: none\n");
}
// else arm in match
num := 42;
if num == {
case 1: print("else-match: one\n");
case 2: print("else-match: two\n");
else: print("else-match: other\n");
}
// Integer pattern matching
code := 2;
if code == {
case 1: print("int-match: one\n");
case 2: print("int-match: two\n");
case 3: print("int-match: three\n");
}
// Integer match with else
im_code := 99;
if im_code == {
case 1: print("int-match-else: one\n");
case 2: print("int-match-else: two\n");
else: print("int-match-else: unknown\n");
}
// Bool pattern matching
bm := true;
if bm == {
case true: print("bool-match-t: yes\n");
case false: print("bool-match-t: no\n");
}
bm2 := false;
if bm2 == {
case true: print("bool-match-f: yes\n");
case false: print("bool-match-f: no\n");
}
// Bool conditional
flag := true;
if flag { print("bool: true\n"); }
// --- Union (untagged) ---
o : Overlay = ---;
o.f = 3.14;
print("union-f: {}\n", o.f);
// Type punning — read same bits as i32
print("union-i: {}\n", o.i);
// Union member promotion
uv : Vec2 = ---;
uv.x = 1.0;
uv.y = 2.0;
print("promoted-x: {}\n", uv.x);
print("promoted-data0: {}\n", uv.data[0]);
// --- Arrays ---
arr : [5]i32 = .[10, 20, 30, 40, 50];
print("arr[2]: {}\n", arr[2]);
print("arr.len: {}\n", arr.len);
// Array element assignment
aa : [3]i32 = .[1, 2, 3];
aa[1] = 99;
print("arr-assign: {}\n", aa);
// --- Slices ---
sl : []i32 = .[1, 2, 3, 4, 5];
print("sl[0]: {}\n", sl[0]);
print("sl.len: {}\n", sl.len);
// Slice element write
sla : []i32 = .[10, 20, 30];
sla[1] = 55;
print("sl-assign: {}\n", sla);
// Subslicing
sub := arr[1..4];
print("sub: {}\n", sub);
head := arr[..3];
print("head: {}\n", head);
tail := arr[2..];
print("tail: {}\n", tail);
// Slice of slice
sos : []i32 = .[10, 20, 30, 40, 50];
mid := sos[1..4];
inner := mid[0..2];
print("slice-of-slice: {}\n", inner);
// String subslicing
msg := "hello world";
print("strsub: {}\n", msg[6..11]);
print("str-prefix: {}\n", msg[..5]);
print("str-suffix: {}\n", msg[6..]);
// --- Pointers ---
// Address-of global variable
write_to_ptr(@g_smoke_val);
print("global-addr-of: {}\n", g_smoke_val);
pv := Point.{ 10, 20 };
ptr := @pv;
print("deref: {}\n", ptr.*);
// Auto-deref
print("auto-deref: {}\n", ptr.x);
// Many-pointer
mp : [*]i32 = @arr[0];
print("mp[0]: {}\n", mp[0]);
print("mp[3]: {}\n", mp[3]);
// Many-pointer write
mpw : [5]i32 = .[10, 20, 30, 40, 50];
mpw_ptr : [*]i32 = @mpw[0];
mpw_ptr[2] = 99;
print("mp-write: {}\n", mpw[2]);
// Pointer-null comparison
np : *i32 = null;
print("ptr==null: {}\n", np == null);
print("ptr!=null: {}\n", np != null);
np2 := @pv.x;
print("ptr2==null: {}\n", np2 == null);
print("ptr2!=null: {}\n", np2 != null);
// Pointer to nested struct field
Inner3 :: struct { a: f32; b: f32; c: f32; }
Outer3 :: struct { key: i32; inner: Inner3; }
out3 := Outer3.{ key = 42, inner = Inner3.{ a = 1.0, b = 2.0, c = 3.0 } };
ip3 := @out3.inner;
print("ptr-nested-field: {} {} {}\n", ip3.a, ip3.b, ip3.c);
// Store to many-pointer field must not corrupt adjacent memory
MpHolder :: struct { items: [*]i64; sentinel: i64; }
mph := MpHolder.{ items = xx 0, sentinel = 42 };
mph.items = xx 0;
print("mp-store-sentinel: {}\n", mph.sentinel);
// --- Vectors ---
vc := vec3(1, 3, 2);
print("vec-construct: {}\n", vc);
va := vec3(1, 2, 3);
vb := vec3(4, 5, 6);
print("vec-add: {}\n", va + vb);
print("vec-sub: {}\n", vec3(5, 5, 5) - vec3(1, 2, 3));
print("vec-mul: {}\n", vec3(2, 3, 4) * vec3(1, 2, 3));
print("vec-div: {}\n", vec3(10, 9, 8) / vec3(2, 3, 4));
print("vec-scalar: {}\n", vec3(1, 3, 2) * 2.0);
print("vec-neg: {}\n", -vec3(1, 3, 2));
ve := vec3(10, 20, 30);
print("vec-x: {}\n", ve.x);
print("vec-y: {}\n", ve.y);
print("vec-z: {}\n", ve.z);
print("vec-idx: {}\n", ve[1]);
}

View File

@@ -0,0 +1,106 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Perms :: enum flags { read; write; execute; }
WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }
// --- Top-level functions ---
main :: () {
// ========================================================
// 9. FLAGS
// ========================================================
print("=== 9. Flags ===\n");
// Combine flags
perm : Perms = .read | .write;
print("flags: {}\n", perm);
// Test flag
if perm & .read { print("has-read: yes\n"); }
if perm & .execute { print("has-exec: yes\n"); }
// Test flag negative
pt : Perms = .write;
if pt & .read {
print("flags-neg: has-read\n");
} else {
print("flags-neg: no-read\n");
}
// Single flag
ps : Perms = .execute;
print("flags-single: {}\n", ps);
// All flags
pall : Perms = .read | .write | .execute;
print("flags-all: {}\n", pall);
// Cast to int
print("flags-raw: {}\n", cast(i64) perm);
// Flags with explicit values
wf : WindowFlags = .vsync | .resizable;
print("flags-explicit: {}\n", wf);
print("flags-explicit-raw: {}\n", cast(i64) wf);
// --- Multi-target assignment (swap) ---
print("--- swap ---\n");
// Variable swap
{
sa := 10;
sb := 20;
sa, sb = sb, sa;
print("var swap: {} {}\n", sa, sb);
}
// Array element swap
{
sarr : [3]i64 = .[1, 2, 3];
sarr[0], sarr[2] = sarr[2], sarr[0];
print("arr swap: {} {}\n", sarr[0], sarr[2]);
}
// 3-way rotation
{
ra := 1;
rb := 2;
rc := 3;
ra, rb, rc = rc, ra, rb;
print("3-way: {} {} {}\n", ra, rb, rc);
}
// --- Tuple destructuring ---
print("--- destructure ---\n");
// Basic tuple destructuring
{
da, db := (10, 20);
print("basic: {} {}\n", da, db);
}
// Destructure from function return
{
dswap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
dx, dy := dswap(1, 2);
print("fn: {} {}\n", dx, dy);
}
// Discard with _
{
_, dsecond := (100, 200);
print("discard: {}\n", dsecond);
}
// Three elements
{
da3, db3, dc3 := (1, 2, 3);
print("triple: {} {} {}\n", da3, db3, dc3);
}
}

View File

@@ -0,0 +1,24 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 16. COMPOUND ASSIGNMENT TYPE CONVERSION
// ========================================================
print("=== 16. Compound Assign ===\n");
{
ca_a : f64 = 10.0;
ca_b : f32 = 3.0;
ca_a += ca_b;
print("f64+=f32: {}\n", ca_a);
ca_c : i64 = 100;
ca_d : i32 = 7;
ca_c -= ca_d;
print("i64-=i32: {}\n", ca_c);
}
}

View File

@@ -0,0 +1,23 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
main :: () {
// ========================================================
// 18. ARRAYS OF USER-DEFINED TYPES
// ========================================================
print("=== 18. Array of Structs ===\n");
{
spts : [2]Point = .[Point.{1, 2}, Point.{3, 4}];
spt2 := spts[1];
print("arr-struct-x: {}\n", spt2.x);
for spts (it) {
print("for-struct: {}\n", it);
}
}
}

View File

@@ -0,0 +1,13 @@
// A local declared with a reserved/builtin type-name spelling (`i2` is the
// arbitrary-width `sN` integer type) is rejected at the declaration site.
// Previously such a name parsed as a `.type_expr`, so address-of sites
// mis-lowered it (load-by-value to a `ptr` param → LLVM verifier abort, or a
// silent `*self`-mutation-losing copy). Regression (issue 0076). Expected:
// error at the declaration; exit 1.
#import "modules/std.sx";
main :: () -> i32 {
i2 := 42;
print("i2: {}\n", i2);
return 0;
}

View File

@@ -0,0 +1,20 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 23. NESTED ARRAYS (2D)
// ========================================================
print("=== 23. Nested Arrays ===\n");
{
matrix : [2][3]i32 = .[ .[1, 2, 3], .[4, 5, 6] ];
print("m[0][0]: {}\n", matrix[0][0]);
print("m[0][2]: {}\n", matrix[0][2]);
print("m[1][0]: {}\n", matrix[1][0]);
print("m[1][2]: {}\n", matrix[1][2]);
}
}

View File

@@ -0,0 +1,47 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// === 26. #using struct composition ===
print("=== 26. #using ===\n");
{
UBase :: struct { x: i32; y: i32; }
UExt :: struct { #using UBase; z: i32; }
e := UExt.{ x = 1, y = 2, z = 3 };
print("using-x: {}\n", e.x);
print("using-y: {}\n", e.y);
print("using-z: {}\n", e.z);
// #using in middle position
UHeader :: struct { version: i32; }
UPacket :: struct { id: i32; #using UHeader; payload: i32; }
p := UPacket.{ id = 10, version = 42, payload = 99 };
print("pkt-id: {}\n", p.id);
print("pkt-ver: {}\n", p.version);
print("pkt-pay: {}\n", p.payload);
// Multiple #using
UPos :: struct { px: i32; py: i32; }
UCol :: struct { r: i32; g: i32; }
USprite :: struct { #using UPos; #using UCol; scale: i32; }
s := USprite.{ px = 10, py = 20, r = 255, g = 128, scale = 1 };
print("sprite-px: {}\n", s.px);
print("sprite-r: {}\n", s.r);
print("sprite-scale: {}\n", s.scale);
}
// --- Comptime format ---
{
ct_body :: "hello";
ct_msg :: format("say: {} (len={})", ct_body, ct_body.len);
print("{}\n", ct_msg);
ct_num :: 42;
ct_num_msg :: format("n={}", ct_num);
print("{}\n", ct_num_msg);
}
}

View File

@@ -0,0 +1,27 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// --- Tuples ---
{
print("=== Tuples ===\n");
pair := (40, 2);
print("{}\n", pair.0);
print("{}\n", pair.1);
named := (x: 10, y: 20);
print("{}\n", named.x);
print("{}\n", named.0);
single := (42,);
print("{}\n", single.0);
zeroed : (i32, i32) = ---;
print("{}\n", zeroed.0);
print("{}\n", zeroed.1);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
// ============================================================
// Struct constants test
Phys :: struct {
x, y: f32;
GRAVITY :f32: 9.81;
MAX_SPEED :: 100;
}
// Init block test struct
main :: () {
// --- Struct Constants ---
print("=== Struct Constants ===\n");
{
print("gravity: {}\n", Phys.GRAVITY); // gravity: 9.810000
print("max speed: {}\n", Phys.MAX_SPEED); // max speed: 100
p := Phys.{ x = 0.0, y = Phys.GRAVITY };
print("p.y: {}\n", p.y); // p.y: 9.810000
}
}

View File

@@ -0,0 +1,200 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
add :: (a: i32, b: i32) -> i32 { a + b }
Counter :: protocol {
inc :: (self: *Self);
get :: (self: *Self) -> i32;
}
Summable :: protocol {
sum :: (self: *Self) -> i32;
}
SimpleCounter :: struct { val: i32; }
impl Counter for SimpleCounter {
inc :: (self: *SimpleCounter) { self.val += 1; }
get :: (self: *SimpleCounter) -> i32 { self.val }
}
impl Summable for Point {
sum :: (self: *Point) -> i32 { self.x + self.y }
}
// Phase 2: #inline protocol for dynamic dispatch
// Phase 7: Generic struct impls
Pair :: struct ($T: Type) {
a: T;
b: T;
}
impl Summable for Pair($T) {
sum :: (self: *Pair(T)) -> i32 {
xx self.a + xx self.b
}
}
// P6.5: Struct type param constraints
// Init block test struct
Builder :: struct {
total: i32;
count: i32;
add :: (self: *Builder, val: i32) {
self.total += val;
self.count += 1;
}
}
// Global variable for address-of test
main :: () {
// --- Init Blocks (IB) ---
print("=== Init Blocks ===\n");
// IB1: basic init block with struct methods
{
b := Builder.{ total = 0, count = 0 } {
self.add(10);
self.add(20);
self.add(30);
};
print("IB1: {} {}\n", b.total, b.count);
}
// IB2: nested init blocks (self shadows correctly)
{
b1 := Builder.{ total = 0, count = 0 } {
self.add(100);
b2 := Builder.{ total = 0, count = 0 } {
self.add(42);
};
self.add(b2.total);
};
print("IB2: {} {}\n", b1.total, b1.count);
}
// IB3: empty init block
{
b := Builder.{ total = 5, count = 1 } {};
print("IB3: {} {}\n", b.total, b.count);
}
// IB4: conditional inside init block
{
add_extra := true;
b := Builder.{ total = 0, count = 0 } {
self.add(10);
if add_extra {
self.add(90);
}
};
print("IB4: {}\n", b.total);
}
// IB5: init block + auto type erasure combined
{
use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
result := use_counter(SimpleCounter.{ val = 0 } {
self.val = 50;
});
print("IB5: {}\n", result);
}
// ============================================================
// SECTION: Struct static method shorthand (.method(args) syntax)
// ============================================================
print("--- struct static method shorthand ---\n");
// SM1: Basic shorthand — .create(args) resolves to Dims.create(args)
{
Dims :: struct {
w: f32;
h: f32;
create :: (w: f32, h: f32) -> Dims {
Dims.{ w = w, h = h }
}
square :: (size: f32) -> Dims {
Dims.{ w = size, h = size }
}
}
use_dims :: (d: Dims) { print("SM1: {} {}\n", d.w, d.h); }
use_dims(.create(16.0, 8.0));
use_dims(.square(5.0));
}
// SM2: Shorthand in variable declaration with explicit type
{
Pair :: struct {
a: i64;
b: i64;
make :: (a: i64, b: i64) -> Pair {
Pair.{ a = a, b = b }
}
}
p : Pair = .make(10, 20);
print("SM2: {} {}\n", p.a, p.b);
}
// ============================================================
// OPTIONAL IF-ELSE COERCION
// ============================================================
{
print("--- optional if-else coercion ---\n");
OptF :: struct { width: ?f32; }
x :f32: 10.0;
// null in then branch
f1 := OptF.{ width = if true then null else x };
print("opt-if1: {}\n", f1.width ?? 99.0);
// value in then branch, null in else
f2 := OptF.{ width = if true then x else null };
print("opt-if2: {}\n", f2.width ?? 99.0);
// both branches are values
f3 := OptF.{ width = if false then 5.0 else x };
print("opt-if3: {}\n", f3.width ?? 99.0);
// standalone optional variable
val: ?f32 = if true then null else 42.0;
print("opt-if4: {}\n", val ?? 0.0);
val2: ?f32 = if false then null else 42.0;
print("opt-if5: {}\n", val2 ?? 0.0);
}
// --- usize / isize ---
{
a : usize = 42;
b : isize = 0 - 7;
print("usize: {}\n", a);
print("isize: {}\n", b);
// arithmetic
c : usize = a + 8;
print("usize+8: {}\n", c);
// coercion from i32
x : i32 = 10;
y : usize = xx x;
print("i32->usize: {}\n", y);
// coercion to i64
z : i64 = xx a;
print("usize->i64: {}\n", z);
}
}

View File

@@ -0,0 +1,24 @@
// Forward identifier type alias — an alias whose target is declared LATER
// in the file resolves the same as an ordered one. `MyChain :: MyInt;`
// appears before `MyInt :: i32;`, yet `MyChain` resolves to `i32` and a
// forward chain (`A :: B; B :: C; C :: u8;`) converges too.
// Regression (issue 0069): the scan only registered identifier aliases whose
// target was already known, so a forward alias was falsely flagged
// `unknown type`. Now a fixpoint pass over the scanned decls resolves them.
#import "modules/std.sx";
MyChain :: MyInt;
MyInt :: i32;
A :: B;
B :: C;
C :: u8;
main :: () -> i32 {
v: MyChain = 7;
n: A = 3;
print("chain i32: {}\n", size_of(MyChain));
print("forward u8: {}\n", size_of(A));
print("v + n: {}\n", v + cast(i32) n);
return v;
}

View File

@@ -0,0 +1,22 @@
// Forward identifier type alias as a TOP-LEVEL annotation — a global var
// and a typed module constant whose annotation is a forward alias
// (`A :: B; B :: i32;`) resolve to the alias target, the same as the
// ordered form, instead of a fabricated stub.
// Regression (issue 0070): top-level global / typed-const annotations were
// resolved inside the scan loop BEFORE the forward-alias fixpoint ran, so
// `g : A` got a stub type that mismatched its initializer at LLVM
// verification. Global/const annotation resolution now runs in scan pass 2,
// after the fixpoint.
#import "modules/std.sx";
A :: B;
B :: i32;
g : A = 7;
K : A : 35;
main :: () -> i32 {
print("global g: {}\n", g);
print("const K: {}\n", K);
return g + K;
}

View File

@@ -0,0 +1,18 @@
// Top-level global initialized from a module constant copies the constant's
// value (not a silent zero). `K : A : 42; g : A = K;` resolves the forward
// alias `A` to `i32` and materializes `g`'s static initializer from `K`.
// Regression (issue 0071): `registerTopLevelGlobal`'s init_val switch only
// handled literals/array/struct literals; an identifier initializer fell
// through to a null payload and the global silently zero-initialized.
#import "modules/std.sx";
A :: B;
B :: i32;
K : A : 42;
g : A = K;
main :: () -> i32 {
print("g={}\n", g);
return g;
}

View File

@@ -0,0 +1,30 @@
// A `*self`-mutating streaming pattern with NON-reserved binding names
// (`hasher`, `ctx`) compiles and accumulates state correctly through BOTH
// call styles — explicit address-of `update(@h, ...)` and autoref
// `h.update(...)` — across multiple mutating calls. Proves the
// `.identifier`-only address-of paths in lowering are correct as-is, with no
// type-shaped-name special-case (companion to the issue-0076 rejection of
// type-named identifiers).
#import "modules/std.sx";
Hasher :: struct { total: i64 = 0; count: i64 = 0; }
update :: ufcs (self: *Hasher, n: i64) {
self.total += n;
self.count += 1;
}
main :: () -> i32 {
hasher := Hasher.{ total = 0, count = 0 };
update(@hasher, 10); // explicit address-of receiver
hasher.update(20); // autoref receiver
update(@hasher, 30);
hasher.update(40);
print("hasher total={} count={}\n", hasher.total, hasher.count);
ctx := Hasher.{ total = 100, count = 0 };
ctx.update(5);
update(@ctx, 7);
print("ctx total={} count={}\n", ctx.total, ctx.count);
return 0;
}

View File

@@ -0,0 +1,57 @@
// A store to a module-global array element writes the global's live storage,
// so a subsequent read sees the stored value — not the array initializer.
// Covers constant index, variable index, and a cross-function store, on a
// scalar global array, a struct-element global array (element-stride), and a
// nested-array global (recursive lvalue).
// Regression (issue 0079): global-array element stores were silently dropped
// (read returned the initializer) because the indexed lvalue base loaded the
// global by value into a temp instead of addressing the global's storage.
#import "modules/std.sx";
g : [3]i64 = .[10, 20, 30];
Pair :: struct { a: i64; b: i64; }
gp : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
grid : [2][3]i64 = .[ .[0, 0, 0], .[0, 0, 0] ];
write_global :: (i: i64, v: i64) { g[i] = v; }
main :: () {
// Scalar global array — const index.
g[1] = 222;
print("g[1]={}\n", g[1]); // 222
// Scalar global array — variable index.
k := 2;
g[k] = 333;
print("g[k]={}\n", g[k]); // 333
// Scalar global array — store from another function.
write_global(0, 111);
print("g[0]={}\n", g[0]); // 111
// Struct-element global array (16-byte stride) — const and var index.
gp[0] = .{ a = 10, b = 20 };
j := 1;
gp[j] = .{ a = 30, b = 40 };
print("gp[0]={},{}\n", gp[0].a, gp[0].b); // 10,20
print("gp[j]={},{}\n", gp[j].a, gp[j].b); // 30,40
// Nested-array global — element is [3]i64, recursive indexed lvalue.
grid[1][2] = 7;
r := 0;
grid[r][0] = 5;
print("grid[1][2]={}\n", grid[1][2]); // 7
print("grid[0][0]={}\n", grid[r][0]); // 5
if g[1] == 222 and g[2] == 333 and g[0] == 111
and gp[0].a == 10 and gp[0].b == 20
and gp[1].a == 30 and gp[1].b == 40
and grid[1][2] == 7 and grid[0][0] == 5 {
print("PASS\n");
} else {
print("FAIL: global array element store dropped\n");
}
}

View File

@@ -0,0 +1,49 @@
// A module-global aggregate (array of struct literals, a struct literal, and
// nested array/struct shapes) materializes its DECLARED field values into the
// global's static initializer, so reading the fields without any prior store
// returns the literal values — not zero.
// Regression (issue 0080): a global `[N]Struct` initialized with struct literals
// was emitted as `zeroinitializer`, silently dropping every field, because the
// constant-aggregate serializer had no struct-literal arm and collapsed the
// whole initializer to null. The fix threads the element/field type so struct
// and nested-array leaves serialize correctly; a genuinely non-constant
// initializer is now rejected loudly instead of silently zeroed.
#import "modules/std.sx";
Pair :: struct { a: i64; b: i64; }
WithArr :: struct { id: i64; xs: [3]i64; }
// global array of struct literals
pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
// global struct literal
solo : Pair = .{ a = 7, b = 9 };
// global struct containing a fixed array (struct-with-array)
wa : WithArr = .{ id = 5, xs = .[ 11, 22, 33 ] };
// nested: global array of structs each containing an array
nested : [2]WithArr = .[ .{ id = 1, xs = .[ 1, 2, 3 ] }, .{ id = 2, xs = .[ 4, 5, 6 ] } ];
main :: () {
// Read the declared initializer values back with NO prior store.
print("pairs={},{} {},{}\n", pairs[0].a, pairs[0].b, pairs[1].a, pairs[1].b);
print("solo={},{}\n", solo.a, solo.b);
print("wa={} xs={},{},{}\n", wa.id, wa.xs[0], wa.xs[1], wa.xs[2]);
print("nested0={} xs={},{},{}\n", nested[0].id, nested[0].xs[0], nested[0].xs[1], nested[0].xs[2]);
print("nested1={} xs={},{},{}\n", nested[1].id, nested[1].xs[0], nested[1].xs[1], nested[1].xs[2]);
// A store on top of the materialized initializer still works (live storage).
pairs[0].a = 100;
nested[1].xs[2] = 999;
print("after-store={} {}\n", pairs[0].a, nested[1].xs[2]);
if pairs[0].b == 2 and pairs[1].a == 3 and pairs[1].b == 4
and solo.a == 7 and solo.b == 9
and wa.id == 5 and wa.xs[0] == 11 and wa.xs[2] == 33
and nested[0].id == 1 and nested[0].xs[0] == 1 and nested[0].xs[2] == 3
and nested[1].id == 2 and nested[1].xs[0] == 4
and pairs[0].a == 100 and nested[1].xs[2] == 999 {
print("PASS\n");
} else {
print("FAIL: global aggregate literal initializer zeroed\n");
}
}

View File

@@ -0,0 +1,48 @@
// A module-global aggregate initializer may carry `null` in a pointer field:
// `null` is a compile-time constant (the zero pointer), so the field reads back
// as null with NO prior store, and its non-pointer neighbors keep their declared
// values. Covered shapes: an array-of-struct with a null pointer field, a global
// array of all-null pointers, and a nested struct-in-struct with a null pointer.
// Regression (issue 0081): the constant-aggregate serializer had no
// `.null_literal` arm, so a `null` in a pointer field made the whole aggregate
// look non-constant and the global was rejected with "must be initialized by a
// compile-time constant". The fix serializes a null literal to a constant zero
// pointer (the same way a top-level pointer global `p : *i64 = null;` does)
// while still rejecting genuinely non-constant fields (see diagnostics 1126).
#import "modules/std.sx";
Box :: struct { p: *i64; marker: i64; }
Inner :: struct { q: *i64; tag: i64; }
Outer :: struct { inner: Inner; label: i64; }
// array-of-struct with null pointer fields + scalar neighbors
boxes : [2]Box = .[ .{ p = null, marker = 11 }, .{ p = null, marker = 22 } ];
// global array of all-null pointers
ptrs : [3]*i64 = .[ null, null, null ];
// nested: struct containing a struct with a null pointer field
nested : [2]Outer = .[
.{ inner = .{ q = null, tag = 1 }, label = 100 },
.{ inner = .{ q = null, tag = 2 }, label = 200 },
];
main :: () {
print("boxes ptrs={},{} markers={},{}\n",
boxes[0].p == null, boxes[1].p == null, boxes[0].marker, boxes[1].marker);
print("ptr arr nulls={},{},{}\n", ptrs[0] == null, ptrs[1] == null, ptrs[2] == null);
print("nested q nulls={},{} tags={},{} labels={},{}\n",
nested[0].inner.q == null, nested[1].inner.q == null,
nested[0].inner.tag, nested[1].inner.tag,
nested[0].label, nested[1].label);
if boxes[0].p == null and boxes[1].p == null
and boxes[0].marker == 11 and boxes[1].marker == 22
and ptrs[0] == null and ptrs[1] == null and ptrs[2] == null
and nested[0].inner.q == null and nested[1].inner.q == null
and nested[0].inner.tag == 1 and nested[1].inner.tag == 2
and nested[0].label == 100 and nested[1].label == 200 {
print("PASS\n");
} else {
print("FAIL: global aggregate null pointer field mis-serialized\n");
}
}

View File

@@ -0,0 +1,51 @@
// A module-global initialized with an enum literal (`.Variant`) reads back the
// declared tag — scalar, inside a global array, and as a struct field, for both
// a plain enum (tag == declaration index) and an explicit-value enum (`enum u16
// { ok :: 200; ... }`, larger backing for element-stride coverage).
// Regression (issue 0082): `globalInitValue` had an `.enum_literal => null`
// carve-out (kept for the compiler-injected `OS`/`ARCH` globals) that silently
// zero-initialized EVERY enum global to the first tag — so `chosen : Color =
// .green` read back as `.red` — while a global array/struct of enums was
// rejected outright as non-constant. The fix serializes the enum literal to its
// tag value (respecting explicit variant values) against the destination enum
// type, for the scalar global, the array element, and the nested aggregate
// field. (Explicit-value enums print as `.` because the `{}` formatter indexes
// variants by position — a separate, pre-existing limitation — so those are
// asserted by equality, not by their printed name.)
#import "modules/std.sx";
Color :: enum u8 { red; green; blue; }
Code :: enum u16 { ok :: 200; not_found :: 404; teapot :: 418; }
Pair :: struct { a: Color; b: Color; }
Row :: struct { status: Code; pad: i64; }
// scalar enum global
chosen : Color = .green;
// global array of enum
palette : [3]Color = .[ .blue, .green, .red ];
// enum field(s) inside a global struct
pair : Pair = .{ a = .blue, b = .green };
// explicit-value enum: scalar, array (2-byte stride), and inside a struct array
status : Code = .teapot;
codes : [3]Code = .[ .ok, .not_found, .teapot ];
rows : [2]Row = .[ .{ status = .not_found, pad = 11 }, .{ status = .teapot, pad = 22 } ];
main :: () {
print("chosen={}\n", chosen);
print("palette={},{},{}\n", palette[0], palette[1], palette[2]);
print("pair.a={} pair.b={}\n", pair.a, pair.b);
if chosen == .green
and palette[0] == .blue and palette[1] == .green and palette[2] == .red
and pair.a == .blue and pair.b == .green
and status == .teapot
and codes[0] == .ok and codes[1] == .not_found and codes[2] == .teapot
and rows[0].status == .not_found and rows[0].pad == 11
and rows[1].status == .teapot and rows[1].pad == 22 {
print("PASS\n");
} else {
print("FAIL: global enum-literal initializer mis-serialized\n");
}
}

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: i64; y: i64; }
// Type aliases whose dimension is the named const N (stateless registration).
Arr :: [N]i64;
SArr :: [N]string;
// Inline union field with a named-const dimension (stateless registration).
U :: union { a: [N]i64; tag: i64; }
main :: () {
// Scalar elements (direct local): store then read back.
a : [N]i64 = ---;
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 i0={} i1={}\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]i64`.
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 i0={} i2={}\n", sa[0], sa[2]);
// Nested fixed array `[N][M]i64`: both dimensions are named consts.
grid : [N][M]i64 = ---;
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) -> i64 {
n := 0;
i := 0;
while i < xs.len { if xs[i] == "nope" { n += 1; } i += 1; }
return n;
}
sum :: (xs: []i64) -> i64 {
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 : []i64 = .[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 (`[][]i64`) 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: [][]i64) -> i64 {
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) -> i64 {
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 [][]i64 — local-bound vs direct-arg both sum to 10.
local : [][]i64 = .[.[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 : i64 : 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 : i64 : 8; // typed const used as a dimension
P :: struct { x: i64; y: i64; }
// Type aliases whose dimension is the TYPED const NT (stateless registration).
TArr :: [NT]i64;
TSArr :: [NT]string;
TPArr :: [NT]P;
// Forward reference: this alias is declared BEFORE its dimension const NF.
FArr :: [NF]i64;
NF :: 5;
main :: () {
// Typed-const dimension, DIRECT local decl.
d : [NT]i64 = ---;
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 i0={} i7={}\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]i64 = ---;
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 : i64 : 2; // typed const, used inside an expression dimension
P :: struct { x: i64; y: i64; }
AddAlias :: [M + 1]i64; // 5
MulAlias :: [M * N]i64; // 24
SubAlias :: [N - M]i64; // 2
NestAlias :: [M + N - 1]i64; // 9
ParenAlias :: [(M + 1) * 2]i64; // 10
TypedAlias :: [M + TK]i64; // 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]i64 = ---;
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]i64 = ---;
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]i64 = ---;
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]i64 = ---;
nest_a : NestAlias = ---;
paren_d : [(M + 1) * 2]i64 = ---;
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]i64 = ---;
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={} i0={} i4={}\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]i64`) all lay out the same
// `[4]i64` 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]i64 = ---; // 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]i64 = ---; // dim from an untyped float const
b[1] = 21;
print("b len={} b1={}\n", b.len, b[1]);
c : [4.0]i64 = ---; // 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 : i64 : 5`), an integral float const
// (`F :: 4.0` ≡ 4), and an ALIASED integer constraint (`Count :: u32`,
// `Small :: i8`) 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 : i64 : 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 :: i8; // integer ALIAS — value-param constraint
ArrN :: [N]i64; // array dim via alias: expression const (3)
ArrF :: [F]i64; // array dim via alias: integral float (4)
ArrS :: [S]i64; // 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 i8 constraint
Make :: ($K: u32, $T: Type) -> Type { return [K]T; } // type-fn value-param
main :: () {
// array dimension — DIRECT
a : [N]i64 = ---; a[0] = 7; a[2] = 9;
print("dim.direct.expr: len={} a0={} a2={}\n", a.len, a[0], a[2]);
f : [F]i64 = ---; 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 i8
bn : Buf(N, i64) = ---; bn.data[2] = 30; print("vp.struct.expr: len={} v={}\n", bn.data.len, bn.data[2]);
bc : BufC(KU, i64) = ---; bc.data[2] = 31; print("vp.struct.alias.u32: len={} v={}\n", bc.data.len, bc.data[2]);
bs : BufS(4, i64) = ---; bs.data[3] = 32; print("vp.struct.alias.i8: len={} v={}\n", bs.data.len, bs.data[3]);
// generic value-param — type-fn binder: expr const
mk : Make(N, i64) = ---; 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]i64; }
main :: () {
a : [0]i64 = ---;
print("array_dim={}\n", a.len);
b : Box(0) = ---;
print("value_param={}\n", b.items.len);
}

View File

@@ -0,0 +1,65 @@
// Integer numeric-limit accessors: `<IntType>.min` / `.max` fold to a
// compile-time constant of the QUERIED integer type, driven by the
// (width, signedness) arithmetic (`sN`: min=-(2^(N-1)), max=2^(N-1)-1; `uN`:
// min=0, max=2^N-1) — every width 1..64, not just the power-of-two ones, plus
// `usize`/`isize` (target-width). Usable in expressions and in array-dimension
// position via the comptime-int path (`[u8.max]T`).
//
// The extreme values that the i64-based integer formatter cannot render
// directly — `i64.min` (i64::MIN) and the all-ones `u64.max`/`usize.max` — are
// asserted EXACTLY via comparison and untagged-union bit reinterpret, never via
// the formatter (which prints i64::MIN as a bare "-" and u64.max as "-1").
#import "modules/std.sx";
// Untagged union for the exact u64.max bit-reinterpret check.
UU :: union { u: u64; s: i64; }
main :: () -> i32 {
// Sub-byte widths — arbitrary bit-width arithmetic, not a per-name table.
print("i1.min={} i1.max={}\n", i1.min, i1.max); // -1 0
print("i2.min={} i2.max={}\n", i2.min, i2.max); // -2 1
print("i3.max={}\n", i3.max); // 3
print("u1.min={} u1.max={}\n", u1.min, u1.max); // 0 1
print("u2.max={}\n", u2.max); // 3
// Byte / word widths.
print("i8.min={} i8.max={}\n", i8.min, i8.max); // -128 127
print("u8.max={}\n", u8.max); // 255
print("i32.min={} i32.max={}\n", i32.min, i32.max); // -2147483648 2147483647
// i64 extremes: max prints; min (i64::MIN) is pinned by relation since the
// formatter cannot render it (this is independent of this feature).
print("i64.max={}\n", i64.max); // 9223372036854775807
print("i64.min+1 == -(i64.max): {}\n", i64.min + 1 == -9223372036854775807); // true
print("i64.min + i64.max == -1: {}\n", i64.min + i64.max == -1); // true
// u64.max / usize.max = all-ones (18446744073709551615); reinterpret to i64
// to confirm the bit pattern is -1 (and NOT a mangled value).
o : UU = ---;
o.u = u64.max;
print("u64.max as i64 == -1: {}\n", o.s == -1); // true
o.u = usize.max;
print("usize.max as i64 == -1: {}\n", o.s == -1); // true (host = u64)
print("usize.max == u64.max: {}\n", usize.max == u64.max); // true
print("isize.min == i64.min: {}\n", isize.min == i64.min); // true (host = i64)
// Result carries the QUERIED type: each binding is declared with the queried
// type and round-trips, so a mistyped fold (e.g. boxed as Any / widened)
// would not type-check here.
m3 : i3 = i3.max;
mu : u8 = u8.max;
ms : i8 = i8.min;
print("typed: m3={} mu={} ms={}\n", m3, mu, ms); // 3 255 -128
// Array-dimension / comptime-int path: `[u8.max]T` and `[i16.max]T` are
// valid counts (255 and 32767), usable end-to-end.
a : [u8.max]u8 = ---;
a[254] = 7;
print("[u8.max]u8 len={} a[254]={}\n", a.len, a[254]); // 255 7
b : [i16.max]u8 = ---;
b[32766] = 9;
print("[i16.max]u8 len={} b[32766]={}\n", b.len, b[32766]); // 32767 9
return 0;
}

View File

@@ -0,0 +1,18 @@
// Numeric-limit accessors apply only to numeric types. `.min`/`.max` on a
// NON-numeric receiver is a clean compile error (never a silent value, never
// the `.unresolved` sentinel reaching codegen):
// - a builtin non-numeric type (`bool`, `void`, `string`) → a dedicated
// "type 'X' has no '.min'/'.max'" diagnostic from the accessor intercept;
// - a user struct (`MyStruct`) → the type name is not a builtin, so the
// intercept stays out and the existing field-not-found path reports it.
// Each case is accurate and located at the access; the program exits non-zero.
#import "modules/std.sx";
MyStruct :: struct { a: i64; }
main :: () -> i32 {
b := bool.max;
s := MyStruct.min;
v := void.max;
return 0;
}

View File

@@ -0,0 +1,35 @@
// Float `!=` is UNORDERED not-equal: `nan != nan` is true (the canonical
// `x != x` NaN idiom), and `!=` is the exact complement of `==` for every
// float input — including NaN, where `nan == nan` is false (ordered `==`).
// For all non-NaN operands unordered `!=` matches ordered `!=`, so finite
// comparisons are unchanged. The native backend agrees with the interpreter.
//
// Regression (issue 0091): the LLVM backend lowered float `!=` to ordered
// not-equal (LLVMRealONE), so `nan != nan` was false in native code.
#import "modules/std.sx";
main :: () {
// Produce a genuine NaN without any numeric-limit accessor: 0.0 / 0.0.
z := 0.0;
nan := z / z;
// The fix: `!=` is unordered, `==` is ordered.
print("nan != nan: {}\n", nan != nan); // true
print("nan == nan: {}\n", nan == nan); // false
print("nan != 1.0: {}\n", nan != 1.0); // true
print("nan == 1.0: {}\n", nan == 1.0); // false
// Complementarity holds for finite operands too (unchanged behavior).
print("1.0 != 2.0: {}\n", 1.0 != 2.0); // true
print("1.0 != 1.0: {}\n", 1.0 != 1.0); // false
print("2.0 != 2.0: {}\n", 2.0 != 2.0); // false
// Native codegen converges with the comptime interpreter.
print("comptime nan != nan: {}\n", #run nan_ne_nan());
}
nan_ne_nan :: () -> bool {
z := 0.0;
n := z / z;
return n != n;
}

View File

@@ -0,0 +1,35 @@
// Backtick raw-identifier escape: a leading backtick makes the following
// identifier RAW — its text excludes the backtick and it is never the
// reserved/builtin keyword, so a reserved type-name spelling (`i2`, `u8`, …)
// can be used as an ordinary identifier. Exercised in every VALUE position:
// global, local, param, struct field + member access, function name + call,
// and a later reference. (A raw identifier in TYPE position references a
// backtick-declared type instead — see examples/0154.) A *bare* `i2` is still
// the reserved type name (see examples/1119), so the escape is the only way to
// spell these as values.
// Regression (issue 0089).
#import "modules/std.sx";
// Global named with a reserved type spelling.
`u8 := 100;
// Function whose name is a reserved type spelling, with a reserved-name param.
`i2 :: (`i1: i64) -> i64 { return `i1 * 2; }
Point :: struct {
`i2: f64; // field name is a reserved type spelling
`u16: i64;
}
main :: () {
// Local with a reserved type spelling; later reference resolves to it.
`i64 := 7;
`i64 = `i64 + 1;
print("local = {}\n", `i64);
print("global = {}\n", `u8);
print("fn = {}\n", `i2(21)); // calls the `i2 function
p := Point.{ `i2 = 2.5, `u16 = 9 };
print("field = {} {}\n", p.`i2, p.`u16);
}

View File

@@ -0,0 +1,57 @@
// Backtick raw identifier across every control-flow / capture / binding form,
// plus bare later uses. A reserved type-name spelling (`i2`, `u8`, …) works as a
// binding name in a destructure, an `if`/`while` optional binding, a `for`
// capture + index, and a match-arm capture; a backtick-named function is
// bare-callable; and a backtick struct field is bare- or backtick-accessible.
// The escape is needed only at the binding site — a later BARE reference / call
// / member access resolves to the binding. A *bare* binding name is still the
// reserved type (see examples/1121), so the escape is the only way to spell
// these as values.
// Regression (issue 0089 — attempt-2 completeness across binding forms).
#import "modules/std.sx";
pair :: () -> (i64, i64) { (1, 2) }
maybe :: () -> ?i64 { return 42; }
// Function named with a reserved spelling — bare-callable (no backtick at call).
`i2 :: (n: i64) -> i64 { return n + 1; }
Quad :: struct { `i1: i32; `i2: i32; }
main :: () -> i32 {
// destructure binding names
`u8, rest := pair();
print("dstr = {} {}\n", `u8, rest);
// if optional binding + bare-position reference inside the branch
if `i16 := maybe() {
print("if = {}\n", `i16);
}
// while optional binding (name only — the while binding isn't body-exposed)
while `i32 := maybe() {
break;
}
// for capture + index names
xs := [3]i64.{ 10, 20, 30 };
for xs, 0.. (`bool, `u16) {
print("for = {} @ {}\n", `bool, `u16);
}
// match-arm capture
opt: ?i64 = 5;
m := if opt == {
case .some: (`string) { `string * 2 }
case .none: { 0 }
};
print("match = {}\n", m);
// backtick function called BARE and via backtick — both resolve to the fn
print("call = {} {}\n", i2(10), `i2(10));
// struct field named with a reserved spelling: bare + backtick member access
q := Quad.{ `i1 = 7, `i2 = 9 };
print("field = {} {} | {} {}\n", q.i1, q.i2, q.`i1, q.`i2);
return 0;
}

View File

@@ -0,0 +1,23 @@
// Backtick raw-identifier escape at the `::` declaration sites: a leading
// backtick makes a CONSTANT name and a FUNCTION name raw, so a reserved type
// spelling (`i2`, `u8`) can be declared and used. Complements examples/0151
// (var / param / field / global). The backtick fn is callable both via the
// backtick (`` `u8(5) ``) and bare (`u8(5)`) — the bare reserved-name callee
// resolves to the raw fn because its declaration is raw (issue 0089). A *bare*
// `i2 :: …` / `u8 :: …` declaration is still the reserved-name error (see
// examples/1140).
// Regression (issue 0089).
#import "modules/std.sx";
// Constant whose name is a reserved type spelling.
`i2 :: 2.5;
// Function whose name is a reserved type spelling.
`u8 :: (n: i64) -> i64 { return n + 7; }
main :: () -> i32 {
print("const = {}\n", `i2);
print("fn tick = {}\n", `u8(5));
print("fn bare = {}\n", u8(5));
return 0;
}

View File

@@ -0,0 +1,42 @@
// Backtick raw identifier in TYPE position (the universal model, issue 0089):
// `` `name `` is the LITERAL identifier `name` used as a type reference, never
// the builtin/reserved spelling. A reserved type spelling (`i2`, `u8`, …) can
// therefore both DECLARE a type (struct / enum / union / error-set / alias) and
// be REFERENCED as that type via the backtick — while a BARE `i2` in type
// position remains the signed-int type (see `add` below) and a bare reserved-
// name declaration still errors (see examples/1141). The backtick is required
// to declare or reference these names; it is never part of the name's text.
// Regression (issue 0089 — attempt-4 universal raw identifier).
#import "modules/std.sx";
// Type-introducing decls whose NAME is a reserved spelling.
`i2 :: struct { x: i64; }
`i8 :: enum { A; B; }
`u16 :: union { i: i32; f: f32; }
`u32 :: error { Bad, Empty }
RawAlias :: `i2; // alias to a backtick-declared struct
// A bare `i2` in type position is still the 2-bit signed int.
add :: (a: i2, b: i2) -> i2 { return a + b; }
main :: () -> i32 {
// Reference the backtick struct as a type; field access works.
v : `i2 = ---;
v.x = 7;
// Reference via a normal alias too.
a : RawAlias = ---;
a.x = 11;
// Backtick enum / union type references.
e : `i8 = .A;
u : `u16 = ---;
u.i = 5;
print("struct = {}\n", v.x);
print("alias = {}\n", a.x);
print("enum = {}\n", e == .A);
print("union = {}\n", u.i);
print("bare = {}\n", add(1, 0)); // bare i2 = the 2-bit int type
return 0;
}

View File

@@ -0,0 +1,24 @@
// Backtick raw identifier at the two remaining binding positions (issue 0089,
// attempt-4): a TYPED constant (`` `i2 : i64 : 5 ``) and a union TAG / field
// (`` `i2: i32 ``). The typed-const form previously slipped past the decl check
// without a name span (caret at 1:1); a bare `i2 : i64 : 5` is still rejected
// with the caret ON the name (see examples/1141). A union tag spelled with a
// reserved name works and is accessible bare or backticked.
// Regression (issue 0089 — attempt-4 typed const + union tag).
#import "modules/std.sx";
// Typed constant whose name is a reserved type spelling.
`i2 : i64 : 5;
// Union whose tags are reserved type spellings.
Mix :: union { `i1: i32; `u8: f32; }
main :: () -> i32 {
print("typed const = {}\n", `i2);
m : Mix = ---;
m.`i1 = 42;
print("union tick = {}\n", m.`i1); // backtick member access
print("union bare = {}\n", m.i1); // bare member access — same field
return 0;
}

View File

@@ -0,0 +1,21 @@
// Backtick raw-identifier escape at a STRUCT-BODY constant — both the untyped
// `` `name :: value `` and the typed `` `name : T : value `` forms. A struct
// member constant is a binding site like any top-level const (examples/0153),
// so a reserved type spelling (`i2`, `u8`) needs the backtick to be used as the
// constant's name; the value is read back via `Holder.`name`. A *bare*
// reserved-name struct const still errors with the caret on the name (see
// examples/1142). The backtick is never part of the name's text.
// Regression (issue 0089 — attempt-5: struct-body const decls thread is_raw +
// the precise name_span, previously dropped to a false reject / 1:1 caret).
#import "modules/std.sx";
Holder :: struct {
`i2 :: 5; // untyped raw struct-body const
`u8 : i64 : 9; // typed raw struct-body const
}
main :: () -> i32 {
print("untyped = {}\n", Holder.`i2);
print("typed = {}\n", Holder.`u8);
return 0;
}

View File

@@ -0,0 +1,30 @@
// Backtick raw identifier in PARAMETERIZED type position. A raw type reference
// (`` `i2 ``) flows through the SAME type-expression continuations as a bare
// name, so a reserved-spelled GENERIC template can be instantiated
// (`` `i2(i64) ``) and the result composes under pointer/field wrappers
// (`` *`i2(i64) ``, a struct field typed `` `i2(i64) ``). A bare `i2` in type
// position is still the 2-bit signed int. Complements examples/0154 (nullary
// raw type references).
// Regression (issue 0089 — attempt-5: the raw type atom no longer parses as a
// terminal `type_expr`; it reaches the parameterized + wrapper continuations).
#import "modules/std.sx";
`i2 :: struct($T: Type) {
x: $T;
}
Wrapper :: struct {
inner: `i2(i64); // raw parameterized type as a struct field
}
main :: () -> i32 {
v : `i2(i64);
v.x = 7;
p : *`i2(i64) = @v; // pointer to a raw parameterized type
w : Wrapper = ---;
w.inner.x = 12;
print("val = {}\n", v.x);
print("ptr = {}\n", p.x);
print("fld = {}\n", w.inner.x);
return 0;
}

View File

@@ -0,0 +1,55 @@
// Reserved-name MEMBER positions are EXEMPT from the reserved-type-name rule:
// a bare reserved spelling (`i2`, `u8`, `i1`, …) is legal as a struct FIELD
// name, a union TAG name, and a protocol METHOD-SIGNATURE name. These are
// unambiguous — the name sits in a member slot and is reached via `obj.name`
// (or dispatched by string), so it is never type-classified and never
// mislowers. The backtick form is optional there and resolves to the same
// member. Backtick access (`obj.`i2`) and bare access (`obj.i2`) both work.
//
// The exemption stops at member SIGNATURES: an `impl` method DEFINITION is a
// real function, so its name is a declaration site (like a free function) and a
// reserved spelling still needs the backtick (`` `i2 :: (self) ``) — bare would
// be type-classified and mislower (the issue-0076 protection). A bare reserved
// VALUE binding / declaration name still errors (see examples/1119, 1141, 1142).
// Regression (issue 0089 — attempt-7: pins the Agra-ruled member-name exemption).
#import "modules/std.sx";
// Struct fields spelled with reserved type names — bare is legal.
Holder :: struct {
i2: i64;
u8: i64;
}
// Union tags spelled with reserved type names — bare is legal.
Tag :: union {
i1: i32;
u16: f64;
}
// Protocol method SIGNATURE spelled with a reserved type name — bare is legal.
Speaker :: protocol {
i2 :: (self: *Self) -> i64;
}
Dog :: struct { n: i64; }
impl Speaker for Dog {
`i2 :: (self: *Dog) -> i64 { self.n } // impl DEFINITION → backtick required
}
main :: () -> i32 {
h := Holder.{ i2 = 10, u8 = 20 };
print("fields bare = {} {}\n", h.i2, h.u8); // bare member access
print("fields tick = {} {}\n", h.`i2, h.`u8); // backtick member access
h.i2 = 11;
h.`u8 = 21; // backtick write
print("fields set = {} {}\n", h.i2, h.u8);
t : Tag = ---;
t.i1 = 5;
print("union = {} {}\n", t.i1, t.`i1); // bare + backtick — same tag
items : List(Speaker) = .{};
items.append(Dog.{ n = 7 });
print("dispatch = {}\n", items.items[0].i2()); // bare reserved-name method call
return 0;
}

View File

@@ -0,0 +1,94 @@
// Float numeric-limit accessors: `f32`/`f64` expose `.min` / `.max` (sibling of
// the integer `.min`/`.max`, NL.1) plus the float-only `.epsilon`,
// `.min_positive`, `.true_min`, `.inf`, and `.nan`. Each folds, at compile time,
// to a constant of the QUERIED float type via the same `lowerNumericLimit`
// intercept as the integer case (`builder.constFloat` + the `std.math`
// constants), driven by `TypeResolver.floatLimitFor`.
//
// The lexer has no exponent notation and the default float formatter is crude
// (issue 0090), so these limits can be pinned NEITHER by literal comparison NOR
// by printing. Every accessor is asserted instead by reinterpreting its bits
// through an untagged union and comparing against the exact IEEE-754 hex
// pattern — plus the defining-property checks that no other value could satisfy.
//
// Semantics (Agra-ruled, consistent with the integer accessors):
// .min = most-NEGATIVE finite (= -max), NOT C's DBL_MIN
// .max = largest finite
// .epsilon = ULP of 1.0 (next f after 1.0 minus 1.0), NOT C#'s denormal Epsilon
// .min_positive = smallest positive NORMAL (= C DBL_MIN / Rust MIN_POSITIVE)
// .true_min = smallest positive SUBNORMAL (next value above 0.0)
// .inf = +infinity
// .nan = a quiet NaN
//
// Regression (issue 0091): `f64.nan != f64.nan` is true — native float `!=`
// lowers UNORDERED, so a NaN compares unequal to everything including itself.
#import "modules/std.sx";
// `bits` mirrors each float's raw IEEE-754 storage. f64 needs 64 bits, f32 32.
// The f64 union's `bits` (u64) view reads the all-ones-ish positive patterns as
// their true magnitude; its `s` (i64) view pins the negative `f64.min` pattern
// (0xFFEF…), whose unsigned form overflows the u64 literal parser, by comparing
// the signed reinterpret to -4503599627370497.
Uf64 :: union { f: f64; bits: u64; s: i64; }
Uf32 :: union { f: f32; bits: u32; }
main :: () -> i32 {
o : Uf64 = ---;
// Read `.true_min` (a subnormal) FIRST and through the union only — never via
// arithmetic. Under flush-to-zero / denormals-are-zero CPU modes a subnormal
// can flush to 0.0 on the first arithmetic op, so the bit reinterpret is the
// only reliable channel for it.
o.f = f64.true_min;
print("f64.true_min {}\n", o.bits == 0x0000000000000001); // true
o.f = f64.max;
print("f64.max {}\n", o.bits == 0x7FEFFFFFFFFFFFFF); // true
// f64.min = -max; its bit pattern 0xFFEFFFFFFFFFFFFF overflows an unsigned u64
// literal, so it is pinned directly via the SIGNED i64 view: -4503599627370497.
o.f = f64.min;
print("f64.min {}\n", o.s == -4503599627370497); // true (bits 0xFFEFFFFFFFFFFFFF)
o.f = f64.epsilon;
print("f64.epsilon {}\n", o.bits == 0x3CB0000000000000); // true
o.f = f64.min_positive;
print("f64.min_positive {}\n", o.bits == 0x0010000000000000); // true
o.f = f64.inf;
print("f64.inf {}\n", o.bits == 0x7FF0000000000000); // true
p : Uf32 = ---;
p.f = f32.true_min;
print("f32.true_min {}\n", p.bits == 0x00000001); // true
p.f = f32.max;
print("f32.max {}\n", p.bits == 0x7F7FFFFF); // true
p.f = f32.min;
print("f32.min {}\n", p.bits == 0xFF7FFFFF); // true
p.f = f32.epsilon;
print("f32.epsilon {}\n", p.bits == 0x34000000); // true
p.f = f32.min_positive;
print("f32.min_positive {}\n", p.bits == 0x00800000); // true
p.f = f32.inf;
print("f32.inf {}\n", p.bits == 0x7F800000); // true
// Defining-property checks — true epsilon is the ULP of 1.0: adding it to 1.0
// changes the value, adding half of it does not (round-to-nearest-even).
print("(1+eps)!=1 {}\n", (1.0 + f64.epsilon) != 1.0); // true
print("(1+eps/2)==1 {}\n", (1.0 + f64.epsilon/2.0) == 1.0); // true
print("inf>max {}\n", f64.inf > f64.max); // true
// f64.min = -max (the 0xFFEF… bit pattern overflows the i64 literal parser).
print("min==-max {}\n", f64.min == -f64.max); // true
print("true_min<min_pos {}\n", f64.true_min < f64.min_positive); // true
print("true_min>0 {}\n", f64.true_min > 0.0); // true
// Quiet NaN: unequal to everything, itself included (mantissa bits not pinned).
print("nan!=nan {}\n", f64.nan != f64.nan); // true
// Result carries the QUERIED type: each binding is declared with the float
// type and round-trips, so a mistyped fold (boxed as Any / wrong width) would
// not type-check here.
e64 : f64 = f64.epsilon;
e32 : f32 = f32.epsilon;
q : Uf64 = ---; q.f = e64;
r : Uf32 = ---; r.f = e32;
print("typed eps bits {}\n", q.bits == 0x3CB0000000000000 and r.bits == 0x34000000); // true
return 0;
}

View File

@@ -0,0 +1,27 @@
// Cross-type rules for the numeric-limit accessors. `.min` / `.max` are valid on
// BOTH integer and float types, but `.epsilon` / `.min_positive` / `.true_min` /
// `.inf` / `.nan` are FLOAT-ONLY. Applying a float-only accessor to an INTEGER
// type, or ANY accessor to a non-numeric type, is a clean compile error — never
// a silent value, never the `.unresolved` sentinel reaching codegen.
//
// - float-only accessor on an integer (`i32.epsilon`, `u8.inf`,
// `i64.true_min`) → a dedicated "applies only to float types" diagnostic
// from the accessor intercept, located at the access;
// - any accessor on a non-numeric builtin (`bool.nan`, `string.max`) → the
// "numeric limits apply only to integer and float types" diagnostic;
// - a user struct (`MyStruct.epsilon`) → the type name is not a builtin, so the
// intercept stays out and the existing field-not-found path reports it.
// Each case is accurate and located at the access; the program exits non-zero.
#import "modules/std.sx";
MyStruct :: struct { a: i64; }
main :: () -> i32 {
a := i32.epsilon;
b := u8.inf;
c := i64.true_min;
d := bool.nan;
e := string.max;
f := MyStruct.epsilon;
return 0;
}

View File

@@ -0,0 +1,76 @@
// Numeric-limit accessor vs. a raw value binding that shadows a builtin type
// name. A backtick raw identifier (F0.6) can legitimately bind a value whose
// spelling is a reserved numeric type name (`` `f64 ``, `` `i32 ``, `` `u8 ``).
// Field access on such a value is an ORDINARY field read — the numeric-limit
// intercept (NL.1 integer `.min`/`.max`, NL.2 float `.epsilon`/… ) must NOT
// hijack it. An adjacent BARE `f64.epsilon` / `i32.max` / `u8.max` — which the
// parser classifies as a type receiver, not the raw value — STILL folds to the
// numeric limit. Both behaviors coexist: the raw receiver reads the value, the
// bare receiver folds the limit.
//
// A raw value binding can reach the intercept through THREE sources, exactly
// mirroring the ordinary identifier field-access path (scope / globals / module
// consts). This example exercises all three: a GLOBAL `` `f32 ``, a MODULE-CONST
// `` `i16 ``, and LOCAL `` `f64 ``/`` `i32 ``/`` `u8 `` — each reads its field,
// and the bare spelling of each STILL folds.
//
// Regression (issues 0092 local, 0093 global + module-const): the intercept
// previously treated any identifier whose text matched a builtin numeric type
// name as a TYPE receiver, silently shadowing the in-scope value binding
// (`` `f64.epsilon `` folded to 2^-52, `` `i32.max `` folded to 2147483647 —
// a silent wrong value). The attempt-3 fix guarded only lexical scope, so
// GLOBAL and MODULE-CONST raw bindings still folded (issue 0093).
#import "modules/std.sx";
FBox :: struct { epsilon: i64; max: i64; min_positive: i64; }
IBox :: struct { max: i64; min: i64; }
UBox :: struct { max: i64; }
// GLOBAL raw value binding whose spelling shadows the builtin `f32`. Reachable
// via `program_index.global_names`, not lexical scope (issue 0093).
`f32 := FBox.{ epsilon = 44, max = 55, min_positive = 66 };
// MODULE-CONST raw value binding whose spelling shadows the builtin `i16`.
// Reachable via `program_index.module_const_map` (issue 0093, const variant).
`i16 :: IBox.{ max = 99, min = -99 };
main :: () -> i32 {
// LOCAL raw value bindings whose spelling shadows a builtin numeric type name.
`f64 := FBox.{ epsilon = 11, max = 22, min_positive = 33 };
`i32 := IBox.{ max = 78, min = -78 };
`u8 := UBox.{ max = 7 };
// Raw receiver → ordinary field READ (the value), never the numeric limit.
print("local f64: epsilon={} max={} min_positive={}\n",
`f64.epsilon, `f64.max, `f64.min_positive); // 11 22 33
print("local i32: max={} min={}\n", `i32.max, `i32.min); // 78 -78
print("local u8: max={}\n", `u8.max); // 7
// GLOBAL raw receiver → ordinary field READ (issue 0093).
print("global f32: epsilon={} max={} min_positive={}\n",
`f32.epsilon, `f32.max, `f32.min_positive); // 44 55 66
// MODULE-CONST raw receiver → ordinary field READ (issue 0093).
print("const i16: max={} min={}\n", `i16.max, `i16.min); // 99 -99
// The value-field read carries the field type (i64 here): round-trips
// through a typed binding, so a mistyped/boxed read would not type-check.
e : i64 = `f64.epsilon;
print("typed val e={}\n", e); // 11
// Bare receiver (a type receiver, NOT the raw value) → STILL folds to the
// numeric limit, even though a LOCAL (`i32`/`u8`/`f64`), GLOBAL (`f32`), or
// MODULE-CONST (`i16`) value of the same spelling is bound. The bare receiver
// is never blocked by any of the three value sources.
print("lim i32.max={} i32.min={}\n", i32.max, i32.min); // 2147483647 -2147483648
print("lim u8.max={}\n", u8.max); // 255
print("lim i16.max={} i16.min={}\n", i16.max, i16.min); // 32767 -32768
// Bare float accessors still fold; the formatter is crude (issue 0090), so
// pin the values by their defining properties rather than by printing.
print("lim (1.0+f64.epsilon)!=1.0: {}\n", (1.0 + f64.epsilon) != 1.0); // true
print("lim f64.inf > f64.max: {}\n", f64.inf > f64.max); // true
print("lim f64.min == -f64.max: {}\n", f64.min == -f64.max); // true
print("lim f32.inf > f32.max: {}\n", f32.inf > f32.max); // true
return 0;
}

View File

@@ -0,0 +1,68 @@
// Valid typed module-level constants compile, fold, and print correctly across
// every initializer/annotation pairing the registrar accepts:
// - integer literal → integer (`K : i64 : 4`) — usable as an array count too
// - integer literal → float (`W : f32 : 800`)
// - float literal → float (`PI : f32 : 3.14159`)
// - string literal → string (`S : string : "hi"`)
// - null → pointer (`P : *void : null`)
// - integer EXPRESSION → integer (`KE : i64 : M + 2`) — usable as a count too
// - integer EXPRESSION → float (`WE : f32 : M + 2`)
// - MIXED int+float EXPRESSION → float (`MF : f64 : M + 0.5`, both operand orders)
// - INTEGRAL float literal → integer (`KF : i64 : 4.0` → 4) — folds under the
// unified narrowing rule (F0.11), usable as a count too
// - INTEGRAL float EXPRESSION → integer (`KFE : i64 : M + 2.0` → 4)
//
// Companion to the negative example 1143: the issue-0088 fix rejects a typed
// const whose initializer mismatches its annotation, and these correctly-typed
// consts must keep working (no over-rejection) — including const-EXPRESSION
// initializers, whose type-based validation (attempt 2) must accept a correctly
// typed expression even though it isn't a literal.
//
// `MF`/`MFR` pin the attempt-3 inferExprType promotion fix: a mixed int+float
// arithmetic expression infers as the float result regardless of operand order,
// so it matches an `f64` annotation (and folds to 2.5, not a truncated 2).
#import "modules/std.sx";
M :: 2;
K : i64 : 4;
W : f32 : 800;
PI : f32 : 3.14159;
S : string : "hi";
P : *void : null;
KE : i64 : M + 2;
WE : f32 : M + 2;
MF : f64 : M + 0.5;
MFR : f64 : 0.5 + M;
KF : i64 : 4.0; // integral float literal → folds to 4
KFE : i64 : M + 2.0; // integral float expression → folds to 4
main :: () {
// Integer const: prints AND drives an array dimension (len 4).
a : [K]i64 = ---;
a[0] = 10;
a[3] = 40;
print("K={} len={} a0={} a3={}\n", K, a.len, a[0], a[3]);
// Integer-into-float and float consts print as floats.
print("W={} PI={}\n", W, PI);
// String const prints its text.
print("S={}\n", S);
// Null pointer const is null.
print("P_is_null={}\n", P == null);
// Integer const-EXPRESSION: prints AND drives an array dimension (len 4).
b : [KE]i64 = ---;
print("KE={} len={} WE={}\n", KE, b.len, WE);
// Mixed int+float const-EXPRESSION folds to the promoted float (2.5),
// operand-order-independent.
print("MF={} MFR={}\n", MF, MFR);
// Integral float const (literal + expression): folds to its integer under
// the unified narrowing rule; `KF` also drives an array dimension (len 4).
cc : [KF]i64 = ---;
print("KF={} len={} KFE={}\n", KF, cc.len, KFE);
}

View File

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

View File

@@ -0,0 +1,43 @@
// Reflection builtins on an `Any` consult the Any's runtime TYPE-TAG,
// not its raw payload.
//
// An `Any` is both a type AND a value: it can hold a runtime *value*
// (whose runtime tag names the value's type) or a *Type value* (the
// `{ .any, tid }` shape `type_of` / a `Type` literal produce). A
// reflection builtin must branch on the tag:
// - holds a value → report the type OF that value (the tag).
// - holds a Type → name the held type (the payload).
//
// Regression (issue 0090, attempt 3): `type_name` / `type_is_unsigned`
// used to read an Any's payload as a TypeId index unconditionally, so
// `type_name(av)` for `av : Any = 6` returned `u8` (types[6]) and
// `type_is_unsigned(av)` returned true. Both now read the runtime tag.
#import "modules/std.sx";
main :: () {
// Any holding a VALUE — reflection names the value's type.
av : Any = 6; // 6 is i64
print("type_name(av)={}\n", type_name(av));
print("type_is_unsigned(av)={}\n", type_is_unsigned(av));
print("print(av)={}\n", av); // formatter already used the tag
u : u32 = 7;
au : Any = u;
print("type_name(au)={}\n", type_name(au));
print("type_is_unsigned(au)={}\n", type_is_unsigned(au));
print("print(au)={}\n", au);
sv : Any = "hi";
print("type_name(sv)={}\n", type_name(sv));
// Any holding a TYPE value — reflection names the held type.
x : u64 = 9;
at : Any = type_of(x);
print("type_name(at)={}\n", type_name(at));
print("type_is_unsigned(at)={}\n", type_is_unsigned(at));
print("print(at)={}\n", at);
// A direct runtime Type value is unchanged.
print("type_is_unsigned(type_of(x))={}\n", type_is_unsigned(type_of(x)));
}

View File

@@ -0,0 +1,21 @@
// Writing through a nested struct field lvalue (`outer.inner.x = v`) and taking
// the address of a valid field both resolve the field pointer correctly: the
// lvalue-pointer path (lowerExprAsPtr) GEPs the matched field, never a silent
// field-0 default. Positive companion to the missing-field diagnostic (1145).
#import "modules/std.sx";
Inner :: struct { a: i64; b: i64; }
Outer :: struct { inner: Inner; tag: i64; }
bump :: (p: *i64) { p.* = p.* + 100; }
main :: () {
o := Outer.{ inner = Inner.{ a = 1, b = 2 }, tag = 9 };
o.inner.a = 11; // nested struct field store via lowerExprAsPtr
o.inner.b = 22;
o.tag = 33; // direct struct field store
print("a={} b={} tag={}\n", o.inner.a, o.inner.b, o.tag);
bump(@o.inner.a); // address-of a matched nested field
print("a2={}\n", o.inner.a);
}

View File

@@ -0,0 +1,32 @@
// Taking the address of a promoted anonymous-struct union member yields a
// pointer to that member's slot, so mutating through the pointer is visible
// through the member. The write path (`v.x = 41`) and the read path already
// resolve promoted members; the lvalue-pointer path (`@v.x`) now resolves them
// too, via the shared field-lvalue resolver.
//
// Regression (issue 0094, attempt 2): lowerExprAsPtr's union branch handled
// only DIRECT union field names, so `@v.x` on a promoted member reported
// "field 'x' not found on type 'Vec2'" even though `v.x = 41` worked. The
// over-rejection is gone, and a member that is NOT at offset 0 (`v.y`) resolves
// to its own slot — not a default field 0.
#import "modules/std.sx";
Vec2 :: union {
data: [2]i64;
struct { x: i64; y: i64; };
}
bump :: (p: *i64) {
p.* = p.* + 1;
}
main :: () {
v : Vec2 = ---;
v.x = 41;
v.y = 100;
bump(@v.x); // promoted member at offset 0 → 42
bump(@v.y); // promoted member at offset 8 → 101 (its own slot)
print("x={}\n", v.x);
print("y={}\n", v.y);
}

View File

@@ -0,0 +1,42 @@
// Storing a struct field whose own type is a pointer to a two-pointer struct
// (`*Pair` — the shape `coerceArg` would closure-auto-promote into `{ptr,null}`)
// must store an 8-byte pointer, never a 16-byte struct that overruns the slot
// and clobbers the neighbouring field. The shared field-lvalue resolver types
// the GEP as `*field_ty`, so `emitStore` unwraps exactly one pointer level to
// the field's own type (`*Pair`, an opaque pointer) instead of the pointee
// aggregate (`Pair`).
//
// Regression (issue 0094, attempt-3 consolidation): the shared `fieldLvaluePtr`
// resolver used to type struct/tuple GEPs with the bare field value type, so a
// `*Pair` field stored via the multi-assign / address-of paths promoted the
// pointer to a 16-byte struct and clobbered the next field (`sentinel` read 0).
// The single-assign path already used the `*field_ty` convention; folding all
// three lvalue sites onto the one resolver applies it uniformly.
#import "modules/std.sx";
Pair :: struct { a: *i64; b: *i64; }
Holder :: struct { pr: *Pair; sentinel: i64; }
main :: () {
x : i64 = 7;
y : i64 = 9;
pair : Pair = .{ a = @x, b = @y };
other : Pair = .{ a = @y, b = @x };
h : Holder = .{ pr = @pair, sentinel = 99 };
// single-assign: store a *Pair into h.pr; sentinel must stay 99.
h.pr = @other;
print("single: a={} b={} sentinel={}\n", h.pr.a.*, h.pr.b.*, h.sentinel);
// multi-assign: store into h.pr alongside a plain local; sentinel untouched.
dummy : i64 = 0;
h.pr, dummy = @pair, 1;
print("multi: a={} b={} sentinel={}\n", h.pr.a.*, h.pr.b.*, h.sentinel);
// address-of the *Pair field, store through it; sentinel untouched.
ppr := @h.pr;
ppr.* = @other;
print("addr: a={} b={} sentinel={}\n", h.pr.a.*, h.pr.b.*, h.sentinel);
}

View File

@@ -0,0 +1,119 @@
// Unified float→int narrowing rule (F0.11), POSITIVE side: an INTEGRAL float
// flowing into an integer-typed binding FOLDS to its integer — the same
// `floatToIntExact` rule an array dimension / `$K: Count` already uses — across
// all FIVE sites: a typed LOCAL, a struct FIELD default, a typed module CONST, a
// function PARAM default, and an array DIMENSION. It folds whether written as a
// float LITERAL (`4.0`), an INT-const-EXPRESSION (`M + 2.0`, with `M :: 2`), a
// FLOAT-const-LEAF expression whose sum is integral (`F + 1.5`, with
// `F : f64 : 2.5`, = 4.0) — including such a float-const-leaf expression driving
// an array dimension directly, through a const, or via a type alias — a builtin
// FLOAT numeric-limit leaf in an integral expression (`f64.max - f64.max` = 0),
// and an integral float `%` (`6.0 % 4.0` = 2). The compile-time float evaluator
// is at parity with the integer one, so integer numeric-limit accessors (`i8.max`,
// `[u8.max]` count) keep folding through the shared int folder, unregressed.
// The escape hatch (`xx` / `cast`) still TRUNCATES any float, integral or not —
// including a non-integral const expression (`xx (M + 0.5)` / `xx (F + 0.25)`).
//
// Companion to the negative example 1146 (non-integral floats error).
// Regression (issue 0095): a typed local/param/field silently truncated a float
// initializer (`y : i64 = 1.5` → 1) with no diagnostic; a non-integral const
// EXPRESSION (`M + 0.5`) and a non-integral float-const-LEAF expression
// (`F + 0.25`) truncated even when written through an int binding; the rule now
// folds an integral float (literal, int-const expr, or float-const leaf) and
// rejects a non-integral one.
#import "modules/std.sx";
M :: 2; // int module const, for the INT-const-EXPRESSION cases
F : f64 : 2.5; // float module const, for the FLOAT-const-LEAF cases
Box :: struct {
n : i64 = 4.0; // integral float field default → folds to 4
ne : i64 = M + 2.0; // integral int-const-EXPR field default → folds to 4
nf : i64 = F + 1.5; // integral float-const-LEAF field default → folds to 4
nd : i64 = 8.0 / 2.0; // integral float-DIVISION field default → folds to 4
}
withDefault :: (x : i64 = 6.0) -> i64 { return x; } // param default → 6
withFlt :: (x : i64 = F + 1.5) -> i64 { return x; } // float-const-leaf param default → 4
K : i64 : 8.0; // integral float module const → folds to 8
KF : i64 : F + 1.5; // integral float-const-LEAF module const → folds to 4
KD : i64 : 12.0 / 4.0; // integral float-DIVISION module const → folds to 3
ArrFE :: [F + 1.5]i64; // array-dim type ALIAS over a float-const-leaf expr → [4]i64
// (the stateless registration path must agree with the
// direct form `a : [F + 1.5]i64` below — issue 0083).
main :: () {
// Typed local: integral float folds (literal + int-const expr + float-const leaf).
z : i64 = 4.0;
ze : i64 = M + 2.0;
zf : i64 = F + 1.5;
print("local={} localExpr={} localFlt={}\n", z, ze, zf);
// Negative integral float folds to its (negative) integer.
neg : i64 = -2.0;
print("neg={}\n", neg);
// Integral float DIVISION folds (the subtle case: integral operands, but the
// `/` is float division). `6.0 / 2.0` = 3.0 → 3; the int folder refuses the
// float `/` and the unified rule folds the integral result.
zd : i64 = 6.0 / 2.0;
print("localDiv={}\n", zd);
// Struct field defaults fold (literal + int-const expr + float-const leaf +
// float division).
b := Box.{};
print("field={} fieldExpr={} fieldFlt={} fieldDiv={}\n", b.n, b.ne, b.nf, b.nd);
// Param defaults fold.
print("param={} paramFlt={}\n", withDefault(), withFlt());
// Module consts fold (and an integral float const can drive an array dim: len 8).
a : [K]i64 = ---;
print("const={} constFlt={} len={}\n", K, KF, a.len);
// Integral float-DIVISION const folds, and drives an array dimension directly
// (`[6.0 / 2.0]` → len 3) through the SAME refuse-int-fold / fold-float rule.
ad2 : [6.0 / 2.0]i64 = ---;
print("constDiv={} dimDiv={}\n", KD, ad2.len);
// Array DIMENSION — the fifth site joins the unified rule: an integral
// float-const-leaf expression folds to a count whether written DIRECTLY
// (`[F + 1.5]` → 4), THROUGH a float-expr const (`[KF]`, KF = F + 1.5 = 4),
// or via a type ALIAS (`ArrFE`, the stateless path agreeing with the direct).
ad : [F + 1.5]i64 = ---;
ak : [KF]i64 = ---;
aa : ArrFE = ---;
print("dim.direct={} dim.const={} dim.alias={}\n", ad.len, ak.len, aa.len);
// Numeric-limit float leaf in an expression: an INTEGRAL result folds (the
// compile-time float evaluator is at parity with the integer one — a
// `f64`/`f32` `.max`/`.min`/`.epsilon`/… leaf is recognised inside an
// expression, not only as a direct value). `f64.max - f64.max` = 0.0 → 0.
lim : i64 = f64.max - f64.max;
// Integral float `%` (parity with int `%`): `6.0 % 4.0` = 2.0 → 2.
fm : i64 = 6.0 % 4.0;
print("limit={} fmod={}\n", lim, fm);
// Integer numeric-limit accessors (NL.1) are unregressed by the float-leaf
// parity work: they still fold at a binding (`i8.max` = 127) and as an array
// dimension count (`[u8.max]` = len 255), through the SAME int folder.
il : i64 = i8.max;
iarr : [u8.max]i64 = ---;
print("intlimit={} intcount={}\n", il, iarr.len);
// Explicit escape: `xx` / `cast` always truncate, integral or not —
// including a non-integral const EXPRESSION (`xx (M + 0.5)` → 2), a
// non-integral float-const-LEAF expression (`xx (F + 0.25)` → 2), a
// non-integral numeric-limit expr (`xx (f64.true_min + 0.5)` → 0), and a
// non-integral float `%` (`xx (5.5 % 2.0)` → 1).
e : i64 = xx 4.9;
c : i64 = cast(i64) 1.5;
xc : i64 = xx (M + 0.5);
xf : i64 = xx (F + 0.25);
xl : i64 = xx (f64.true_min + 0.5);
xm : i64 = xx (5.5 % 2.0);
xd : i64 = xx (5.0 / 2.0); // non-integral float DIVISION → truncates to 2
print("xx={} cast={} xxExpr={} xxFlt={} xxLimit={} xxMod={} xxDiv={}\n", e, c, xc, xf, xl, xm, xd);
}

View File

@@ -0,0 +1,58 @@
// A raw value binding whose spelling shadows a builtin FLOAT type name
// (`` `f64 ``) and whose FLOAT field is read into an INTEGER binding. Field
// access on such a value is an ORDINARY runtime field read — the unified
// float→int narrowing rule (F0.11) must treat it EXACTLY like a non-shadowed
// struct's field read, never as the builtin numeric-limit accessor. So
// `` `f64.epsilon `` reads the value's `epsilon` field (a runtime f64) and a
// float→int narrowing TRUNCATES it, identical to a plainly-named `b.epsilon` —
// it does NOT fold the builtin `f64.epsilon` (= 2.22e-16) into the binding.
//
// The receiver is a mutable `:=` local, so its field is a RUNTIME value, not a
// compile-time constant: reading it after a reassignment yields the new value,
// proving it can never be const-folded from the initializer literal.
//
// Companion to 0161 (value-shadow field reads in NON-narrowing, i64-field
// contexts). This file exercises the narrowing path 0161 does not: a FLOAT
// field flowing into an integer binding.
//
// Regression (issue 0095 / F0.11-7): the compile-time float evaluator's
// field-access arm misclassified a raw value-shadow receiver as the builtin
// numeric-limit accessor, so `` `f64.epsilon `` newly errored under the
// narrowing rule with the BUILTIN value (2.22e-16) instead of reading the
// field. The fix mirrors the `is_raw` guard the sibling `isFloatValuedExpr`
// already applies, so the const-folding cluster agrees: a raw receiver is a
// field read, only a bare type receiver folds a limit.
#import "modules/std.sx";
FBox :: struct { epsilon: f64; }
main :: () {
// Raw value-shadow of the builtin `f64`, FLOAT field → narrow into i64.
// Ordinary field read + runtime float→int truncation: 11.0 → 11.
`f64 := FBox.{ epsilon = 11.0 };
x : i64 = `f64.epsilon;
// A NON-integral field value truncates exactly the same way — a runtime
// f64 has no compile-time value to fold, so 11.5 → 11 (NOT a non-integral
// narrowing error, which would only fire on a compile-time-constant float).
`f64b := FBox.{ epsilon = 11.5 };
y : i64 = `f64b.epsilon;
// The value-shadowed read is identical to a plainly-named one: `b.epsilon`
// narrows the same way, so the backtick spelling changes nothing.
b := FBox.{ epsilon = 11.5 };
yb : i64 = b.epsilon;
print("x={} y={} yb={}\n", x, y, yb); // 11 11 11
// The field is a RUNTIME value: reassign, then read → the new value, not
// the initializer literal (so const-folding it would be unsound).
`f64.epsilon = 4.0;
xm : i64 = `f64.epsilon;
print("xm={}\n", xm); // 4
// The bare builtin receiver (not raw-escaped) is UNAFFECTED — it still
// folds the numeric limit. `f64.max - f64.max` = 0.0 is integral → 0.
lim : i64 = f64.max - f64.max;
print("lim={}\n", lim); // 0
}

View File

@@ -0,0 +1,20 @@
// Two top-level structs each carry an inline anonymous-struct field named
// `inner`, but of DIFFERENT shapes. Each `inner` must resolve to its OWN
// anonymous type (`A.inner` has `x`; `B.inner` has `y, z`) — they must not
// cross-bind on the shared field spelling.
//
// Regression (folded from the Phase-D `replaceKeyedInfo` re-key, which made the
// per-parent anon rename key-safe): on master 7ffc0c1 the two anon types
// cross-bound and `b.inner.y` failed with "field 'y' not found on type
// 'B.inner'". Pins fail-before / pass-after.
#import "modules/std.sx";
A :: struct { inner: struct { x: i64; }; }
B :: struct { inner: struct { y: i64; z: i64; }; }
main :: () -> i32 {
a := A.{ inner = .{ x = 1 } };
b := B.{ inner = .{ y = 2, z = 3 } };
print("{} {} {}\n", a.inner.x, b.inner.y, b.inner.z);
0
}

View File

@@ -0,0 +1,29 @@
// A genuinely-undeclared type name used as a field type inside a MAIN-file
// GENERIC struct must emit a clean "unknown type" diagnostic, not silently
// compile.
//
// The `UnknownTypeChecker` used to SKIP generic structs entirely ("their field
// types reference the struct's own `$T`, resolved at instantiation"). That skip
// was too broad: a field type like `bad: MissingType` — which is NOT a type
// param and names no declared type — fell through the type leaf's empty-struct
// stub and the struct silently compiled, mis-sizing every downstream load.
//
// The checker now walks generic-struct fields with the struct's own type params
// (`$T`) in scope: `good: T` resolves (it IS a param) while `bad: MissingType`
// is reported. A value-param position (a `Vector` lane count, a `$N: u32` arg)
// is still skipped, so a valid generic struct keeps compiling unchanged.
//
// Expected: `error: unknown type 'MissingType'` pointing at the field; exit 1.
// Regression (stdlib E3).
#import "modules/std.sx";
Box :: struct($T: Type) {
good: T;
bad: MissingType;
}
main :: () -> i32 {
b : Box(i64) = .{ good = 7, bad = 0 };
print("{}\n", b.good);
return 0;
}

View File

@@ -0,0 +1,29 @@
// A generic struct's VALUE param (`$N: u32`) is a compile-time integer, not a
// type. Naming it in a TYPE position — here a field type `x: N` — must emit a
// clean diagnostic, NOT silently compile.
//
// The parser marks any reference to a struct's own type param `is_generic`
// (so `x: T` for a real `$T: Type` resolves without a `$`). That marking is
// the same for a value param, so the unknown-type walk used to skip `x: N`
// entirely; the field's type leaf then resolved to the `.unresolved` sentinel
// and PANICKED at LLVM emission ("unresolved type reached LLVM emission").
//
// The checker now distinguishes TYPE params (`$T: Type`, `$T: SomeProtocol`,
// the `..$Ts` pack) from VALUE params (`$N: u32`) using the binder's own rule:
// a value param named in a type position gets the tailored hint. A value param
// in a VALUE position (a `[N]u8` array dimension, a `Vector` lane count) still
// works (see 0147 / 0201).
//
// Expected: `error: 'N' is a value parameter, not a type`; exit 1.
// Regression (stdlib E3).
#import "modules/std.sx";
Bad :: struct($N: u32) {
x: N;
}
main :: () -> i32 {
b : Bad(3) = .{ x = 1 };
print("{}\n", b.x);
return 0;
}

View File

@@ -0,0 +1,42 @@
// Integer literals default to i64 regardless of context: an unannotated
// `x := <int literal>` local stays i64 even inside a function whose return
// type is a narrower integer (the implicit-return target must not type the
// body's declarations), and a large literal initializer keeps its value.
// Also covers destructure decls (`a, b := ...`), which share the same rule.
// Regression (issue 0111): these locals adopted the enclosing fn's return
// type (i32/i8), silently wrapping `big := 3000000000` to -1294967296.
#import "modules/std.sx";
f :: () -> i32 {
x := 0;
print("f.x: {}\n", type_name(type_of(x)));
0
}
g :: () -> i8 {
x := 0;
print("g.x: {}\n", type_name(type_of(x)));
0
}
big_host :: () -> i32 {
big := 3000000000;
print("big: {} = {}\n", type_name(type_of(big)), big);
0
}
d_host :: () -> i32 {
a, b := (1, 2);
print("a: {} b: {}\n", type_name(type_of(a)), type_name(type_of(b)));
0
}
main :: () {
f();
g();
big_host();
d_host();
x := 0;
print("main.x: {}\n", type_name(type_of(x)));
}

View File

@@ -0,0 +1,22 @@
// Boundary and exemption cases for the int-literal fits-check: extreme
// in-range values compile (incl. negated literals via the constant fold);
// width-64 types accept any representable literal; explicit `xx` / `cast`
// still truncate on request; literal call args check against param types.
#import "modules/std.sx";
clamp_i8 :: (v: i8) -> i8 { v }
main :: () {
a : i8 = -128;
b : i8 = 127;
c : u8 = 0;
d : u8 = 255;
e : u64 = 0x7FFFFFFFFFFFFFFF;
f : u32 = 0xFFFFFFFF;
g : i16 = -32768;
h : i8 = xx 300; // explicit truncation stays legal
i := cast(i8) 300; // cast form too
j : i8 = clamp_i8(-5);
print("{} {} {} {} {} {} {} {} {} {}\n", a, b, c, d, e, f, g, h, i, j);
}

View File

@@ -0,0 +1,16 @@
// A negated literal is a compile-time constant for a global initializer:
// ints serialize directly, an integral negative float narrows into an
// integer global (non-integral errors), and boundary values fit exactly.
// Out-of-range negatives get the literal fits-check, not "non-constant".
// Regression (issue 0113): `g : i64 = -1;` was rejected as not a
// compile-time constant (globalInitValue had no unary_op arm).
#import "modules/std.sx";
g1 : i64 = -1;
g2 : i64 = -4.0;
g3 : i8 = -128;
main :: () {
print("{} {} {}\n", g1, g2, g3);
}

View File

@@ -0,0 +1,21 @@
// Indexing through a pointer-to-array auto-derefs: `p : *[N]T` makes
// `p[i]` GEP the pointee array and load the element, and `p[i] = v` /
// `p[i] += v` store through it — mirroring the struct-pointer auto-deref
// on field access. Writes through the pointer are visible in the
// original array and vice versa.
//
// Regression (issue 0117): this shape used to reach LLVM emission with
// an unresolved element type and panic.
#import "modules/std.sx";
main :: () {
k : [4]i64 = .[11, 22, 33, 44];
p := @k;
print("read={}\n", p[2]);
p[1] = 99;
p[3] += 1;
print("write={} {} {}\n", k[1], p[1], k[3]);
e := @p[2];
print("elem-ptr={}\n", e.*);
}

View File

@@ -0,0 +1,34 @@
// Array-typed `::` constants are IMMUTABLE GLOBALS: one storage, reads
// GEP it, the emitter marks it constant, dead-global elimination drops
// unused ones. Typed (`K : [4]i64 : .[...]`) and untyped (`A :: .[...]`,
// element type inferred — all ints i64; any float promotes the element
// type to f64 with ints converting exactly; bool/string homogeneous).
// Element shapes cover nested aggregates. Reads are normal array values:
// indexing, .len, by-value copies (independent of the const), passing to
// fixed-array params, and @-address (reads through *[4]i64 — issue 0117).
#import "modules/std.sx";
K : [4]i64 : .[11, 22, 33, 44];
A :: .[1, 2, 3];
M :: .[1, 2.2, 3];
F : [2]f64 : .[1, 2.5];
S :: .["alpha", "beta"];
B :: .[true, false, true];
P :: struct { x, y: i64; }
PTS : [2]P : .[P.{ x = 1, y = 2 }, P.{ x = 3, y = 4 }];
GRID : [2][2]i64 : .[.[1, 2], .[3, 4]];
sum :: (xs: [4]i64) -> i64 { xs[0] + xs[1] + xs[2] + xs[3] }
main :: () {
print("typed={} untyped={} len={}\n", K[2], A[1], K.len);
print("mixed={} {} {} intfloat={}\n", M[0], M[1], M[2], F[0]);
print("str={} bool={} pt={} grid={}\n", S[1], B[2], PTS[1].y, GRID[1][0]);
k := K;
k[0] = 99;
print("copy={} const={}\n", k[0], K[0]);
print("sum={}\n", sum(K));
p := @K;
print("via-ptr={}\n", p[2]);
}

View File

@@ -0,0 +1,16 @@
// A TYPED struct constant ('W : Color : Color.{...}') registers like the
// untyped form ('WHITE :: Color.{...}') — value reads, field reads, and
// independent copies. (Both shapes keep inline re-lowering semantics
// until PLAN-CONST-AGG step 4 migrates them to const globals.)
#import "modules/std.sx";
Color :: struct { r, g, b: i64; }
W : Color : Color.{ r = 1, g = 2, b = 3 };
main :: () {
print("{} {} {}\n", W.r, W.g, W.b);
c := W;
c.g = 99;
print("copy={} const={}\n", c.g, W.g);
}

View File

@@ -0,0 +1,23 @@
// Const aggregates fold at compile time: an array const's `.len` and
// `K[<const idx>]` element reads, and a struct const's field (`LIT.r`),
// are compile-time integer leaves — usable in array dimensions and in
// other constants' initializer expressions, source-aware like every
// const fold.
#import "modules/std.sx";
Color :: struct { r, g, b: i64; }
K : [4]i64 : .[11, 22, 33, 44];
LIT :: Color.{ r = 5, g = 0, b = 0 };
N :: K[0] + K[3];
L :: K.len;
R :: LIT.r;
main :: () {
a : [K.len]u8 = ---;
b : [K[1]]u8 = ---;
c : [LIT.r]u8 = ---;
print("N={} L={} R={}\n", N, L, R);
print("dims={} {} {}\n", a.len, b.len, c.len);
}

View File

@@ -0,0 +1,23 @@
// Serializable struct constants are IMMUTABLE GLOBALS (one storage, no
// per-use rebuild): literal fields, const-EXPRESSION fields (`K + 1`),
// another const's field (`LIT.r`), and a const array's element (`A[1]`)
// all serialize. The const is addressable (`@LIT`) and copies stay
// independent.
#import "modules/std.sx";
Color :: struct { r, g, b: i64; }
K :: 10;
A : [2]i64 : .[7, 8];
LIT :: Color.{ r = 255, g = 0, b = 0 };
EXPR :: Color.{ r = K + 1, g = K * 2, b = A[1] };
REF :: Color.{ r = LIT.r, g = 1, b = 2 };
main :: () {
print("lit={} expr={} {} {} ref={}\n", LIT.r, EXPR.r, EXPR.g, EXPR.b, REF.r);
p := @LIT;
print("via-ptr={}\n", p.r);
c := LIT;
c.r = 9;
print("copy={} const={}\n", c.r, LIT.r);
}

View File

@@ -0,0 +1,18 @@
// A struct constant with a NON-serializable initializer field (a call, a
// runtime read) keeps INLINE RE-LOWERING semantics: the initializer is
// evaluated AT EACH USE. This is the documented contract for this class
// — `CALL.r` may differ between reads and side effects run per use.
// For evaluate-once semantics use `NAME :: #run f();`.
#import "modules/std.sx";
Color :: struct { r, g, b: i64; }
counter : i64 = 0;
bump :: () -> i64 { counter += 1; counter }
CALL :: Color.{ r = bump(), g = 0, b = 0 };
main :: () {
print("use1={}\n", CALL.r);
print("use2={}\n", CALL.r);
print("counter={}\n", counter);
}

View File

@@ -0,0 +1,27 @@
// `cast(T) expr` accepts any compile-time-resolvable type argument,
// including compound shapes: `*T`, `[*]T`, `?T`, `[]T`. The same lowering
// makes a compound type literal a first-class `Type` value in expression
// position (`t : Type = *i64;`), mirroring named types (`t : Type = f64;`).
// Regression (issue 0118): compound casts fell into the runtime-dispatch
// path and died with "unresolved 'unknown_expr'".
#import "modules/std.sx";
main :: () {
x := 42;
p : *i64 = @x;
q : *i64 = cast(*i64) p; // no-op pointer cast
print("a: {}\n", q.*);
addr : i64 = xx p;
r : *i64 = cast(*i64) addr; // int → ptr through compound cast
print("b: {}\n", r.*);
mp : [*]i64 = cast([*]i64) p; // ptr → many-pointer
print("c: {}\n", mp[0]);
o : ?i32 = cast(?i32) 7; // optional wrap
print("d: {}\n", o ?? -1);
arr := .[1, 2, 3];
s : []i64 = cast([]i64) arr; // array → slice
print("e: {} {}\n", s.len, s[2]);
t : Type = *i64; // first-class compound Type value
print("f: {}\n", type_name(t));
}

View File

@@ -0,0 +1,34 @@
// Enum literals flowing into OPTIONAL destinations resolve against the
// optional's CHILD type and wrap (issue 0098). Pre-fix, the literal fell
// into resolveVariantValue's non-enum fallback: variant 0, mis-typed as
// the optional itself — `return .android_apk;` was observed by callers as
// `.ios` or even null, layout-dependent.
#import "modules/std.sx";
Platform :: enum u8 { ios; android_apk; macos; linux; windows; }
classify :: (n: i64) -> ?Platform {
if n == 1 { return .android_apk; } // return: literal into ?Platform
if n == 2 { return .windows; } // non-zero, non-adjacent variant
return null;
}
main :: () -> i32 {
p := classify(1);
if p == null { print("BUG: null\n"); return 1; }
if p! != .android_apk { print("BUG: wrong variant\n"); return 2; }
w := classify(2);
if w == null or w! != .windows { print("BUG: windows\n"); return 3; }
if classify(9) != null { print("BUG: not null\n"); return 4; }
// assignment + reassignment: literal into a ?Platform slot
q : ?Platform = .macos;
if q == null or q! != .macos { print("BUG: assign\n"); return 5; }
q = .linux;
if q! != .linux { print("BUG: reassign\n"); return 6; }
print("ok\n");
return 0;
}

View File

@@ -0,0 +1,22 @@
// Assigning a struct LITERAL to a named-struct member of a plain `union`.
// `u.b = .{ code = 9 }` types the literal as the union member's struct type
// `S` and stores it — the target type propagates to a union-member lvalue
// exactly as it does to a struct field.
//
// Regression (issue 0133): the literal used to lower as `.unresolved` (the
// target-type path only inspected struct fields, not union members) and trip
// the LLVM-emission tripwire in emitStructInit.
#import "modules/std.sx";
S :: struct { code: i64; }
U :: union { a: i64; b: S; }
main :: () {
u : U = ---;
u.b = .{ code = 9 }; // union member <- struct literal
print("code={}\n", u.b.code); // 9
u.a = 5; // scalar member still works
print("a={}\n", u.a); // 5
}

View File

@@ -0,0 +1,20 @@
// A direct write to a tagged-union (enum-with-payload) variant member is
// rejected: a tagged union is laid out `{ tag, payload }`, and a member write
// would set the payload but leave the tag stale. The variant is set via
// construction (`s = .rect(...)`), which writes both tag and payload.
//
// Regression (issue 0136): `s.rect = .{...}` used to silently store the payload
// only, desyncing the tag so a later `match` took the wrong arm. It now errors.
#import "modules/std.sx";
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
}
main :: () {
s : Shape = .circle(1.0);
s.rect = .{ w = 4.0, h = 2.0 }; // rejected — use `s = .rect(.{...})` instead
print("unreachable: {}\n", s.rect.w);
}

View File

@@ -0,0 +1,22 @@
// A write to a sub-field of a tagged-union variant's payload (`s.rect.w = ...`)
// is NOT rejected: the immediate object is the payload struct, so it mutates a
// field of the already-active variant in place and leaves the tag alone. This
// pins the scope of issue 0136's guard — only a WHOLE-variant member write
// (`s.rect = ...`) is rejected; nested sub-field writes keep working.
#import "modules/std.sx";
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
}
main :: () {
s : Shape = .rect(.{ w = 1.0, h = 2.0 });
s.rect.w = 9.0; // nested sub-field write — allowed
r := s.rect;
print("w={} h={}\n", r.w, r.h); // 9 2
s = .circle(3.5); // construction reassign — allowed
print("c={}\n", s.circle); // 3.5
}

View File

@@ -0,0 +1,35 @@
// Qualified enum-variant construction: `EnumType.variant` for a payloadless
// variant, the explicit twin of the leading-dot `.variant` form. Works for a
// plain enum and for a payloadless variant of a tagged union; a payload-carrying
// variant keeps its call form (`Shape.circle(2.0)`), unaffected.
#import "modules/std.sx";
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32; // payload-carrying
dot; // payloadless
}
main :: () {
// Plain enum, qualified construction.
c := Color.green;
if c == {
case .red: { print("red\n"); }
case .green: { print("green\n"); }
case .blue: { print("blue\n"); }
}
// Tagged union: payloadless variant qualified, payload variant via call.
d := Shape.dot;
if d == {
case .circle: (r) { print("circle {}\n", r); }
case .dot: { print("dot\n"); }
}
s := Shape.circle(2.0);
if s == {
case .circle: (r) { print("circle {}\n", r); }
case .dot: { print("dot\n"); }
}
}

View File

@@ -0,0 +1,26 @@
// A `*self` method called directly on an array-index place (`arr[i].method()`)
// must mutate the LIVE array slot, not a throwaway copy. `fixupMethodReceiver`
// now takes the real address of `.index_expr`/`.deref_expr` receivers (via
// `lowerExprAsPtr`), mirroring the explicit-argument path — instead of falling
// through to an alloca+store-of-value that the callee then mutates in vain.
//
// Regression (issue 0145).
#import "modules/std.sx";
S :: struct {
flag: bool;
set :: (self: *S) { self.flag = true; }
}
A :: struct { items: [4]S; }
main :: () -> i32 {
a : A = .{};
a.items[1].set(); // direct: must mutate the slot
print("direct = {}\n", a.items[1].flag); // true
p := @a.items[1];
p.set(); // via explicit pointer
print("ptr = {}\n", a.items[1].flag); // true
0
}

View File

@@ -0,0 +1,32 @@
// A comparison with mismatched scalar operands (int vs float, or two float
// widths) must promote both operands to the common type before emitting the
// compare — `lowerBinaryOp` now coerces each operand to the promoted float
// type for the ordering/equality arms (as the arithmetic arms already do),
// so the cast materializes and LLVM gets a well-typed fcmp instead of a
// mixed-type compare that the verifier rejects / silently mis-evaluates.
//
// Regression (issue 0146).
#import "modules/std.sx";
// i32 < f32: the `xx i` cast must materialize as an i32->f32 SIToFP.
ceil_half :: (v: f32) -> i32 {
t := v - 0.5;
i : i32 = xx t;
if xx i < t { // 1.0 < 1.8 -> true
i += 1;
}
i
}
// f64 vs f32 sibling: `xx y + 0.5` infers f64; comparing to an f32 must FPExt.
ge :: (y: i32, lo: f32) -> i32 {
sy := xx y + 0.5; // 3.5 (f64)
if sy >= lo { return 1; }
0
}
main :: () -> i32 {
print("{}\n", ceil_half(2.3)); // 2
print("{}\n", ge(3, 2.0)); // 1
0
}

View File

@@ -0,0 +1,27 @@
// A `void` (zero-sized) struct/tuple field is legitimate (e.g. `Future(void)`)
// and must NOT crash the compiler. It lowers to a zero-width `[0 x i8]` LLVM
// slot (TypeLowering.fieldLLVMType), and aggregate init skips storing a value
// into it (emitStructInit) — so the struct stays sized and field access past
// the void field is correct, instead of the old unsized-type SIGTRAP / a
// corrupt aggregate constant.
//
// Regression (issue 0150).
#import "modules/std.sx";
Holder :: struct { v: void; ok: bool; }
Box :: struct($T: Type) { v: T; tag: i32; }
main :: () -> i32 {
h : Holder = .{ ok = true };
if h.ok { print("ok\n"); }
// Through a generic instantiated at `void`.
b : Box(void) = .{ tag = 7 };
print("tag={}\n", b.tag);
// A tuple with a void element.
t : (void, i32) = .{ {}, 9 };
print("t1={}\n", t.1);
return 0;
}

View File

@@ -0,0 +1,3 @@
// Companion module for 0192 — exports `Selection`, a struct whose size is NOT 8
// bytes (so a wrong fallback like `.i64` would be detectable).
Selection :: struct { a: i32; b: i32; c: i32; } // 12 bytes

View File

@@ -0,0 +1,18 @@
// `size_of(alias.Type)` on a module-alias-qualified type must resolve the type,
// exactly as a declaration `: alias.Type` does. In argument position the
// qualified name parses as a field-access expression; resolveTypeArg now
// reconstructs the dotted name and resolves it through the alias map instead of
// returning `.unresolved`.
//
// Regression (issue 0147).
#import "modules/std.sx";
sel :: #import "0192-types-size-of-qualified-alias-mod.sx";
main :: () -> i32 {
// Qualified through the module alias, in a type-arg slot.
print("size={}\n", size_of(sel.Selection)); // 12
// Same name also resolves in a declaration (already worked).
s : sel.Selection = .{ a = 1, b = 2, c = 3 };
print("sum={}\n", s.a + s.b + s.c); // 6
return 0;
}

View File

@@ -0,0 +1,45 @@
// E6a (attempt-2) regression — RECURSIVE top-level enum/union via per-decl nominal
// identity. Three single-author shapes that reference a not-yet-interned name in a
// `*Name` field:
// * `Node` — a SELF-referential UNION (linked cells: `next: *Node`).
// * `Tree` — a SELF-referential ENUM/tagged-union (`branch: *Tree`).
// * `A`/`B` — a MUTUALLY-referential union pair (`A` holds `*B`, `B` holds `*A`).
//
// Pre-fix (eed2f99) the new per-decl register path built each enum/union body
// through the STATELESS `type_bridge` BEFORE a matching nominal slot existed, so a
// `*Name` field forward-created a STRUCT stub under `Name`; `internNamedTypeDecl`
// then refreshed that struct stub as an enum/union and tripped the kind-stability
// assert in `types.zig` `updatePreservingKey` — a hard panic (the corpus had no
// recursive enum/union, so the gate missed it). The fix adopts the forward struct
// stub IN PLACE (re-key to the real enum/union kind), mirroring how a self-ref
// struct adopts its own forward stub — so `*Node`/`*Tree`/`*B`/`*A` resolve to the
// genuine 8-byte-pointer nominal types and the recursive walks read through.
#import "modules/std.sx";
Node :: union { next: *Node; value: i32; }
Tree :: enum { leaf: i32; branch: *Tree; }
A :: union { b: *B; tag: i32; }
B :: union { a: *A; val: i32; }
main :: () -> i32 {
// Self-ref union: two-hop walk to the tail cell's value.
n2 : Node = ---;
n2.value = 7;
n1 : Node = ---;
n1.next = @n2;
n0 : Node = ---;
n0.next = @n1;
// Self-ref enum: a branch whose payload pointer derefs to a leaf.
leaf_node : Tree = .leaf(42);
root : Tree = .branch(@leaf_node);
// Mutual-ref pair: reach B's value through A's `*B`.
bv : B = ---;
bv.val = 99;
av : A = ---;
av.b = @bv;
print("union={} enum={} mutual={}\n", n0.next.*.next.*.value, root.branch.*.leaf, av.b.*.val);
0
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
v1: Vec4{x: 1.000000, y: 2.000000, z: 3.000000, w: 0.000000}
v2: Vec4{x: 4.000000, y: 1.000000, z: 1.000000, w: 3.000000}
v3: Vec4{x: 2.000000, y: 3.000000, z: 4.000000, w: 0.000000}
v4: Vec4{x: 9.000000, y: 0.000000, z: 5.000000, w: 6.000000}
Complex{foo: .B(Complex.foo.B{val: hello})}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
a 0 : Foo{a: 0, b: 42, c: 0, d: 17}
a 1 : Foo{a: 1, b: 42, c: 8, d: 17}
b: Foo{a: 1, b: 42, c: 101, d: 17}
Pack{a: 1, b: 0, c: 3, d: 5, f: 9, v: 100, x: 3.500000}

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