diff --git a/examples/36-protocols.sx b/examples/36-protocols.sx new file mode 100644 index 0000000..e274214 --- /dev/null +++ b/examples/36-protocols.sx @@ -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"); +} diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index 3a84045..be68d48 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -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"); } diff --git a/src/codegen.zig b/src/codegen.zig index aca9d17..002aa53 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -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); } diff --git a/tests/expected/36-protocols.exit b/tests/expected/36-protocols.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/36-protocols.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/36-protocols.txt b/tests/expected/36-protocols.txt new file mode 100644 index 0000000..15e9c38 --- /dev/null +++ b/tests/expected/36-protocols.txt @@ -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 ===