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,30 @@
// impl Protocol for built-in scalar types (f32, i64, bool, u32, ...) —
// both static dispatch (`f32.lerp(...)`) and protocol-boxed dispatch via
// `#inline` erasure.
Lerpable :: protocol #inline {
lerp :: (self: *Self, b: Self, t: f32) -> Self;
}
impl Lerpable for f32 {
lerp :: (self: f32, b: f32, t: f32) -> f32 { self + (b - self) * t }
}
do_lerp :: (a: Lerpable, b: f32, t: f32) -> f32 {
a.lerp(b, t)
}
main :: () -> void {
// Static call through impl
result := f32.lerp(0.0, 10.0, 0.5);
print("lerp(0, 10, 0.5) = {}\n", result);
// Protocol dispatch through #inline erasure
val : f32 = 0.0;
p : *f32 = @val;
l : Lerpable = xx p;
result2 := do_lerp(l, 10.0, 0.25);
print("lerp(0, 10, 0.25) = {}\n", result2);
}
#import "modules/std.sx";

View File

@@ -0,0 +1,48 @@
// Protocol value as a field of a wrapper struct, constructed from a stack
// local inside a function and appended to a `List`. The payload must be
// heap-copied so dispatch survives the constructing function returning.
#import "modules/std.sx";
Sizable :: protocol {
size :: (self: *Self) -> i64;
}
Widget :: struct { value: i64; }
impl Sizable for Widget {
size :: (self: *Widget) -> i64 { self.value }
}
// Wrapper struct with a protocol field (like ViewChild)
Item :: struct {
view: Sizable;
}
Container :: struct {
items: List(Item);
add :: (self: *Container, w: Widget) {
p := w; // local copy
self.items.append(Item.{ view = p }); // protocol created from stack local `p`
// Works here: stack local `p` is still alive
out("inside add: ");
print("{}\n", self.items.items[self.items.len - 1].view.size());
}
}
main :: () -> void {
c : Container = .{};
c.add(Widget.{ value = 42 });
c.add(Widget.{ value = 99 });
// BUG: items[0] should return 42, but returns 99 (reads items[1]'s stack slot)
// Both protocol values point to the same stack address (the `p` local in add())
r0 := c.items.items[0].view.size();
r1 := c.items.items[1].view.size();
print("items[0] = {} (expected 42)\n", r0);
print("items[1] = {} (expected 99)\n", r1);
// With more stack activity between add() and the reads, this crashes
// (stack memory overwritten by other function calls)
}

View File

@@ -0,0 +1,42 @@
// `List(Protocol)` appended from inside a helper function, dispatched
// repeatedly from `main` after the helper returns. Exercises the heap-copy
// path for both implicit-erasure-on-append and pre-erased protocol values.
#import "modules/std.sx";
Sizable :: protocol {
size :: (self: *Self) -> i64;
}
Leaf :: struct { value: i64; }
impl Sizable for Leaf {
size :: (self: *Leaf) -> i64 { self.value }
}
add :: (items: *List(Sizable), w: Leaf) {
p := w;
items.append(p); // protocol value created from stack local `p`
}
main :: () -> void {
// Works: protocol value created in main, appended to list
out("=== Created in main ===\n");
list_a : List(Sizable) = .{};
s : Sizable = Leaf.{ value = 42 };
list_a.append(s);
r1 := list_a.items[0].size();
print("first: {} (expected 42)\n", r1);
r2 := list_a.items[0].size();
print("second: {} (expected 42)\n", r2);
// BUG: protocol value created in add(), first dispatch works, second crashes
out("=== Created in add() ===\n");
list_b : List(Sizable) = .{};
add(@list_b, Leaf.{ value = 99 });
r3 := list_b.items[0].size();
print("first: {} (expected 99)\n", r3); // works (stack not yet clobbered)
r4 := list_b.items[0].size();
print("second: {} (expected 99)\n", r4); // CRASH: stack memory reused
out("=== OK ===\n");
}

View File

@@ -0,0 +1,44 @@
// Protocol dispatch on `List(Protocol)` items where the list pointer is
// passed into another function — verifies the boxed payload survives an
// extra call frame between erasure and dispatch.
#import "modules/std.sx";
Sizable :: protocol {
size :: (self: *Self) -> i64;
}
Leaf :: struct { value: i64; }
impl Sizable for Leaf {
size :: (self: *Leaf) -> i64 { self.value }
}
add :: (items: *List(Sizable), w: Leaf) {
p := w;
items.append(p);
}
dispatch_fn :: (items: *List(Sizable)) {
out("dispatch_fn: about to dispatch\n");
s := items.items[0].size();
print("dispatch_fn: result = {}\n", s);
}
main :: () -> void {
items : List(Sizable) = .{};
add(@items, Leaf.{ value = 42 });
// Direct dispatch twice
out("=== Direct 1 ===\n");
r1 := items.items[0].size();
print("r1 = {}\n", r1);
out("=== Direct 2 ===\n");
r2 := items.items[0].size();
print("r2 = {}\n", r2);
// Then from function
out("=== From function ===\n");
dispatch_fn(@items);
out("=== OK ===\n");
}

View File

@@ -0,0 +1,79 @@
// Dot-shorthand `.{ child = d }` for a struct whose first field is a protocol
// value, used as the argument to `List(Container).append` from two distinct
// container types. Exercises the cross-callsite path of dot-shorthand inference.
#import "modules/std.sx";
Drawable :: protocol {
draw :: (self: *Self) -> i32;
name :: (self: *Self) -> string;
layout :: (self: *Self, x: i32) -> i32;
handle :: (self: *Self, event: i32) -> bool;
}
Circle :: struct { radius: i32; }
impl Drawable for Circle {
draw :: (self: *Circle) -> i32 { self.radius }
name :: (self: *Circle) -> string { "circle" }
layout :: (self: *Circle, x: i32) -> i32 { x + self.radius }
handle :: (self: *Circle, event: i32) -> bool { event > 0 }
}
Square :: struct { side: i32; }
impl Drawable for Square {
draw :: (self: *Square) -> i32 { self.side * self.side }
name :: (self: *Square) -> string { "square" }
layout :: (self: *Square, x: i32) -> i32 { x + self.side }
handle :: (self: *Square, event: i32) -> bool { event > 1 }
}
Rect :: struct {
x: f32;
y: f32;
w: f32;
h: f32;
zero :: () -> Rect { Rect.{ x = 0.0, y = 0.0, w = 0.0, h = 0.0 } }
}
Container :: struct {
child: Drawable;
computed_frame: Rect = .zero();
}
// Two different structs, each with List(Container), both calling .append(.{...})
// This mirrors VStack/HStack in the game.
StackA :: struct {
children: List(Container);
add :: (self: *StackA, d: Drawable) {
// BUG: `.{ child = d }` causes LLVM error when 2+ structs do this
self.children.append(.{ child = d });
}
}
StackB :: struct {
children: List(Container);
add :: (self: *StackB, d: Drawable) {
// BUG: second struct doing `.{ child = d }` triggers the error
self.children.append(.{ child = d });
// FIX: explicit `Container.{ child = d }` works
// self.children.append(Container.{ child = d });
}
}
main :: () -> void {
c := Circle.{ radius = 42 };
s := Square.{ side = 5 };
a : StackA = .{};
a.add(c);
print("StackA: draw={}\n", a.children.items[0].child.draw());
b : StackB = .{};
b.add(s);
print("StackB: draw={}\n", b.children.items[0].child.draw());
print("OK\n");
}

