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,97 @@
#import "modules/std.sx";
// --- Type declarations ---
OptNode :: struct { value: i32; next: ?i32; }
OptInner :: struct { val: i32; }
OptOuter :: struct { inner: ?OptInner; }
// --- Comptime optionals ---
ct_sum :: () -> i32 {
x: ?i32 = 42;
y: ?i32 = null;
return (x ?? 0) + (y ?? 99);
}
CT_RESULT :: #run ct_sum();
main :: () -> i32 {
// Basic optional creation
x: ?i32 = 42;
y: ?i32 = null;
print("x = {}\n", x);
print("y = {}\n", y);
// Force unwrap
print("x! = {}\n", x!);
// Null coalescing
print("x ?? 0 = {}\n", x ?? 0);
print("y ?? 99 = {}\n", y ?? 99);
// If-binding (safe unwrap)
if val := x {
print("if-bind x: {}\n", val);
}
if val := y {
print("should not print\n");
} else {
print("if-bind y: none\n");
}
// Pattern matching
check :: (v: ?i32) -> i32 {
return if v == {
case .some: (val) { val }
case .none: { 0 }
};
}
print("match some: {}\n", check(42));
print("match none: {}\n", check(null));
// Optional chaining
p: ?OptNode = OptNode.{ value = 10, next = 20 };
q: ?OptNode = null;
print("p?.value = {}\n", p?.value ?? 0);
print("q?.value = {}\n", q?.value ?? 0);
// Deep chaining
o1 := OptOuter.{ inner = OptInner.{ val = 99 } };
o2 := OptOuter.{ inner = null };
print("o1.inner?.val = {}\n", o1.inner?.val ?? 0);
print("o2.inner?.val = {}\n", o2.inner?.val ?? 0);
// Flow-sensitive narrowing
a: ?i32 = 10;
b: ?i32 = 20;
if a != null {
print("narrowed a: {}\n", a);
}
// Guard narrowing
guard :: (v: ?i32) -> i32 {
if v == null { return 0; }
return v;
}
print("guard 42: {}\n", guard(42));
print("guard null: {}\n", guard(null));
// Compound narrowing
if a != null and b != null {
print("both: {} {}\n", a, b);
}
// Compound guard
guard2 :: (a: ?i32, b: ?i32) -> i32 {
if a == null or b == null { return 0; }
return a + b;
}
print("guard2: {}\n", guard2(3, 4));
// Struct field defaults
n := OptNode.{ value = 10 };
print("default next: {}\n", n.next);
// Comptime result
print("comptime: {}\n", CT_RESULT);
return 0;
}

View File

@@ -0,0 +1,85 @@
// Match expression with both `null` arms and concrete struct value arms
// produces an optional type (?T) and correctly wraps non-null values.
#import "modules/std.sx";
HAlignment :: enum { leading; center; trailing; }
VAlignment :: enum { top; center; bottom; }
Alignment :: struct { h: HAlignment; v: VAlignment; }
ALIGN_CENTER :: Alignment.{ h = .center, v = .center };
ALIGN_TOP :: Alignment.{ h = .center, v = .top };
ALIGN_BOTTOM :: Alignment.{ h = .center, v = .bottom };
ALIGN_LEADING :: Alignment.{ h = .leading, v = .center };
ALIGN_TRAILING :: Alignment.{ h = .trailing, v = .center };
ALIGN_TOP_LEADING :: Alignment.{ h = .leading, v = .top };
ALIGN_TOP_TRAILING :: Alignment.{ h = .trailing, v = .top };
ALIGN_BOTTOM_LEADING :: Alignment.{ h = .leading, v = .bottom };
ALIGN_BOTTOM_TRAILING :: Alignment.{ h = .trailing, v = .bottom };
Zone :: enum {
floating;
fill;
center;
top;
bottom;
left;
right;
top_left;
top_right;
bottom_left;
bottom_right;
}
// Match expression as implicit return with mixed null/concrete arms
zone_to_alignment :: (zone: Zone) -> ?Alignment {
if zone == {
case .floating: null;
case .fill: ALIGN_CENTER;
case .center: ALIGN_CENTER;
case .top: ALIGN_TOP;
case .bottom: ALIGN_BOTTOM;
case .left: ALIGN_LEADING;
case .right: ALIGN_TRAILING;
case .top_left: ALIGN_TOP_LEADING;
case .top_right: ALIGN_TOP_TRAILING;
case .bottom_left: ALIGN_BOTTOM_LEADING;
case .bottom_right: ALIGN_BOTTOM_TRAILING;
}
}
// Side-effect match inside a function returning bool — must NOT be
// affected by optional inference (no null arms here)
NodeType :: enum { rect; text; image; }
process_node :: (t: NodeType) -> bool {
if t == {
case .rect: { out("rect\n"); }
case .text: { out("text\n"); }
case .image: { out("image\n"); }
}
true
}
main :: () -> void {
// Test null arm
r0 := zone_to_alignment(.floating);
if a := r0 { print("BUG: floating should be null, got h={}\n", xx a.h); }
else { out("ok: floating is null\n"); }
// Test concrete arms
r1 := zone_to_alignment(.left);
if a := r1 { print("ok: left h={}\n", xx a.h); }
else { out("BUG: left returned null\n"); }
r2 := zone_to_alignment(.center);
if a := r2 { print("ok: center h={}\n", xx a.h); }
else { out("BUG: center returned null\n"); }
r3 := zone_to_alignment(.top_right);
if a := r3 { print("ok: top_right h={} v={}\n", xx a.h, xx a.v); }
else { out("BUG: top_right returned null\n"); }
// Test side-effect match (no null arms) still works
process_node(.rect);
process_node(.text);
}

