protocols
This commit is contained in:
380
examples/36-protocols.sx
Normal file
380
examples/36-protocols.sx
Normal file
@@ -0,0 +1,380 @@
|
||||
// Dedicated protocol test suite
|
||||
// Tests: declaration, impl, inline/vtable dispatch, default methods,
|
||||
// Self type, generic constraints, generic struct impls, xx on literals
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Point :: struct { x, y: s32; }
|
||||
|
||||
// --- P1: vtable protocol (default layout) ---
|
||||
|
||||
Counter :: protocol {
|
||||
inc :: ();
|
||||
get :: () -> s32;
|
||||
}
|
||||
|
||||
SimpleCounter :: struct { val: s32; }
|
||||
|
||||
impl Counter for SimpleCounter {
|
||||
inc :: (self: *SimpleCounter) { self.val += 1; }
|
||||
get :: (self: *SimpleCounter) -> s32 { self.val; }
|
||||
}
|
||||
|
||||
// --- P2: #inline protocol ---
|
||||
|
||||
Adder :: protocol #inline {
|
||||
add :: (n: s32);
|
||||
value :: () -> s32;
|
||||
}
|
||||
|
||||
Accumulator :: struct { total: s32; }
|
||||
|
||||
impl Adder for Accumulator {
|
||||
add :: (self: *Accumulator, n: s32) { self.total += n; }
|
||||
value :: (self: *Accumulator) -> s32 { self.total; }
|
||||
}
|
||||
|
||||
Doubler :: struct { val: s32; }
|
||||
|
||||
impl Adder for Doubler {
|
||||
add :: (self: *Doubler, n: s32) { self.val = self.val + n + n; }
|
||||
value :: (self: *Doubler) -> s32 { self.val; }
|
||||
}
|
||||
|
||||
// --- P3: Summable (for generic constraint tests) ---
|
||||
|
||||
Summable :: protocol {
|
||||
sum :: () -> s32;
|
||||
}
|
||||
|
||||
impl Summable for Point {
|
||||
sum :: (self: *Point) -> s32 { self.x + self.y; }
|
||||
}
|
||||
|
||||
// --- P4: default methods ---
|
||||
|
||||
Repeater :: protocol {
|
||||
say :: (msg: string);
|
||||
say_twice :: (msg: string) {
|
||||
self.say(msg);
|
||||
self.say(msg);
|
||||
}
|
||||
}
|
||||
|
||||
Printer :: struct { count: s32; }
|
||||
|
||||
impl Repeater for Printer {
|
||||
say :: (self: *Printer, msg: string) {
|
||||
self.count += 1;
|
||||
out(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Chained default→default calls
|
||||
Chained :: protocol {
|
||||
base :: (msg: string) -> s32;
|
||||
wrap :: (msg: string) -> s32 {
|
||||
self.base(msg) + 1;
|
||||
}
|
||||
double_wrap :: (msg: string) -> s32 {
|
||||
self.wrap(msg) + self.wrap(msg);
|
||||
}
|
||||
}
|
||||
|
||||
ChainImpl :: struct { val: s32; }
|
||||
impl Chained for ChainImpl {
|
||||
base :: (self: *ChainImpl, msg: string) -> s32 {
|
||||
self.val += 1;
|
||||
msg.len;
|
||||
}
|
||||
}
|
||||
|
||||
// --- P5: Self type ---
|
||||
|
||||
Eq :: protocol {
|
||||
eq :: (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;
|
||||
}
|
||||
|
||||
impl Cloneable for Point {
|
||||
clone :: (self: *Point) -> Point {
|
||||
Point.{ x = self.x, y = self.y };
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for s64 {
|
||||
eq :: (self: *s64, other: s64) -> bool {
|
||||
self.* == other;
|
||||
}
|
||||
}
|
||||
|
||||
// --- P6: generic constraints ---
|
||||
|
||||
are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
|
||||
a.eq(b);
|
||||
}
|
||||
|
||||
Hashable :: protocol {
|
||||
hash :: () -> s64;
|
||||
}
|
||||
|
||||
impl Hashable for Point {
|
||||
hash :: (self: *Point) -> s64 {
|
||||
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);
|
||||
}
|
||||
|
||||
sum_of_inline :: (a: $T/Summable, b: T) -> s32 {
|
||||
a.sum() + b.sum();
|
||||
}
|
||||
|
||||
// --- P7: generic struct impls ---
|
||||
|
||||
Pair :: struct ($T: Type) {
|
||||
a: T;
|
||||
b: T;
|
||||
}
|
||||
|
||||
impl Summable for Pair($T) {
|
||||
sum :: (self: *Pair(T)) -> s32 {
|
||||
xx self.a + xx self.b;
|
||||
}
|
||||
}
|
||||
|
||||
SumBox :: struct ($T: Type/Summable) {
|
||||
val: T;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
main :: () {
|
||||
|
||||
// === P1: static dispatch on concrete types ===
|
||||
|
||||
// P1.1: basic protocol + impl
|
||||
{
|
||||
sc := SimpleCounter.{ val = 0 };
|
||||
sc.inc();
|
||||
sc.inc();
|
||||
sc.inc();
|
||||
print("P1.1: {}\n", sc.get());
|
||||
}
|
||||
|
||||
// P1.2: retroactive conformance (impl for existing type)
|
||||
{
|
||||
p := Point.{ x = 10, y = 20 };
|
||||
print("P1.2: {}\n", p.sum());
|
||||
}
|
||||
|
||||
// === P2: #inline protocol — dynamic dispatch ===
|
||||
|
||||
// P2.1: xx conversion + method 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 inline protocol to function
|
||||
{
|
||||
use_adder :: (a: Adder, n: s32) -> s32 {
|
||||
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());
|
||||
}
|
||||
|
||||
// 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) -> s32 { a.add(10); a.value(); }
|
||||
result := use_adder(xx Accumulator.{ total = 5 });
|
||||
print("P2.7: {}\n", result);
|
||||
}
|
||||
|
||||
// === P3: vtable protocol (default, no #inline) ===
|
||||
|
||||
// P3.1: xx + vtable dispatch
|
||||
{
|
||||
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) -> s32 {
|
||||
c.inc();
|
||||
c.inc();
|
||||
c.get();
|
||||
}
|
||||
sc := SimpleCounter.{ val = 10 };
|
||||
result := use_counter(xx @sc);
|
||||
print("P3.2: {}\n", result);
|
||||
}
|
||||
|
||||
// P3.3: xx on inline struct literal with vtable protocol
|
||||
{
|
||||
use_counter :: (c: Counter) -> s32 { c.inc(); c.inc(); c.get(); }
|
||||
result := use_counter(xx SimpleCounter.{ val = 100 });
|
||||
print("P3.3: {}\n", result);
|
||||
}
|
||||
|
||||
// === P4: default methods ===
|
||||
|
||||
// P4.1: default method via 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;
|
||||
result := ch.double_wrap("hi");
|
||||
print("P4.3: {} {}\n", result, ci.val);
|
||||
}
|
||||
|
||||
// === P5: Self type ===
|
||||
|
||||
// P5.1: Self in parameter position (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.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));
|
||||
}
|
||||
|
||||
// P5.5: impl for primitive type
|
||||
{
|
||||
x := 42;
|
||||
y := 42;
|
||||
z := 99;
|
||||
print("P5.5: {} {}\n", x.eq(y), x.eq(z));
|
||||
}
|
||||
|
||||
// === P6: generic constraints ===
|
||||
|
||||
// P6.1: single constraint
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
box := SumBox(Point).{ val = Point.{ x = 5, y = 15 } };
|
||||
print("P6.5: {}\n", box.val.sum());
|
||||
}
|
||||
|
||||
// === P7: generic struct impls ===
|
||||
|
||||
// P7.1: impl for generic struct
|
||||
{
|
||||
p := Pair(s32).{ a = 10, b = 20 };
|
||||
print("P7.1: {}\n", p.sum());
|
||||
}
|
||||
|
||||
// P7.2: different type args
|
||||
{
|
||||
p1 := Pair(s32).{ a = 3, b = 7 };
|
||||
p2 := Pair(s64).{ a = 100, b = 200 };
|
||||
print("P7.2: {} {}\n", p1.sum(), p2.sum());
|
||||
}
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
@@ -2834,5 +2834,19 @@ END;
|
||||
print("P2.6: {} {}\n", acc.total, dbl.val);
|
||||
}
|
||||
|
||||
// P2.7: xx on inline struct literal (no intermediate variable)
|
||||
{
|
||||
use_adder :: (a: Adder) -> s32 { 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) -> s32 { c.inc(); c.inc(); c.get(); }
|
||||
result := use_counter(xx SimpleCounter.{ val = 100 });
|
||||
print("P3.3: {}\n", result);
|
||||
}
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
|
||||
@@ -6078,8 +6078,16 @@ pub const CodeGen = struct {
|
||||
// xx prefix: unwrap and convert freely (explicit cast)
|
||||
if (node.data == .unary_op and node.data.unary_op.op == .xx) {
|
||||
const inner = node.data.unary_op.operand;
|
||||
const val = try self.genExpr(inner);
|
||||
var val = try self.genExpr(inner);
|
||||
const src_ty = self.inferType(inner);
|
||||
// genExpr on struct literals returns an alloca (ptr), not a loaded value.
|
||||
// Load it so convertValue/buildProtocolValue sees the actual struct value.
|
||||
if (inner.data == .struct_literal and src_ty.isStruct()) {
|
||||
const sname = self.resolveAlias(src_ty.struct_type);
|
||||
if (self.lookupStructInfo(sname)) |si| {
|
||||
val = c.LLVMBuildLoad2(self.builder, si.llvm_type.?, val, "xx_struct_load");
|
||||
}
|
||||
}
|
||||
return self.convertValue(val, src_ty, target_ty);
|
||||
}
|
||||
|
||||
|
||||
1
tests/expected/36-protocols.exit
Normal file
1
tests/expected/36-protocols.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
27
tests/expected/36-protocols.txt
Normal file
27
tests/expected/36-protocols.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
P1.1: 3
|
||||
P1.2: 30
|
||||
P2.1: 42
|
||||
P2.2: 150
|
||||
P2.3: 5 10
|
||||
P2.6: 5 10
|
||||
P2.7: 15
|
||||
P3.1: 5
|
||||
P3.2: 12
|
||||
P3.3: 102
|
||||
hi hi
|
||||
P4.1: 2
|
||||
yo yo
|
||||
P4.2: 2
|
||||
P4.3: 6 2
|
||||
P5.1: true false
|
||||
P5.2: 10 20
|
||||
P5.3: true false
|
||||
P5.5: 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
|
||||
=== DONE ===
|
||||
Reference in New Issue
Block a user