View File

@@ -0,0 +1,32 @@
// Sub-32-bit enum variants ride through a protocol-typed receiver's
// method call without being collapsed to tag=0.
#import "modules/std.sx";
Fmt :: enum { a; b; }
Proto :: protocol {
take_fmt :: (self: *Self, f: Fmt);
}
Impl :: struct {}
impl Proto for Impl {
take_fmt :: (self: *Impl, f: Fmt) {
n : i64 = xx f;
print("proto f = {}\n", n);
}
}
take :: (f: Fmt) -> i64 {
n : i64 = xx f;
n
}
main :: () -> i32 {
print("direct a={} b={}\n", take(.a), take(.b));
p : Proto = xx @Impl.{};
p.take_fmt(.b);
p.take_fmt(.a);
0
}

View File

@@ -0,0 +1,30 @@
// A protocol method declared with a real pointer return (`-> *u8`,
// NOT `-> Self`) returns the raw pointer to the caller without the
// dispatch path auto-dereferencing it. Without this, a method whose
// pointee is a single byte gets `sizeof(target)` bytes loaded past
// it and segfaults.
#import "modules/std.sx";
Proto :: protocol {
get :: (self: *Self) -> *u8;
}
Impl :: struct {
val: u8 = 42;
}
impl Proto for Impl {
get :: (self: *Impl) -> *u8 {
@self.val
}
}
main :: () -> i32 {
imp : Impl = .{};
p : Proto = xx @imp;
raw : *u8 = p.get();
addr_word : u64 = xx raw;
print("got pointer: {}\n", addr_word != 0);
0
}

View File

@@ -0,0 +1,20 @@
// Phase 1 (xx-via-Into mechanism): proves the new syntax parses + lowers
// without error. The parameterised protocol Into(Target: Type) and the
// matching `impl Into(Block) for Closure() -> void` declarations are
// registered but unused. Resolution (Phase 3) is what makes the impl
// reachable from `xx`.
#import "modules/std.sx";
MyTag :: struct { value: i64 = 0; }
impl Into(MyTag) for i64 {
convert :: (self: i64) -> MyTag {
.{ value = self }
}
}
main :: () -> i32 {
print("ok\n");
0
}

View File

@@ -0,0 +1,34 @@
// `?Protocol = null` — optional protocol boxes use sentinel-shape
// (ctx == null is the "none" state), so they cost no extra storage
// beyond the protocol's standard 2-pointer layout. Method calls on
// a non-null optional protocol auto-unwrap and dispatch through the
// vtable / inline fn-ptrs as usual.
#import "modules/std.sx";
GPU :: protocol {
ping :: (self: *Self) -> i64;
}
Impl :: struct {}
impl GPU for Impl {
ping :: (self: *Impl) -> i64 { 42 }
}
main :: () -> i32 {
g : ?GPU = null;
if g != null {
print("BAD: g not null at start\n");
} else {
print("g initially null\n");
}
g = xx @Impl.{};
if g != null {
n := g.ping();
print("after assign: g.ping() = {}\n", n);
} else {
print("BAD: g still null after assign\n");
}
0
}

View File

@@ -0,0 +1,30 @@
// A protocol method declared `-> *void` (literal void-pointer return,
// NOT `Self`) returns the underlying impl's pointer to the caller
// unchanged. The dispatch path must NOT auto-load from the result —
// `*void` outside a `Self`-disguise is a real pointer whose pointee
// size is unknown.
//
// Regression: target_type leaks from the surrounding scope (e.g. the
// enclosing function's return type). The dispatcher used to auto-load
// `sizeof(target_type)` bytes from every `*void` return, mistaking
// real pointers for Self-encoded boxes. Result was that
// `alloc.alloc_bytes(64)` through an Allocator protocol value returned the
// first 4 bytes of malloc'd memory interpreted as `i32` (= 0 → null).
#import "modules/std.sx";
#import "modules/std/mem.sx";
main :: () -> i32 {
gpa := GPA.init();
alloc : Allocator = xx gpa;
p_direct := gpa.alloc_bytes(64);
print("direct: null? {}\n", p_direct == null);
p_protocol := alloc.alloc_bytes(64);
print("protocol: null? {}\n", p_protocol == null);
print("alloc_count: {}\n", gpa.alloc_count);
0
}

View File

@@ -0,0 +1,10 @@
// Helper that defines the impl. 179-impl-visibility's user file does NOT
// directly import this — that's the whole point of the test.
#import "modules/std.sx";
#import "./0410-protocols-impl-visibility-types.sx";
impl Into(Wrap) for i64 {
convert :: (self: i64) -> Wrap {
.{ v = self * 10 }
}
}

View File

@@ -0,0 +1,2 @@
// Shared type for 179-impl-visibility — Wrap struct.
Wrap :: struct { v: i64 = 0; }

View File

@@ -0,0 +1,10 @@
// User file uses xx but only imports the shared types — NOT the impl.
// The Phase 4 visibility filter should reject the impl from 179-impl-visibility-impl.sx.
#import "modules/std.sx";
#import "./0410-protocols-impl-visibility-types.sx";
run_user :: () -> i32 {
w : Wrap = xx 7;
print("user: w.v = {}\n", w.v);
0
}

View File

@@ -0,0 +1,21 @@
// Impl visibility — an `impl Into(...) for ...` is registered into
// the global impl table when its module is imported anywhere in the
// program, but is only **visible** from files that themselves
// transitively import the impl's defining module.
//
// Setup:
// - 179-impl-visibility-impl.sx declares an `impl Into(Wrap) for i64`.
// - 179-impl-visibility-user.sx tries `xx 7 : Wrap` but only
// imports the shared types — NOT the impl module.
// - The xx at the user-file site must produce a "no visible xx
// conversion" diagnostic, not silently fall through to whatever
// was registered in another module.
//
// The diagnostic is the success criterion — the compile error is the
// expected output. Tests/expected/.txt captures it; .exit is 1.
#import "modules/std.sx";
#import "./0410-protocols-impl-visibility-impl.sx";
#import "./0410-protocols-impl-visibility-user.sx";
main :: () -> i32 { run_user() }

