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:
30
examples/protocols/0400-protocols-impl-for-builtin.sx
Normal file
30
examples/protocols/0400-protocols-impl-for-builtin.sx
Normal 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";
|
||||
@@ -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)
|
||||
}
|
||||
42
examples/protocols/0402-protocols-protocol-list-from-fn.sx
Normal file
42
examples/protocols/0402-protocols-protocol-list-from-fn.sx
Normal 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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
34
examples/protocols/0408-protocols-optional-protocol.sx
Normal file
34
examples/protocols/0408-protocols-optional-protocol.sx
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
10
examples/protocols/0410-protocols-impl-visibility-impl.sx
Normal file
10
examples/protocols/0410-protocols-impl-visibility-impl.sx
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Shared type for 179-impl-visibility — Wrap struct.
|
||||
Wrap :: struct { v: i64 = 0; }
|
||||
10
examples/protocols/0410-protocols-impl-visibility-user.sx
Normal file
10
examples/protocols/0410-protocols-impl-visibility-user.sx
Normal 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
|
||||
}
|
||||
21
examples/protocols/0410-protocols-impl-visibility.sx
Normal file
21
examples/protocols/0410-protocols-impl-visibility.sx
Normal 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() }
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Shared type for 180-impl-duplicate.
|
||||
Wrap :: struct { v: i64 = 0; }
|
||||
26
examples/protocols/0411-protocols-impl-duplicate.sx
Normal file
26
examples/protocols/0411-protocols-impl-duplicate.sx
Normal 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
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
617
examples/protocols/0415-protocols-protocols.sx
Normal file
617
examples/protocols/0415-protocols-protocols.sx
Normal 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);
|
||||
}
|
||||
}
|
||||
92
examples/protocols/0416-protocols-auto-type-erasure.sx
Normal file
92
examples/protocols/0416-protocols-auto-type-erasure.sx
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
47
examples/protocols/0418-protocols-explicit-receiver.sx
Normal file
47
examples/protocols/0418-protocols-explicit-receiver.sx
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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; }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
12
examples/protocols/1634-protocol-call-arity.sx
Normal file
12
examples/protocols/1634-protocol-call-arity.sx
Normal 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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
16255
examples/protocols/expected/0400-protocols-impl-for-builtin.ir
Normal file
16255
examples/protocols/expected/0400-protocols-impl-for-builtin.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lerp(0, 10, 0.5) = 5.000000
|
||||
lerp(0, 10, 0.25) = 2.500000
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
inside add: 42
|
||||
inside add: 99
|
||||
items[0] = 42 (expected 42)
|
||||
items[1] = 99 (expected 99)
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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 ===
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
=== Direct 1 ===
|
||||
r1 = 42
|
||||
=== Direct 2 ===
|
||||
r2 = 42
|
||||
=== From function ===
|
||||
dispatch_fn: about to dispatch
|
||||
dispatch_fn: result = 42
|
||||
=== OK ===
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
StackA: draw=42
|
||||
StackB: draw=25
|
||||
OK
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
direct a=0 b=1
|
||||
proto f = 1
|
||||
proto f = 0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
got pointer: true
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ok
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
g initially null
|
||||
after assign: g.ping() = 42
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
direct: null? false
|
||||
protocol: null? false
|
||||
alloc_count: 2
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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;
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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;
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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 | }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
a.get=42
|
||||
b.get=hi
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
99
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
27
examples/protocols/expected/0415-protocols-protocols.stdout
Normal file
27
examples/protocols/expected/0415-protocols-protocols.stdout
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
18933
examples/protocols/expected/0416-protocols-auto-type-erasure.ir
Normal file
18933
examples/protocols/expected/0416-protocols-auto-type-erasure.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
=== Auto Type Erasure ===
|
||||
AE1: 12
|
||||
AE2: 8
|
||||
AE3: 102
|
||||
AE4: 51
|
||||
AE5: 15
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user