View File

@@ -0,0 +1,39 @@
// Struct with multiple `?f32` fields, all set to `null` simultaneously, passed
// into a protocol-dispatched method. Exercises the all-null-payload path through
// the boxed call.
#import "modules/std.sx";
ProposedSize :: struct {
width: ?f32;
height: ?f32;
}
Sizable :: protocol {
size :: (self: *Self, proposal: ProposedSize) -> f32;
}
Widget :: struct {}
impl Sizable for Widget {
size :: (self: *Widget, proposal: ProposedSize) -> f32 {
w := if pw := proposal.width { pw } else { 100.0 };
h := if ph := proposal.height { ph } else { 100.0 };
w + h
}
}
main :: () -> void {
w := Widget.{};
s : Sizable = w;
// These work:
r1 := s.size(ProposedSize.{ width = 50.0, height = null });
print("r1 = {}\n", r1);
r2 := s.size(ProposedSize.{ width = null, height = 50.0 });
print("r2 = {}\n", r2);
// This fails with "scalar-to-vector conversion failed":
r3 := s.size(ProposedSize.{ width = null, height = null });
print("r3 = {}\n", r3);
}

View File

@@ -0,0 +1,56 @@
// Optional `?f32` fields in struct literals — exhaustively combine null/value
// for both fields, through both direct calls and protocol dispatch.
#import "modules/std.sx";
ProposedSize :: struct {
width: ?f32;
height: ?f32;
}
// Direct function — does it work?
direct_size :: (proposal: ProposedSize) -> f32 {
w := if pw := proposal.width { pw } else { 100.0 };
h := if ph := proposal.height { ph } else { 100.0 };
w + h
}
Sizable :: protocol {
size :: (self: *Self, proposal: ProposedSize) -> f32;
}
Widget :: struct {}
impl Sizable for Widget {
size :: (self: *Widget, proposal: ProposedSize) -> f32 {
w := if pw := proposal.width { pw } else { 100.0 };
h := if ph := proposal.height { ph } else { 100.0 };
w + h
}
}
main :: () -> void {
// Test 1: Direct call
print("=== Direct calls ===\n");
d1 := direct_size(ProposedSize.{ width = 50.0, height = null });
print("d1 = {}\n", d1);
d2 := direct_size(ProposedSize.{ width = null, height = 50.0 });
print("d2 = {}\n", d2);
d3 := direct_size(ProposedSize.{ width = null, height = null });
print("d3 = {}\n", d3);
d4 := direct_size(ProposedSize.{ width = 50.0, height = 60.0 });
print("d4 = {}\n", d4);
// Test 2: Protocol dispatch
print("=== Protocol dispatch ===\n");
w := Widget.{};
s : Sizable = w;
r1 := s.size(ProposedSize.{ width = 50.0, height = null });
print("r1 = {}\n", r1);
r2 := s.size(ProposedSize.{ width = null, height = 50.0 });
print("r2 = {}\n", r2);
r3 := s.size(ProposedSize.{ width = null, height = null });
print("r3 = {}\n", r3);
r4 := s.size(ProposedSize.{ width = 50.0, height = 60.0 });
print("r4 = {}\n", r4);
}