View File

@@ -0,0 +1,9 @@
// Helper A — one of two conflicting impls for the same (i64, Wrap) pair.
#import "modules/std.sx";
#import "./0411-protocols-impl-duplicate-types.sx";
impl Into(Wrap) for i64 {
convert :: (self: i64) -> Wrap {
.{ v = self * 10 }
}
}

View File

@@ -0,0 +1,9 @@
// Helper B — second conflicting impl for the same (i64, Wrap) pair.
#import "modules/std.sx";
#import "./0411-protocols-impl-duplicate-types.sx";
impl Into(Wrap) for i64 {
convert :: (self: i64) -> Wrap {
.{ v = self + 100 }
}
}

View File

@@ -0,0 +1,2 @@
// Shared type for 180-impl-duplicate.
Wrap :: struct { v: i64 = 0; }

View File

@@ -0,0 +1,26 @@
// Duplicate impl detection — two impls for the same (Source, Target)
// pair are both visible from the same `xx` site (because both their
// defining modules are transitively imported). The compiler must
// emit a "duplicate xx conversion" diagnostic naming both modules,
// not silently pick one or the other.
//
// Setup:
// - 180-impl-duplicate-impl-a.sx: `impl Into(Wrap) for i64` (mul by 10).
// - 180-impl-duplicate-impl-b.sx: `impl Into(Wrap) for i64` (add 100).
// - Main imports both; the `xx 7 : Wrap` site must error.
//
// Expected exit = 1, expected output = the focused diagnostic naming
// both impl modules.
#import "modules/std.sx";
// `Wrap` is declared in the shared types module; bare-import visibility is
// non-transitive, so naming it here means importing it here (not via impl-a/b).
#import "./0411-protocols-impl-duplicate-types.sx";
#import "./0411-protocols-impl-duplicate-impl-a.sx";
#import "./0411-protocols-impl-duplicate-impl-b.sx";
main :: () -> i32 {
w : Wrap = xx 7;
print("w.v = {}\n", w.v);
0
}

View File

@@ -0,0 +1,19 @@
// Duplicate impl detection (same-file) — two `impl Into(MyA) for i64`
// declarations in the same file produce a "duplicate impl" diagnostic
// at registration time, not silently shadow one with the other. Sibling
// case to `examples/180-impl-duplicate.sx` which covers the
// cross-module variant.
#import "modules/std.sx";
MyA :: struct { v: i64 = 0; }
impl Into(MyA) for i64 {
convert :: (self: i64) -> MyA { .{ v = self } }
}
impl Into(MyA) for i64 {
convert :: (self: i64) -> MyA { .{ v = self * 2 } }
}
main :: () -> i32 { 0 }

View File

@@ -0,0 +1,22 @@
// Phase 4.2 — parameterized protocol as a runtime VALUE type. `VL(i64)` is a
// 16-byte protocol value {ctx, vtable} (a plain protocol was already, but a
// parameterized one used to resolve to a 0-field stub). A conforming struct
// `xx`-erases into it, and method dispatch uses the bound type-arg
// (`get -> T` becomes `get -> i64` for `VL(i64)`).
#import "modules/std.sx";
VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; }
StrCell :: struct { s: string; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
main :: () -> i32 {
a : VL(i64) = xx IntCell.{ v = 42 };
print("a.get={}\n", a.get()); // 42 (T = i64)
b : VL(string) = xx StrCell.{ s = "hi" };
print("b.get={}\n", b.get()); // hi (T = string)
0
}

View File

@@ -0,0 +1,33 @@
// Phase 6 / issue 0054 — generic-struct → parameterized-protocol erasure.
// - A generic-struct impl method `self: *Box` now resolves `self.field` to the
// concrete INSTANCE (the template name binds to the instance type), so
// `self.x` works (was "field not found on type 'Box'").
// - `xx c` erases a generic-struct instance (`Combined__i64_i64`) to a
// parameterized protocol (`VL(i64)`) via the generic
// `impl VL($R) for Combined($R, ..$Ts)`: the thunk monomorphizes the
// template method for the instance and dispatch works (was a trap).
#import "modules/std.sx";
VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts));
value: $R;
}
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; }
make :: (..sources: VL) -> VL(i64) {
c : Combined(i64, ..sources.T) = ---;
c.value = 99;
c.sources = (..sources);
return xx c; // Combined__i64_i64 -> VL(i64)
}
main :: () -> i32 {
r := make(IntCell.{ v = 1 });
print("{}\n", r.get()); // 99 (dispatch through the erased Combined)
0
}

View File

