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:
97
examples/optionals/0900-optionals-optionals.sx
Normal file
97
examples/optionals/0900-optionals-optionals.sx
Normal 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;
|
||||
}
|
||||
85
examples/optionals/0901-optionals-match-optional-arms.sx
Normal file
85
examples/optionals/0901-optionals-match-optional-arms.sx
Normal 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);
|
||||
}
|
||||
39
examples/optionals/0902-optionals-optional-all-null.sx
Normal file
39
examples/optionals/0902-optionals-optional-all-null.sx
Normal 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);
|
||||
}
|
||||
56
examples/optionals/0903-optionals-optional-roundtrip.sx
Normal file
56
examples/optionals/0903-optionals-optional-roundtrip.sx
Normal 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);
|
||||
}
|
||||
31
examples/optionals/0904-optionals-any-to-string-optional.sx
Normal file
31
examples/optionals/0904-optionals-any-to-string-optional.sx
Normal 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;
|
||||
}
|
||||
51
examples/optionals/0905-optionals-unwrap-field-chain.sx
Normal file
51
examples/optionals/0905-optionals-unwrap-field-chain.sx
Normal 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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
20
examples/optionals/expected/0900-optionals-optionals.stdout
Normal file
20
examples/optionals/expected/0900-optionals-optionals.stdout
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
r1 = 150.000000
|
||||
r2 = 150.000000
|
||||
r3 = 200.000000
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
17224
examples/optionals/expected/0903-optionals-optional-roundtrip.ir
Normal file
17224
examples/optionals/expected/0903-optionals-optional-roundtrip.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
16135
examples/optionals/expected/0904-optionals-any-to-string-optional.ir
Normal file
16135
examples/optionals/expected/0904-optionals-any-to-string-optional.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
S{a: 42, b: hi, c: true}
|
||||
S{a: null, b: null, c: null}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user