View File

@@ -0,0 +1,31 @@
// any_to_string didn't handle optionals. A struct field of type `?T`
// printed as `<?>` (any_to_string's "no case matched" default)
// because there was no `case optional:` arm, and no dispatch table
// entry mapping `?T` TypeIds to the optional category.
//
// The variadic auto-unwrap path (packVariadicCallArgs) papered over
// this for direct `print("{}\n", opt)` calls — it stringified
// optionals to either the inner value's repr or `"null"` BEFORE
// boxing as Any. But anywhere else that boxes an optional and reads
// it back through any_to_string (struct field printing,
// `xx opt : Any`, future user code) hit the `<?>` floor.
//
// Locks in the fix: each ?T variant routes through `case optional:`
// → `optional_to_string(cast(type) val)` → either the inner value's
// `any_to_string` representation or the literal `"null"`.
#import "modules/std.sx";
S :: struct {
a: ?i64;
b: ?string;
c: ?bool;
}
main :: () {
filled := S.{ a = 42, b = "hi", c = true };
print("{}\n", filled);
empty := S.{ a = null, b = null, c = null };
print("{}\n", empty);
0;
}

View File

@@ -0,0 +1,51 @@
// Postfix `!` (optional force-unwrap) chained directly with a member access.
// `opt!.field`, `opt!.method()`, `opt!.a.b`, and `opt![i]` must read the same
// value the bind-first form (`v := opt!; v.field`) produces — the unwrapped
// value's type has to flow into the chained access.
//
// Regression (issue 0101): chained `opt!.field` typed its receiver as
// `.unresolved` (inferExprType had no force_unwrap arm), so a string field read
// as garbage and `opt!.method()` failed to resolve at all.
#import "modules/std.sx";
Inner :: struct { tag: string; k: i64; }
S :: struct {
id: string;
n: i64;
inner: Inner;
greet :: (self: *S) -> string { return self.id; } // pointer receiver
bump :: (self: S, extra: i64) -> i64 { return self.n + extra; } // value receiver
}
mk :: () -> ?S {
return S.{ id = "hello", n = 42, inner = Inner.{ tag = "deep", k = 7 } };
}
arr :: () -> ?[3]i64 {
v : [3]i64 = .[10, 20, 30];
return v;
}
main :: () -> void {
// opt!.field — string and int field, chained vs bind-first.
print("chain id: {}\n", mk()!.id); // hello
print("chain n: {}\n", mk()!.n); // 42
v := mk()!;
print("bind id: {}\n", v.id); // hello
print("bind n: {}\n", v.n); // 42
// opt!.method()
print("meth ptr: {}\n", mk()!.greet()); // hello
print("meth val: {}\n", mk()!.bump(8)); // 50
// nested opt!.a.b
print("nest tag: {}\n", mk()!.inner.tag); // deep
print("nest k: {}\n", mk()!.inner.k); // 7
// opt![i]
print("index 0: {}\n", arr()![0]); // 10
print("index 2: {}\n", arr()![2]); // 30
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,20 @@
x = 42
y = null
x! = 42
x ?? 0 = 42
y ?? 99 = 99
if-bind x: 42
if-bind y: none
match some: 42
match none: 0
p?.value = 10
q?.value = 0
o1.inner?.val = 99
o2.inner?.val = 0
narrowed a: 10
guard 42: 42
guard null: 0
both: 10 20
guard2: 7
default next: null
comptime: 141

View File

@@ -0,0 +1,6 @@
ok: floating is null
ok: left h=.leading
ok: center h=.center
ok: top_right h=.trailing v=.top
rect
text

View File

@@ -0,0 +1,3 @@
r1 = 150.000000
r2 = 150.000000
r3 = 200.000000

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
=== Direct calls ===
d1 = 150.000000
d2 = 150.000000
d3 = 200.000000
d4 = 110.000000
=== Protocol dispatch ===
r1 = 150.000000
r2 = 150.000000
r3 = 200.000000
r4 = 110.000000

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
S{a: 42, b: hi, c: true}
S{a: null, b: null, c: null}

View File

@@ -0,0 +1,10 @@
chain id: hello
chain n: 42
bind id: hello
bind n: 42
meth ptr: hello
meth val: 50
nest tag: deep
nest k: 7
index 0: 10
index 2: 30