@@ -0,0 +1,617 @@
#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 = ---;
}
OptNode :: struct {
value: i32;
next: ?i32;
}
OptInner :: struct { val: i32; }
OptOuter :: struct { inner: ?OptInner; }
MyFloat :: f64;
Perms :: enum flags { read; write; execute; }
Status :: enum u8 { ok; err; timeout; }
WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }
// --- Top-level functions ---
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: i32, b: i32) -> i32 { a * b }
identity :: (x: $T) -> T { x }
pair_add :: (a: $T, b: $U) -> i64 {
cast(i64) a + cast(i64) b
}
typed_sum :: (..args: []i32) -> i32 {
result := 0;
for args (it) { result = result + it; }
result
}
apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
f(x, y)
}
void_return :: () {
return;
}
implicit_return :: (x: i32) -> i32 {
x * 2
}
early_return :: (x: i32) -> i32 {
if x > 10 { return 99; }
x
}
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
.[x, y, z]
}
point_sum :: (p: Point) -> i32 { p.x + p.y }
// #run compile-time constants
// #run compile-time constants
CT_VAL :: #run add(10, 15);
CT_MUL :: #run mul(6, 7);
CT_CHAIN :: #run add(CT_VAL, 5);
// #run compile-time optional tests
// #run compile-time optional tests
ct_opt_coalesce :: () -> i32 {
x: ?i32 = 42;
y: ?i32 = null;
return (x ?? 0) + (y ?? 99);
}
ct_opt_unwrap :: () -> i32 {
x: ?i32 = 77;
return x!;
}
ct_opt_guard :: () -> i32 {
x: ?i32 = 10;
if x == null { return -1; }
return x;
}
CT_OPT_COALESCE :: #run ct_opt_coalesce();
CT_OPT_UNWRAP :: #run ct_opt_unwrap();
CT_OPT_GUARD :: #run ct_opt_guard();
// #insert helpers
// #insert helpers
gen_code :: () -> string {
return "print(\"insert-ok\\n\");";
}
gen_val :: () -> string {
return "print(\"insert-gen: {}\\n\", 42);";
}
// --- Error handling (failable functions: sets, raise/try/catch/or/onfail) ---
SmokeErr :: error { Empty, BadDigit, Overflow }
// value-carrying, named set: raise three tags or succeed
// value-carrying, named set: raise three tags or succeed
sm_parse :: (n: i32) -> (i32, !SmokeErr) {
if n < 0 { raise error.BadDigit; }
if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; }
return n * 2;
}
// pure failable, inferred set (ad-hoc tag minted into `!`)
// pure failable, inferred set (ad-hoc tag minted into `!`)
sm_check :: (ok: bool) -> ! {
if !ok { raise error.NotReady; }
return;
}
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
sm_pair :: (a: i32, b: i32) -> (i32, i32, !) {
x := try sm_parse(a);
y := try sm_parse(b);
return (x, y);
}
// `catch` block that diverges (logs the tag, then returns a fallback)
// `catch` block that diverges (logs the tag, then returns a fallback)
sm_or_default :: (n: i32) -> i32 {
return sm_parse(n) catch (e) {
print(" logged {}\n", e);
return -1;
};
}
// `onfail` + `defer` interleave: cleanup runs only on the error path
// `onfail` + `defer` interleave: cleanup runs only on the error path
sm_acquire :: (fail: bool) -> (i32, !) {
defer print(" smoke defer A\n");
onfail print(" smoke onfail B\n");
if fail { raise error.Acquire; }
return 7;
}
// `or`-chain: try a, fall to try b; propagate if both fail
// `or`-chain: try a, fall to try b; propagate if both fail
sm_first :: (a: i32, b: i32) -> (i32, !) {
v := try sm_parse(a) or try sm_parse(b);
return v;
}
// --- Extern function binding ---
// --- Extern function binding ---
libc :: #library "c";
c_abs :: (n: i32) -> i32 extern libc "abs";
// --- Protocol declarations (Phase 1: static dispatch only) ---
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 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline {
add :: (self: *Self, n: i32);
value :: (self: *Self) -> i32;
}
Accumulator :: struct {
total: i32;
}
impl Adder for Accumulator {
add :: (self: *Accumulator, n: i32) { self.total += n; }
value :: (self: *Accumulator) -> i32 { self.total }
}
Doubler :: struct { val: i32; }
impl Adder for Doubler {
add :: (self: *Doubler, n: i32) { self.val = self.val + n + n; }
value :: (self: *Doubler) -> i32 { self.val }
}
// Phase 4: default methods
// Phase 4: default methods
Repeater :: protocol {
say :: (self: *Self, msg: string);
say_twice :: (self: *Self, msg: string) {
self.say(msg);
self.say(msg);
}
}
Printer :: struct { count: i32; }
impl Repeater for Printer {
say :: (self: *Printer, msg: string) {
self.count += 1;
out(msg);
}
}
// P4 edge: Chained default→default calls
// P4 edge: Chained default→default calls
Chained :: protocol {
base :: (self: *Self, msg: string) -> i32;
wrap :: (self: *Self, msg: string) -> i32 {
self.base(msg) + 1
}
double_wrap :: (self: *Self, msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg)
}
}
ChainImpl :: struct { val: i32; }
impl Chained for ChainImpl {
base :: (self: *ChainImpl, msg: string) -> i32 {
self.val += 1;
msg.len
}
}
// Phase 5: Self type
// Phase 5: Self type
Eq :: protocol {
eq :: (self: *Self, other: Self) -> bool;
}
impl Eq for Point {
eq :: (self: *Point, other: Point) -> bool {
self.x == other.x and self.y == other.y
}
}
Cloneable :: protocol {
clone :: (self: *Self) -> Self;
}
impl Cloneable for Point {
clone :: (self: *Point) -> Point {
Point.{ x = self.x, y = self.y }
}
}
impl Eq for i64 {
eq :: (self: *i64, other: i64) -> bool {
self.* == other
}
}
// Phase 6: Generic constraints
// Phase 6: Generic constraints
are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
a.eq(b)
}
Hashable :: protocol {
hash :: (self: *Self) -> i64;
}
impl Hashable for Point {
hash :: (self: *Point) -> i64 {
xx self.x * 31 + xx self.y
}
}
eq_and_hash :: ($T: Type/Eq/Hashable, a: T, b: T) -> bool {
if a.hash() != b.hash() { return false; }
a.eq(b)
}
// P6.4: inline constraint syntax ($T/Protocol)
// P6.4: inline constraint syntax ($T/Protocol)
sum_of_inline :: (a: $T/Summable, b: T) -> i32 {
a.sum() + b.sum()
}
// Phase 7: Generic struct impls
// 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
// P6.5: Struct type param constraints
SumBox :: struct ($T: Type/Summable) {
val: T;
}
// ============================================================
// Struct constants test
// ============================================================
// Struct constants test
Phys :: struct {
x, y: f32;
GRAVITY :f32: 9.81;
MAX_SPEED :: 100;
}
// Init block test struct
// 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
// Global variable for address-of test
g_smoke_val : i32 = 42;
write_to_ptr :: (p: *i32) {
p.* = 99;
}
main :: () {
// ========================================================
// PROTOCOLS (Phase 1: static dispatch)
// ========================================================
print("=== Protocols ===\n");
// P1.1: Basic protocol + impl, direct call on concrete type
{
sc := SimpleCounter.{ val = 0 };
sc.inc();
sc.inc();
sc.inc();
print("P1.1: {}\n", sc.get());
}
// P1.2: impl in separate scope (retroactive conformance)
{
p := Point.{ x = 10, y = 20 };
print("P1.2: {}\n", p.sum());
}
// P2.1: #inline protocol — xx conversion + dynamic dispatch
{
acc := Accumulator.{ total = 0 };
a : Adder = xx @acc;
a.add(10);
a.add(20);
a.add(12);
print("P2.1: {}\n", a.value());
}
// P2.2: pass protocol value to function
{
use_adder :: (a: Adder, n: i32) -> i32 {
a.add(n);
a.value()
}
acc := Accumulator.{ total = 100 };
result := use_adder(xx @acc, 50);
print("P2.2: {}\n", result);
}
// P2.3: different impls through same protocol type
{
acc := Accumulator.{ total = 0 };
dbl := Doubler.{ val = 0 };
a1 : Adder = xx @acc;
a2 : Adder = xx @dbl;
a1.add(5);
a2.add(5);
print("P2.3: {} {}\n", a1.value(), a2.value());
}
// P3.1: vtable-pointer protocol (default, no #inline)
{
sc := SimpleCounter.{ val = 0 };
c : Counter = xx @sc;
c.inc();
c.inc();
c.inc();
c.inc();
c.inc();
print("P3.1: {}\n", c.get());
}
// P3.2: vtable protocol passed to function
{
use_counter :: (c: Counter) -> i32 {
c.inc();
c.inc();
c.get()
}
sc := SimpleCounter.{ val = 10 };
result := use_counter(xx @sc);
print("P3.2: {}\n", result);
}
// P4.1: default method calls required method (static dispatch)
{
pr := Printer.{ count = 0 };
pr.say_twice("hi ");
print("\nP4.1: {}\n", pr.count);
}
// P4.2: default method via dynamic dispatch (vtable)
{
pr := Printer.{ count = 0 };
r : Repeater = xx @pr;
r.say_twice("yo ");
print("\nP4.2: {}\n", pr.count);
}
// P4.3: chained default→default calls via vtable
{
ci := ChainImpl.{ val = 0 };
ch : Chained = xx @ci;
// double_wrap calls wrap twice, wrap calls base once each
// base("hi") returns 2 (len), wrap adds 1 → 3, double_wrap = 3 + 3 = 6
result := ch.double_wrap("hi");
// base was called 2 times (once per wrap call)
print("P4.3: {} {}\n", result, ci.val);
}
// P5.1: Self type in protocol — static dispatch
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P5.1: {} {}\n", p1.eq(p2), p1.eq(p3));
}
// P5.2: Self in return position
{
p := Point.{ x = 10, y = 20 };
p2 := p.clone();
print("P5.2: {} {}\n", p2.x, p2.y);
}
// P5.5: impl for primitive type
{
x := 42;
y := 42;
z := 99;
r1 := x.eq(y);
r2 := x.eq(z);
print("P5.5: {} {}\n", r1, r2);
}
// P5.3: Self with dynamic dispatch (erased to *void)
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
e : Eq = xx p1;
print("P5.3: {} {}\n", e.eq(p2), e.eq(p3));
}
// P6.1: Single constraint — constrained generic function
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P6.1: {} {}\n", are_equal(p1, p2), are_equal(p1, p3));
}
// P6.2: Constraint with primitive type
{
print("P6.2: {} {}\n", are_equal(42, 42), are_equal(42, 99));
}
// P6.3: Multiple constraints
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P6.3: {} {}\n", eq_and_hash(p1, p2), eq_and_hash(p1, p3));
}
// P6.4: inline constraint syntax ($T/Protocol)
{
// sum_of_inline uses $T/Summable inline (not $T: Type/Summable)
p1 := Point.{ x = 10, y = 20 };
p2 := Point.{ x = 3, y = 7 };
print("P6.4: {}\n", sum_of_inline(p1, p2));
}
// P6.5: Struct type param constraints ($T: Type/Summable)
{
box := SumBox(Point).{ val = Point.{ x = 5, y = 15 } };
print("P6.5: {}\n", box.val.sum());
}
// P7.1: impl for generic struct
{
p := Pair(i32).{ a = 10, b = 20 };
print("P7.1: {}\n", p.sum());
}
// P7.2: generic struct impl with different type arg
{
p1 := Pair(i32).{ a = 3, b = 7 };
p2 := Pair(i64).{ a = 100, b = 200 };
print("P7.2: {} {}\n", p1.sum(), p2.sum());
}
// P2.4: xx in function return position (tested in standalone test_return.sx)
// Covered by: make_adder :: (acc: *Accumulator) -> Adder { xx acc; }
// P2.6: protocol values in arrays
{
acc := Accumulator.{ total = 0 };
dbl := Doubler.{ val = 0 };
adders : [2]Adder = .[xx @acc, xx @dbl];
i := 0;
while i < 2 {
adders[i].add(5);
i += 1;
}
print("P2.6: {} {}\n", acc.total, dbl.val);
}
// P2.7: xx on inline struct literal (no intermediate variable)
{
use_adder :: (a: Adder) -> i32 { a.add(10); a.value() }
result := use_adder(xx Accumulator.{ total = 5 });
print("P2.7: {}\n", result);
}
// P3.3: xx on inline struct literal with vtable protocol
{
use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
result := use_counter(xx SimpleCounter.{ val = 100 });
print("P3.3: {}\n", result);
}
}

View File

@@ -0,0 +1,92 @@
#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 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline {
add :: (self: *Self, n: i32);
value :: (self: *Self) -> i32;
}
Accumulator :: struct {
total: i32;
}
impl Adder for Accumulator {
add :: (self: *Accumulator, n: i32) { self.total += n; }
value :: (self: *Accumulator) -> i32 { self.total }
}
main :: () {
// --- Auto type erasure (AE) ---
print("=== Auto Type Erasure ===\n");
// AE1: function argument — concrete passed where protocol expected (no xx)
{
use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
sc := SimpleCounter.{ val = 10 };
result := use_counter(sc);
print("AE1: {}\n", result);
}
// AE2: variable assignment — concrete to protocol variable (no xx)
{
acc := Accumulator.{ total = 0 };
a: Adder = acc;
a.add(5);
a.add(3);
print("AE2: {}\n", a.value());
}
// AE3: struct literal passed directly (no xx)
{
use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
result := use_counter(SimpleCounter.{ val = 100 });
print("AE3: {}\n", result);
}
// AE4: explicit xx still works (not broken)
{
use_counter :: (c: Counter) -> i32 { c.inc(); c.get() }
result := use_counter(xx SimpleCounter.{ val = 50 });
print("AE4: {}\n", result);
}
// AE5: pointer auto-erasure — *ConcreteType to protocol
{
use_adder :: (a: Adder) { a.add(10); }
acc := Accumulator.{ total = 5 };
p := @acc;
use_adder(p);
print("AE5: {}\n", acc.total);
}
}

View File

@@ -0,0 +1,44 @@
// Protocol method signatures resolve their param/return type NAMES in the
// protocol's OWN declaring module (own-wins visibility), so a bare type name
// that collides with a same-name namespaced import binds to the local author.
//
// Here the user's `Event` enum shares its name with the stdlib
// `std/event.sx` `Event :: struct` (pulled in, namespaced as `event`, by
// `#import "modules/std.sx"`). `Plat.one_event` returns the user's `Event`;
// `ev := g_plat.one_event()` infers that type, so the `case .key_up:(e)`
// payload binds a `KeyData` and `.escape` resolves against `Keycode`.
//
// Regression (issue 0132): `registerProtocolDecl` used to resolve method
// signature types through the flat, visibility-UNAWARE `type_bridge`
// resolver, which picked the stdlib `event.Event` struct instead — typing
// `ev` as a fieldless struct, binding `.unresolved`, and emitting
// "enum literal '.escape' has no destination type to resolve against". The
// fix pins resolution to `pd.source_file`, mirroring the parameterized-
// protocol and concrete-fn signature paths.
//
// Expect: prints `escape!`, exit 0.
#import "modules/std.sx";
Keycode :: enum { unknown; escape; enter; }
KeyData :: struct { key: Keycode; }
Event :: enum { none; key_up: KeyData; }
Plat :: protocol { one_event :: (self: *Self) -> Event; }
Impl :: struct { dummy: i64; }
impl Plat for Impl {
one_event :: (self: *Impl) -> Event { return .key_up(.{ key = .escape }); }
}
main :: () {
impl : Impl = .{ dummy = 0 };
g_plat : Plat = xx @impl;
ev := g_plat.one_event(); // type INFERRED from protocol return
if ev == {
case .key_up: (e) {
// `e` is KeyData (payload of the user's Event), `.escape` a Keycode
if e.key == .escape { print("escape!\n"); }
}
}
}

View File

@@ -0,0 +1,47 @@
// Protocol methods declare their receiver EXPLICITLY as the first parameter —
// `self: *Self` (or `self: Self`) — matching the `impl` method signature. This
// is required (the old implicit-receiver form is a parse error). The receiver
// annotation is validated then erased, so dispatch and call sites are unchanged:
// a method with N declared extra args is still called with N args.
#import "modules/std.sx";
Size :: struct { w: i32; h: i32; }
// `self: *Self` receiver; one no-arg method and one with extra args.
Measurable :: protocol #inline {
measure :: (self: *Self) -> Size;
scaled :: (self: *Self, factor: i32) -> Size;
}
// `self: Self` (by-value) receiver is also accepted.
Named :: protocol {
name :: (self: Self) -> string;
}
Widget :: struct { base: i32; }
impl Measurable for Widget {
measure :: (self: *Widget) -> Size { return Size.{ w = self.base, h = self.base * 2 }; }
scaled :: (self: *Widget, factor: i32) -> Size {
return Size.{ w = self.base * factor, h = self.base * 2 * factor };
}
}
impl Named for Widget {
name :: (self: Widget) -> string { return "widget"; }
}
main :: () -> i32 {
w : Widget = .{ base = 5 };
p : *Widget = @w;
m : Measurable = xx p;
s := m.measure(); // 0 extra args
s2 := m.scaled(3); // 1 extra arg
print("measure={}x{}\n", s.w, s.h); // 5x10
print("scaled={}x{}\n", s2.w, s2.h); // 15x30
n : Named = xx p;
print("name={}\n", n.name()); // widget
return 0;
}

View File

@@ -0,0 +1,37 @@
// E6BR-2 / G8 (own-wins) — a protocol method-signature CONCRETE named return type
// resolves SOURCE-AWARE, pinned to the protocol's defining module. `main`
// flat-imports `dep.sx` (`Box { a }`) AND authors its OWN `Box { m }`; the
// protocol `Provider` returns `Box` and the impl builds main's `Box`. Dispatching
// `p.get()` must type the result as MAIN's `Box`, so `b.m` resolves.
//
// Fail-before (pre-E6BR-2): the method signature went through the no-author
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins), so the
// return typed as dep's `Box { a }` and `b.m` was `field 'm' not found on type
// 'Box'`. Pass-after: source-aware → main's `Box`, exit 0.
#import "modules/std.sx";
#import "0820-protocols-same-name-method-own-wins/dep.sx";
Box :: struct { m: i32; }
Provider :: protocol {
get :: (self: *Self) -> Box;
}
Holder :: struct { val: i32 = 7; }
impl Provider for Holder {
get :: (self: *Holder) -> Box {
b : Box = ---;
b.m = self.val;
b
}
}
main :: () -> i32 {
h : Holder = .{};
p : Provider = xx @h;
b := p.get();
print("m={} dep={}\n", b.m, dep_box());
0
}

View File

@@ -0,0 +1,10 @@
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
// main's same-name `Box { m }`. The protocol method-signature return must NOT bind
// this one — the disjoint field sets make a wrong binding a hard compile error.
Box :: struct { a: i32; }
dep_box :: () -> i32 {
b : Box = ---;
b.a = 9;
return b.a;
}

View File

@@ -0,0 +1,18 @@
// G8 (ambiguous half) — because a protocol method-signature concrete return type
// is now resolved SOURCE-AWARE, a genuinely ambiguous same-name type poisons
// LOUDLY at the protocol declaration instead of silently picking a global
// `findByName` last-wins author. `main` flat-imports two `Box` authors and
// declares none itself, so the `Provider.get` return `Box` is an unresolvable
// collision and the build exits 1.
#import "modules/std.sx";
#import "0821-protocols-same-name-method-ambiguous/a.sx";
#import "0821-protocols-same-name-method-ambiguous/b.sx";
Provider :: protocol {
get :: (self: *Self) -> Box;
}
main :: () -> i32 {
0
}

View File

@@ -0,0 +1,3 @@
// One of two flat-imported same-name `Box` authors — the bare `Box` in the
// protocol return is a genuine collision the source cannot disambiguate.
Box :: struct { a: i32; }

View File

@@ -0,0 +1,3 @@
// The second flat-imported same-name `Box` author. Two distinct flat authors and
// no own author → the protocol return `Box` is ambiguous.
Box :: struct { b: i32; }

View File

@@ -0,0 +1,78 @@
// E6BR-4 (own-wins) — a protocol method-signature names a same-name `Box` under
// every WRAPPER / COMPOUND form, and each must resolve SOURCE-AWARE (pinned to the
// protocol's defining module = main), selecting main's OWN `Box { m }` rather than
// the global last-wins `Box { a }` from the flat-imported `dep.sx`.
//
// The reconciled choke-point (resolveRegistrationSigTypeInSource → the recursive
// source-aware engine) recurses every structural shape and resolves the leaf
// author own-wins. The E6BR-4 RED CELL is the WRAPPED RETURN `() -> *Box`: pre-fix
// the wrapped sig fell to the no-author `type_bridge.resolveTemplateSignatureType`
// (global last-wins) and typed the result as `dep`'s `Box { a }`, so `bp.m` was
// `field 'm' not found on type 'Box'`. Discriminating returns: `*Box`, `?Box`,
// `(Box,Box)`, `[2]Box` (each observed by a `.m` access). Routing-only params:
// `*Box`, `?Box`, `[]Box`, `[2]Box`, `(Box,Box)`, nested `*?[]Box` — all resolved
// through the same `resolveCompound` recursion at protocol-decl registration.
#import "modules/std.sx";
#import "0824-protocols-same-name-method-wrapped-own-wins/dep.sx";
Box :: struct { m: i32; }
Holder :: struct { b: Box = ---; }
Provider :: protocol {
// discriminating wrapped/compound RETURNS
getp :: (self: *Self) -> *Box;
geto :: (self: *Self) -> ?Box;
gett :: (self: *Self) -> (Box, Box);
geta :: (self: *Self) -> [2]Box;
// routing-only wrapped/compound PARAMS
sump :: (self: *Self, p: *Box) -> i32;
sumo :: (self: *Self, o: ?Box) -> i32;
sums :: (self: *Self, s: []Box) -> i32;
suma :: (self: *Self, a: [2]Box) -> i32;
sumt :: (self: *Self, t: (Box, Box)) -> i32;
sumn :: (self: *Self, n: *?[]Box) -> i32;
}
impl Provider for Holder {
getp :: (self: *Holder) -> *Box { @self.b }
geto :: (self: *Holder) -> ?Box { self.b }
gett :: (self: *Holder) -> (Box, Box) { (self.b, self.b) }
geta :: (self: *Holder) -> [2]Box { r : [2]Box = ---; r[0] = self.b; r[1] = self.b; r }
sump :: (self: *Holder, p: *Box) -> i32 { p.m }
sumo :: (self: *Holder, o: ?Box) -> i32 { o!.m }
sums :: (self: *Holder, s: []Box) -> i32 { s[0].m }
suma :: (self: *Holder, a: [2]Box) -> i32 { a[0].m }
sumt :: (self: *Holder, t: (Box, Box)) -> i32 { t.0.m }
sumn :: (self: *Holder, n: *?[]Box) -> i32 { if n == null { 0 } else { 6 } }
}
main :: () -> i32 {
h : Holder = ---;
h.b.m = 7;
p : Provider = xx @h;
// discriminating returns — `.m` only resolves if each binds main's `Box`
bp := p.getp();
bo := p.geto();
bt := p.gett();
ba := p.geta();
// routing-only params, constructed in main and passed through the protocol
one : Box = ---; one.m = 1;
arr : [2]Box = ---; arr[0].m = 2; arr[1].m = 3;
sl : []Box = arr[0..2];
osl : ?[]Box = sl;
tup : (Box, Box) = (one, one);
sp := p.sump(@one);
so := p.sumo(one);
ss := p.sums(sl);
sa := p.suma(arr);
st := p.sumt(tup);
sn := p.sumn(@osl);
print("p={} o={} t={} a={} | sp={} so={} ss={} sa={} st={} sn={} dep={}\n",
bp.m, bo!.m, bt.0.m, ba[0].m, sp, so, ss, sa, st, sn, dep_box());
0
}

View File

@@ -0,0 +1,11 @@
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
// main's same-name `Box { m }`. The protocol method signatures below name `Box`
// under every wrapper/compound form; a wrong (last-wins) author binds this `Box`,
// whose disjoint field set makes a `.m` access a hard compile error.
Box :: struct { a: i32; }
dep_box :: () -> i32 {
b : Box = ---;
b.a = 9;
return b.a;
}

View File

@@ -0,0 +1,23 @@
// E6BR-4 (ambiguous) — because a protocol method-signature WRAPPED return type is
// now resolved SOURCE-AWARE (the reconciled choke-point recurses `*Box` and
// resolves its leaf via `resolveNominalLeaf`), a genuinely ambiguous same-name
// element poisons LOUDLY at the protocol declaration instead of silently picking a
// global `findByName` last-wins author. `main` flat-imports two `Box` authors and
// declares none itself, so the `Provider.getp` return `*Box` is an unresolvable
// collision and the build exits 1.
//
// Fail-before (pre-E6BR-4): the wrapped `*Box` fell to the no-author
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins, no
// diagnostic), so the build did NOT report the collision.
#import "modules/std.sx";
#import "0825-protocols-same-name-method-wrapped-ambiguous/a.sx";
#import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx";
Provider :: protocol {
getp :: (self: *Self) -> *Box;
}
main :: () -> i32 {
0
}

View File

@@ -0,0 +1,9 @@
// One of two flat-imported `Box` authors. With no own author in main, the
// protocol method-signature `() -> *Box` is a genuine collision.
Box :: struct { a: i32; }
a_box :: () -> i32 {
b : Box = ---;
b.a = 1;
return b.a;
}

View File

@@ -0,0 +1,8 @@
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`.
Box :: struct { b: i32; }
b_box :: () -> i32 {
x : Box = ---;
x.b = 2;
return x.b;
}

View File

@@ -0,0 +1,32 @@
// E6BR-4 (param-impl SOURCE, own-wins) — a parameterised-impl SOURCE type under a
// wrapper (`impl Into(Marker) for *Box`) registers SOURCE-AWARE, pinned to the
// impl's defining module (main), and the `xx` lookup at the use site mangles to the
// SAME author, so the conversion is found and runs. `main` authors its OWN
// `Box { m }` and flat-imports `dep.sx` (`Box { a }`); the `*Box` source binds
// main's `Box`, so `convert`'s `self.m` resolves and `xx @b` selects this impl.
//
// This locks the route-all registration path for the param-impl-source surface:
// the source `*Box` flows through `resolveRegistrationSigTypeInSource` →
// `resolveCompound` → `resolveNominalLeaf` (own-wins), never the no-author leaf.
// The param_impl_map key is name-based (`Into\0Marker\0*Box`), so registration and
// `tryUserConversion` agree by construction.
#import "modules/std.sx";
#import "0826-protocols-param-impl-source-wrapped-own-wins/dep.sx";
Box :: struct { m: i32; }
Marker :: struct { v: i32; }
impl Into(Marker) for *Box {
convert :: (self: *Box) -> Marker {
.{ v = self.m }
}
}
main :: () -> i32 {
b : Box = ---;
b.m = 7;
mk : Marker = xx @b;
print("v={} dep={}\n", mk.v, dep_box());
0
}

View File

@@ -0,0 +1,11 @@
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
// main's same-name `Box { m }`. The param-impl SOURCE `*Box` must register against
// main's `Box`, and the `xx` lookup at the use site must mangle to the SAME author,
// so the conversion is found and `self.m` resolves.
Box :: struct { a: i32; }
dep_box :: () -> i32 {
b : Box = ---;
b.a = 9;
return b.a;
}

View File

@@ -0,0 +1,27 @@
// E6BR-4 (param-impl SOURCE, ambiguous) — because a parameterised-impl wrapped
// SOURCE type is now registered SOURCE-AWARE (the source `*Box` flows through
// `resolveRegistrationSigTypeInSource` → `resolveCompound` → `resolveNominalLeaf`),
// a genuinely ambiguous same-name element poisons LOUDLY at registration instead of
// silently selecting a global `findByName` last-wins author. `main` flat-imports two
// `Box` authors and declares none itself, so `impl Into(Marker) for *Box` cannot
// pick a `Box` and the build exits 1.
//
// Fail-before (pre-E6BR-4): the source `*Box` fell to the no-author
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins, no
// diagnostic), so the collision was registered silently.
#import "modules/std.sx";
#import "0827-protocols-param-impl-source-wrapped-ambiguous/a.sx";
#import "0827-protocols-param-impl-source-wrapped-ambiguous/b.sx";
Marker :: struct { v: i32; }
impl Into(Marker) for *Box {
convert :: (self: *Box) -> Marker {
.{ v = 0 }
}
}
main :: () -> i32 {
0
}

View File

@@ -0,0 +1,9 @@
// One of two flat-imported `Box` authors; with no own author the param-impl
// SOURCE `*Box` is a genuine collision.
Box :: struct { a: i32; }
a_box :: () -> i32 {
b : Box = ---;
b.a = 1;
return b.a;
}

View File

@@ -0,0 +1,8 @@
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`.
Box :: struct { b: i32; }
b_box :: () -> i32 {
x : Box = ---;
x.b = 2;
return x.b;
}

View File

@@ -0,0 +1,31 @@
// E6BR-4 (param-impl ARG, own-wins) — a parameterised-impl PROTOCOL TYPE-ARG under
// a wrapper (`impl Tagged(*Box) for Holder`) is resolved through the same
// source-aware registration helper (`resolveRegistrationSigTypeInSource` with the
// `.param_impl_arg` purpose), pinned to the impl's defining module (main). The arg
// `*Box` flows through `resolveCompound` → `resolveNominalLeaf` and selects main's
// OWN `Box { m }` rather than the flat-imported `dep.sx` `Box { a }`, so the impl
// registers and `Holder.tag` is callable. (The `param_impl_map` key is name-based,
// so own-wins registration and any later lookup agree by construction; this cell
// locks that the wrapped arg routes through the engine, not the no-author leaf.)
#import "modules/std.sx";
#import "0828-protocols-param-impl-arg-wrapped-own-wins/dep.sx";
Box :: struct { m: i32; }
Holder :: struct { n: i32; }
Tagged :: protocol(T: Type) {
tag :: (self: *Self) -> i32;
}
impl Tagged(*Box) for Holder {
tag :: (self: *Holder) -> i32 {
self.n
}
}
main :: () -> i32 {
h : Holder = .{ n = 7 };
print("tag={} dep={}\n", h.tag(), dep_box());
0
}

View File

@@ -0,0 +1,11 @@
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
// main's same-name `Box { m }`. The parameterised-impl protocol type-ARG `*Box`
// registers SOURCE-AWARE against main's `Box` (own-wins), never the global
// last-wins author.
Box :: struct { a: i32; }
dep_box :: () -> i32 {
b : Box = ---;
b.a = 9;
return b.a;
}

View File

@@ -0,0 +1,12 @@
// issue 0131 regression: a protocol method call must arity-check like a
// plain call. `Allocator.dealloc_bytes` declares (ptr); calling it with
// an extra argument used to compile and silently drop the extra.
#import "modules/std.sx";
main :: () -> i32 {
gpa := GPA.init();
a : Allocator = xx gpa;
p := a.alloc_bytes(64);
a.dealloc_bytes(p, 12345);
return 0;
}

View File

@@ -0,0 +1 @@
0

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
lerp(0, 10, 0.5) = 5.000000
lerp(0, 10, 0.25) = 2.500000

View File

@@ -0,0 +1,4 @@
inside add: 42
inside add: 99
items[0] = 42 (expected 42)
items[1] = 99 (expected 99)

View File

@@ -0,0 +1,7 @@
=== Created in main ===
first: 42 (expected 42)
second: 42 (expected 42)
=== Created in add() ===
first: 99 (expected 99)
second: 99 (expected 99)
=== OK ===

View File

@@ -0,0 +1,8 @@
=== Direct 1 ===
r1 = 42
=== Direct 2 ===
r2 = 42
=== From function ===
dispatch_fn: about to dispatch
dispatch_fn: result = 42
=== OK ===

View File

@@ -0,0 +1,3 @@
StackA: draw=42
StackB: draw=25
OK

View File

@@ -0,0 +1,3 @@
direct a=0 b=1
proto f = 1
proto f = 0

View File

@@ -0,0 +1 @@
got pointer: true

View File

@@ -0,0 +1,2 @@
g initially null
after assign: g.ping() = 42

View File

@@ -0,0 +1,3 @@
direct: null? false
protocol: null? false
alloc_count: 2

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: no visible xx conversion from 'i64' to 'Wrap' — impl exists in another module but is not imported
--> examples/protocols/./0410-protocols-impl-visibility-user.sx:7:17
|
7 | w : Wrap = xx 7;
| ^

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: duplicate xx conversion from 'i64' to 'Wrap': impls in examples/protocols/./0411-protocols-impl-duplicate-impl-a.sx and examples/protocols/./0411-protocols-impl-duplicate-impl-b.sx
--> examples/protocols/0411-protocols-impl-duplicate.sx:23:17
|
23 | w : Wrap = xx 7;
| ^

View File

@@ -0,0 +1,9 @@
error: duplicate impl 'Into' for source 'i64' in examples/protocols/0412-protocols-impl-duplicate-same-file.sx
--> examples/protocols/0412-protocols-impl-duplicate-same-file.sx:15:1
|
15 | impl Into(MyA) for i64 {
| ^^^^^^^^^^^^^^^^^^^^^^^^
16 | convert :: (self: i64) -> MyA { .{ v = self * 2 } }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17 | }
| ^

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
a.get=42
b.get=hi

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,27 @@
=== Protocols ===
P1.1: 3
P1.2: 30
P2.1: 42
P2.2: 150
P2.3: 5 10
P3.1: 5
P3.2: 12
hi hi
P4.1: 2
yo yo
P4.2: 2
P4.3: 6 2
P5.1: true false
P5.2: 10 20
P5.5: true false
P5.3: true false
P6.1: true false
P6.2: true false
P6.3: true false
P6.4: 40
P6.5: 20
P7.1: 30
P7.2: 10 300
P2.6: 5 10
P2.7: 15
P3.3: 102

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
=== Auto Type Erasure ===
AE1: 12
AE2: 8
AE3: 102
AE4: 51
AE5: 15

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