From 6b0ebdd92be37acfbee4d1b0fdb0555220afd1b4 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 21 Jun 2026 11:02:16 +0300 Subject: [PATCH] lang: require explicit receiver in protocol method declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Protocol method declarations now declare their receiver explicitly as the first parameter — 'self: *Self' (or 'self: Self') — matching the impl method signature, instead of the old implicit-receiver form where the listed params were only the extra args. That asymmetry repeatedly caused confusion over whether the first param was the receiver or an argument. The parser validates the first param is 'self' typed Self/*Self, then strips it, so all downstream lowering and the dispatch ABI are unchanged (impl blocks and call sites are unaffected). A protocol method missing the receiver is now a parse error. Migrated all 129 protocol method signatures across library + examples (+ one inline-sx test in sema.zig) to the explicit form. Updated specs.md + readme.md. New: examples/0418-protocols-explicit-receiver.sx (feature), examples/1190-diagnostics-protocol-missing-receiver.sx (negative/diagnostic). --- examples/0022-basic-for-range.sx | 2 +- examples/0026-basic-operators.sx | 6 +-- examples/0129-types-tuple-operators.sx | 6 +-- examples/0131-types-init-blocks.sx | 6 +-- .../0158-types-reserved-name-member-exempt.sx | 2 +- ...04-generics-generic-protocol-constraint.sx | 2 +- ...303-closures-closure-returning-protocol.sx | 2 +- examples/0400-protocols-impl-for-builtin.sx | 2 +- ...01-protocols-protocol-in-wrapper-struct.sx | 2 +- .../0402-protocols-protocol-list-from-fn.sx | 2 +- ...-protocols-protocol-dispatch-via-fn-arg.sx | 2 +- ...-protocols-dot-shorthand-protocol-field.sx | 8 ++-- ...rotocols-enum-through-protocol-dispatch.sx | 2 +- ...-protocols-protocol-real-pointer-return.sx | 2 +- examples/0408-protocols-optional-protocol.sx | 2 +- ...-protocols-parameterized-protocol-value.sx | 2 +- ...protocols-generic-struct-protocol-erase.sx | 2 +- examples/0415-protocols-protocols.sx | 26 +++++----- examples/0416-protocols-auto-type-erasure.sx | 10 ++-- ...rotocols-protocol-return-name-collision.sx | 2 +- examples/0418-protocols-explicit-receiver.sx | 47 +++++++++++++++++++ .../0517-packs-pack-reflection-intrinsics.sx | 2 +- examples/0528-packs-protocol-pack-methods.sx | 2 +- .../0529-packs-protocol-pack-parameterized.sx | 2 +- examples/0530-packs-pack-interface-only.sx | 2 +- examples/0531-packs-pack-value-projection.sx | 2 +- examples/0532-packs-pack-spread-call.sx | 2 +- examples/0533-packs-pack-tuple-materialize.sx | 2 +- examples/0534-packs-pack-type-projection.sx | 2 +- .../0535-packs-slice-of-protocol-variadic.sx | 2 +- examples/0536-packs-pack-as-value.sx | 2 +- examples/0537-packs-pack-xx-to-slice.sx | 2 +- examples/0539-packs-combined-pack-field.sx | 2 +- examples/0540-packs-pack-type-arg-spread.sx | 2 +- examples/0541-packs-pack-to-protocol-tuple.sx | 2 +- .../0542-packs-mapper-projection-spread.sx | 2 +- examples/0543-packs-canonical-map.sx | 2 +- examples/0545-packs-inline-for-element.sx | 2 +- .../0547-packs-xx-pack-index-to-protocol.sx | 2 +- .../0548-packs-xx-pack-index-two-elements.sx | 2 +- ...820-protocols-same-name-method-own-wins.sx | 2 +- ...21-protocols-same-name-method-ambiguous.sx | 2 +- ...ocols-same-name-method-wrapped-own-wins.sx | 20 ++++---- ...cols-same-name-method-wrapped-ambiguous.sx | 2 +- ...otocols-param-impl-arg-wrapped-own-wins.sx | 2 +- ...-param-impl-mixed-pack-source-ambiguous.sx | 2 +- examples/0902-optionals-optional-all-null.sx | 2 +- examples/0903-optionals-optional-roundtrip.sx | 2 +- ...-diagnostics-inline-for-pack-rejections.sx | 2 +- ...1165-diagnostics-generic-return-unbound.sx | 2 +- ...0-diagnostics-protocol-missing-receiver.sx | 12 +++++ .../0418-protocols-explicit-receiver.exit | 1 + .../0418-protocols-explicit-receiver.stderr | 1 + .../0418-protocols-explicit-receiver.stdout | 3 ++ ...diagnostics-protocol-missing-receiver.exit | 1 + ...agnostics-protocol-missing-receiver.stderr | 5 ++ ...agnostics-protocol-missing-receiver.stdout | 1 + library/modules/gpu/api.sx | 42 ++++++++--------- library/modules/platform/api.sx | 22 ++++----- library/modules/std/core.sx | 18 +++---- library/modules/ui/animation.sx | 2 +- library/modules/ui/view.sx | 8 ++-- readme.md | 12 +++-- specs.md | 16 +++---- src/parser.zig | 36 ++++++++++++-- src/sema.zig | 2 +- 66 files changed, 249 insertions(+), 146 deletions(-) create mode 100644 examples/0418-protocols-explicit-receiver.sx create mode 100644 examples/1190-diagnostics-protocol-missing-receiver.sx create mode 100644 examples/expected/0418-protocols-explicit-receiver.exit create mode 100644 examples/expected/0418-protocols-explicit-receiver.stderr create mode 100644 examples/expected/0418-protocols-explicit-receiver.stdout create mode 100644 examples/expected/1190-diagnostics-protocol-missing-receiver.exit create mode 100644 examples/expected/1190-diagnostics-protocol-missing-receiver.stderr create mode 100644 examples/expected/1190-diagnostics-protocol-missing-receiver.stdout diff --git a/examples/0022-basic-for-range.sx b/examples/0022-basic-for-range.sx index b9e64830..555300cf 100644 --- a/examples/0022-basic-for-range.sx +++ b/examples/0022-basic-for-range.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; Show :: protocol { - show :: () -> string; + show :: (self: *Self) -> string; } A :: struct { x: i64; } B :: struct { s: string; } diff --git a/examples/0026-basic-operators.sx b/examples/0026-basic-operators.sx index 9fb4842a..0f123f8b 100644 --- a/examples/0026-basic-operators.sx +++ b/examples/0026-basic-operators.sx @@ -10,11 +10,11 @@ mul :: (a: i32, b: i32) -> i32 { a * b } // P4 edge: Chained default→default calls Chained :: protocol { - base :: (msg: string) -> i32; - wrap :: (msg: string) -> i32 { + base :: (self: *Self, msg: string) -> i32; + wrap :: (self: *Self, msg: string) -> i32 { self.base(msg) + 1 } - double_wrap :: (msg: string) -> i32 { + double_wrap :: (self: *Self, msg: string) -> i32 { self.wrap(msg) + self.wrap(msg) } } diff --git a/examples/0129-types-tuple-operators.sx b/examples/0129-types-tuple-operators.sx index 348c9d64..739a058f 100644 --- a/examples/0129-types-tuple-operators.sx +++ b/examples/0129-types-tuple-operators.sx @@ -28,11 +28,11 @@ apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 { // P4 edge: Chained default→default calls Chained :: protocol { - base :: (msg: string) -> i32; - wrap :: (msg: string) -> i32 { + base :: (self: *Self, msg: string) -> i32; + wrap :: (self: *Self, msg: string) -> i32 { self.base(msg) + 1 } - double_wrap :: (msg: string) -> i32 { + double_wrap :: (self: *Self, msg: string) -> i32 { self.wrap(msg) + self.wrap(msg) } } diff --git a/examples/0131-types-init-blocks.sx b/examples/0131-types-init-blocks.sx index a0f2bce3..68e80d98 100644 --- a/examples/0131-types-init-blocks.sx +++ b/examples/0131-types-init-blocks.sx @@ -9,12 +9,12 @@ Point :: struct { x, y: i32; } add :: (a: i32, b: i32) -> i32 { a + b } Counter :: protocol { - inc :: (); - get :: () -> i32; + inc :: (self: *Self); + get :: (self: *Self) -> i32; } Summable :: protocol { - sum :: () -> i32; + sum :: (self: *Self) -> i32; } SimpleCounter :: struct { val: i32; } diff --git a/examples/0158-types-reserved-name-member-exempt.sx b/examples/0158-types-reserved-name-member-exempt.sx index c085d38a..83bcf3c5 100644 --- a/examples/0158-types-reserved-name-member-exempt.sx +++ b/examples/0158-types-reserved-name-member-exempt.sx @@ -28,7 +28,7 @@ Tag :: union { // Protocol method SIGNATURE spelled with a reserved type name — bare is legal. Speaker :: protocol { - i2 :: () -> i64; + i2 :: (self: *Self) -> i64; } Dog :: struct { n: i64; } diff --git a/examples/0204-generics-generic-protocol-constraint.sx b/examples/0204-generics-generic-protocol-constraint.sx index eaee7bad..63c01644 100644 --- a/examples/0204-generics-generic-protocol-constraint.sx +++ b/examples/0204-generics-generic-protocol-constraint.sx @@ -5,7 +5,7 @@ #import "modules/math"; Lerpable :: protocol #inline { - lerp :: (b: Self, t: f32) -> Self; + lerp :: (self: *Self, b: Self, t: f32) -> Self; } Size :: struct { diff --git a/examples/0303-closures-closure-returning-protocol.sx b/examples/0303-closures-closure-returning-protocol.sx index e39b2819..2d096208 100644 --- a/examples/0303-closures-closure-returning-protocol.sx +++ b/examples/0303-closures-closure-returning-protocol.sx @@ -4,7 +4,7 @@ #import "modules/std.sx"; MyProtocol :: protocol { - get_value :: () -> i64; + get_value :: (self: *Self) -> i64; } MyImpl :: struct { value: i64; } diff --git a/examples/0400-protocols-impl-for-builtin.sx b/examples/0400-protocols-impl-for-builtin.sx index fe7899f2..5317320f 100644 --- a/examples/0400-protocols-impl-for-builtin.sx +++ b/examples/0400-protocols-impl-for-builtin.sx @@ -3,7 +3,7 @@ // `#inline` erasure. Lerpable :: protocol #inline { - lerp :: (b: Self, t: f32) -> Self; + lerp :: (self: *Self, b: Self, t: f32) -> Self; } impl Lerpable for f32 { diff --git a/examples/0401-protocols-protocol-in-wrapper-struct.sx b/examples/0401-protocols-protocol-in-wrapper-struct.sx index fb0c5987..7fe31cce 100644 --- a/examples/0401-protocols-protocol-in-wrapper-struct.sx +++ b/examples/0401-protocols-protocol-in-wrapper-struct.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; Sizable :: protocol { - size :: () -> i64; + size :: (self: *Self) -> i64; } Widget :: struct { value: i64; } diff --git a/examples/0402-protocols-protocol-list-from-fn.sx b/examples/0402-protocols-protocol-list-from-fn.sx index a6f916c1..ebd34028 100644 --- a/examples/0402-protocols-protocol-list-from-fn.sx +++ b/examples/0402-protocols-protocol-list-from-fn.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; Sizable :: protocol { - size :: () -> i64; + size :: (self: *Self) -> i64; } Leaf :: struct { value: i64; } diff --git a/examples/0403-protocols-protocol-dispatch-via-fn-arg.sx b/examples/0403-protocols-protocol-dispatch-via-fn-arg.sx index 3816e464..64c89ace 100644 --- a/examples/0403-protocols-protocol-dispatch-via-fn-arg.sx +++ b/examples/0403-protocols-protocol-dispatch-via-fn-arg.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; Sizable :: protocol { - size :: () -> i64; + size :: (self: *Self) -> i64; } Leaf :: struct { value: i64; } diff --git a/examples/0404-protocols-dot-shorthand-protocol-field.sx b/examples/0404-protocols-dot-shorthand-protocol-field.sx index 2832aff1..c6d38b7c 100644 --- a/examples/0404-protocols-dot-shorthand-protocol-field.sx +++ b/examples/0404-protocols-dot-shorthand-protocol-field.sx @@ -5,10 +5,10 @@ #import "modules/std.sx"; Drawable :: protocol { - draw :: () -> i32; - name :: () -> string; - layout :: (x: i32) -> i32; - handle :: (event: i32) -> bool; + draw :: (self: *Self) -> i32; + name :: (self: *Self) -> string; + layout :: (self: *Self, x: i32) -> i32; + handle :: (self: *Self, event: i32) -> bool; } Circle :: struct { radius: i32; } diff --git a/examples/0405-protocols-enum-through-protocol-dispatch.sx b/examples/0405-protocols-enum-through-protocol-dispatch.sx index 5aef554a..95cd0577 100644 --- a/examples/0405-protocols-enum-through-protocol-dispatch.sx +++ b/examples/0405-protocols-enum-through-protocol-dispatch.sx @@ -6,7 +6,7 @@ Fmt :: enum { a; b; } Proto :: protocol { - take_fmt :: (f: Fmt); + take_fmt :: (self: *Self, f: Fmt); } Impl :: struct {} diff --git a/examples/0406-protocols-protocol-real-pointer-return.sx b/examples/0406-protocols-protocol-real-pointer-return.sx index 5e782137..99b6c674 100644 --- a/examples/0406-protocols-protocol-real-pointer-return.sx +++ b/examples/0406-protocols-protocol-real-pointer-return.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; Proto :: protocol { - get :: () -> *u8; + get :: (self: *Self) -> *u8; } Impl :: struct { diff --git a/examples/0408-protocols-optional-protocol.sx b/examples/0408-protocols-optional-protocol.sx index 2cb845a5..32f507ba 100644 --- a/examples/0408-protocols-optional-protocol.sx +++ b/examples/0408-protocols-optional-protocol.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; GPU :: protocol { - ping :: () -> i64; + ping :: (self: *Self) -> i64; } Impl :: struct {} diff --git a/examples/0413-protocols-parameterized-protocol-value.sx b/examples/0413-protocols-parameterized-protocol-value.sx index 76836368..3bf0fcbe 100644 --- a/examples/0413-protocols-parameterized-protocol-value.sx +++ b/examples/0413-protocols-parameterized-protocol-value.sx @@ -6,7 +6,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +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; } diff --git a/examples/0414-protocols-generic-struct-protocol-erase.sx b/examples/0414-protocols-generic-struct-protocol-erase.sx index a4a887a2..4de4039f 100644 --- a/examples/0414-protocols-generic-struct-protocol-erase.sx +++ b/examples/0414-protocols-generic-struct-protocol-erase.sx @@ -9,7 +9,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0415-protocols-protocols.sx b/examples/0415-protocols-protocols.sx index 8a0bb986..bf88a050 100644 --- a/examples/0415-protocols-protocols.sx +++ b/examples/0415-protocols-protocols.sx @@ -203,12 +203,12 @@ c_abs :: (n: i32) -> i32 extern libc "abs"; // --- Protocol declarations (Phase 1: static dispatch only) --- Counter :: protocol { - inc :: (); - get :: () -> i32; + inc :: (self: *Self); + get :: (self: *Self) -> i32; } Summable :: protocol { - sum :: () -> i32; + sum :: (self: *Self) -> i32; } SimpleCounter :: struct { val: i32; } @@ -226,8 +226,8 @@ impl Summable for Point { // Phase 2: #inline protocol for dynamic dispatch Adder :: protocol #inline { - add :: (n: i32); - value :: () -> i32; + add :: (self: *Self, n: i32); + value :: (self: *Self) -> i32; } Accumulator :: struct { @@ -250,8 +250,8 @@ impl Adder for Doubler { // Phase 4: default methods Repeater :: protocol { - say :: (msg: string); - say_twice :: (msg: string) { + say :: (self: *Self, msg: string); + say_twice :: (self: *Self, msg: string) { self.say(msg); self.say(msg); } @@ -270,11 +270,11 @@ impl Repeater for Printer { // P4 edge: Chained default→default calls Chained :: protocol { - base :: (msg: string) -> i32; - wrap :: (msg: string) -> i32 { + base :: (self: *Self, msg: string) -> i32; + wrap :: (self: *Self, msg: string) -> i32 { self.base(msg) + 1 } - double_wrap :: (msg: string) -> i32 { + double_wrap :: (self: *Self, msg: string) -> i32 { self.wrap(msg) + self.wrap(msg) } } @@ -291,7 +291,7 @@ impl Chained for ChainImpl { // Phase 5: Self type Eq :: protocol { - eq :: (other: Self) -> bool; + eq :: (self: *Self, other: Self) -> bool; } impl Eq for Point { @@ -301,7 +301,7 @@ impl Eq for Point { } Cloneable :: protocol { - clone :: () -> Self; + clone :: (self: *Self) -> Self; } impl Cloneable for Point { @@ -324,7 +324,7 @@ are_equal :: ($T: Type/Eq, a: T, b: T) -> bool { } Hashable :: protocol { - hash :: () -> i64; + hash :: (self: *Self) -> i64; } impl Hashable for Point { diff --git a/examples/0416-protocols-auto-type-erasure.sx b/examples/0416-protocols-auto-type-erasure.sx index f26d8e09..2cc9c97b 100644 --- a/examples/0416-protocols-auto-type-erasure.sx +++ b/examples/0416-protocols-auto-type-erasure.sx @@ -9,12 +9,12 @@ Point :: struct { x, y: i32; } add :: (a: i32, b: i32) -> i32 { a + b } Counter :: protocol { - inc :: (); - get :: () -> i32; + inc :: (self: *Self); + get :: (self: *Self) -> i32; } Summable :: protocol { - sum :: () -> i32; + sum :: (self: *Self) -> i32; } SimpleCounter :: struct { val: i32; } @@ -32,8 +32,8 @@ impl Summable for Point { // Phase 2: #inline protocol for dynamic dispatch Adder :: protocol #inline { - add :: (n: i32); - value :: () -> i32; + add :: (self: *Self, n: i32); + value :: (self: *Self) -> i32; } Accumulator :: struct { diff --git a/examples/0417-protocols-protocol-return-name-collision.sx b/examples/0417-protocols-protocol-return-name-collision.sx index ad99bea8..424dcd29 100644 --- a/examples/0417-protocols-protocol-return-name-collision.sx +++ b/examples/0417-protocols-protocol-return-name-collision.sx @@ -24,7 +24,7 @@ Keycode :: enum { unknown; escape; enter; } KeyData :: struct { key: Keycode; } Event :: enum { none; key_up: KeyData; } -Plat :: protocol { one_event :: () -> Event; } +Plat :: protocol { one_event :: (self: *Self) -> Event; } Impl :: struct { dummy: i64; } impl Plat for Impl { diff --git a/examples/0418-protocols-explicit-receiver.sx b/examples/0418-protocols-explicit-receiver.sx new file mode 100644 index 00000000..842f5862 --- /dev/null +++ b/examples/0418-protocols-explicit-receiver.sx @@ -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; +} diff --git a/examples/0517-packs-pack-reflection-intrinsics.sx b/examples/0517-packs-pack-reflection-intrinsics.sx index bbc55471..f05e304d 100644 --- a/examples/0517-packs-pack-reflection-intrinsics.sx +++ b/examples/0517-packs-pack-reflection-intrinsics.sx @@ -24,7 +24,7 @@ // User-defined parameterised protocol + an impl, so has_impl can // confirm parameterised matching works with a known-true case. Wrap :: protocol(Target: Type) { - wrap :: () -> Target; + wrap :: (self: *Self) -> Target; } impl Wrap(i64) for i32 { diff --git a/examples/0528-packs-protocol-pack-methods.sx b/examples/0528-packs-protocol-pack-methods.sx index b9667e3a..83ac59b5 100644 --- a/examples/0528-packs-protocol-pack-methods.sx +++ b/examples/0528-packs-protocol-pack-methods.sx @@ -9,7 +9,7 @@ #import "modules/std.sx"; Greeter :: protocol { - greet :: () -> i64; + greet :: (self: *Self) -> i64; } Dog :: struct { age: i64; } diff --git a/examples/0529-packs-protocol-pack-parameterized.sx b/examples/0529-packs-protocol-pack-parameterized.sx index d38eabfe..7482fd37 100644 --- a/examples/0529-packs-protocol-pack-parameterized.sx +++ b/examples/0529-packs-protocol-pack-parameterized.sx @@ -10,7 +10,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> T; + get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } diff --git a/examples/0530-packs-pack-interface-only.sx b/examples/0530-packs-pack-interface-only.sx index 7889c47e..d4722a47 100644 --- a/examples/0530-packs-pack-interface-only.sx +++ b/examples/0530-packs-pack-interface-only.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> T; + get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl Box(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0531-packs-pack-value-projection.sx b/examples/0531-packs-pack-value-projection.sx index 36bb057d..2dc592a6 100644 --- a/examples/0531-packs-pack-value-projection.sx +++ b/examples/0531-packs-pack-value-projection.sx @@ -8,7 +8,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> T; + get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } diff --git a/examples/0532-packs-pack-spread-call.sx b/examples/0532-packs-pack-spread-call.sx index 72944b9f..a3d62014 100644 --- a/examples/0532-packs-pack-spread-call.sx +++ b/examples/0532-packs-pack-spread-call.sx @@ -6,7 +6,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> i64; + get :: (self: *Self) -> i64; } IntCell :: struct { v: i64; } Dbl :: struct { n: i64; } diff --git a/examples/0533-packs-pack-tuple-materialize.sx b/examples/0533-packs-pack-tuple-materialize.sx index 7a9faf01..b2ec35e5 100644 --- a/examples/0533-packs-pack-tuple-materialize.sx +++ b/examples/0533-packs-pack-tuple-materialize.sx @@ -6,7 +6,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> T; + get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } StrCell :: struct { s: string; } diff --git a/examples/0534-packs-pack-type-projection.sx b/examples/0534-packs-pack-type-projection.sx index e09df906..60da85e0 100644 --- a/examples/0534-packs-pack-type-projection.sx +++ b/examples/0534-packs-pack-type-projection.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; Box :: protocol(T: Type) { - get :: () -> T; + get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } diff --git a/examples/0535-packs-slice-of-protocol-variadic.sx b/examples/0535-packs-slice-of-protocol-variadic.sx index 33be4615..ce7563e0 100644 --- a/examples/0535-packs-slice-of-protocol-variadic.sx +++ b/examples/0535-packs-slice-of-protocol-variadic.sx @@ -9,7 +9,7 @@ #import "modules/std.sx"; -Show :: protocol { show :: () -> string; } +Show :: protocol { show :: (self: *Self) -> string; } A :: struct { x: i64; } B :: struct { s: string; } impl Show for A { show :: (self: *A) -> string => "A"; } diff --git a/examples/0536-packs-pack-as-value.sx b/examples/0536-packs-pack-as-value.sx index 46ca0e4e..85c1aab9 100644 --- a/examples/0536-packs-pack-as-value.sx +++ b/examples/0536-packs-pack-as-value.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; -Show :: protocol { show :: () -> string; } +Show :: protocol { show :: (self: *Self) -> string; } A :: struct {} impl Show for A { show :: (self: *A) -> string => "A"; } diff --git a/examples/0537-packs-pack-xx-to-slice.sx b/examples/0537-packs-pack-xx-to-slice.sx index 9fed452c..86ef05f3 100644 --- a/examples/0537-packs-pack-xx-to-slice.sx +++ b/examples/0537-packs-pack-xx-to-slice.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; -Show :: protocol { show :: () -> string; } +Show :: protocol { show :: (self: *Self) -> string; } A :: struct {} B :: struct { s: string; } impl Show for A { show :: (self: *A) -> string => "A"; } diff --git a/examples/0539-packs-combined-pack-field.sx b/examples/0539-packs-combined-pack-field.sx index b8f85ea1..ad2925a2 100644 --- a/examples/0539-packs-combined-pack-field.sx +++ b/examples/0539-packs-combined-pack-field.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +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; } diff --git a/examples/0540-packs-pack-type-arg-spread.sx b/examples/0540-packs-pack-type-arg-spread.sx index 16339d3e..ce394182 100644 --- a/examples/0540-packs-pack-type-arg-spread.sx +++ b/examples/0540-packs-pack-type-arg-spread.sx @@ -6,7 +6,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0541-packs-pack-to-protocol-tuple.sx b/examples/0541-packs-pack-to-protocol-tuple.sx index 49765ecf..752d0dd6 100644 --- a/examples/0541-packs-pack-to-protocol-tuple.sx +++ b/examples/0541-packs-pack-to-protocol-tuple.sx @@ -5,7 +5,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +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; } diff --git a/examples/0542-packs-mapper-projection-spread.sx b/examples/0542-packs-mapper-projection-spread.sx index 1b2829e6..3e7703f9 100644 --- a/examples/0542-packs-mapper-projection-spread.sx +++ b/examples/0542-packs-mapper-projection-spread.sx @@ -4,7 +4,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0543-packs-canonical-map.sx b/examples/0543-packs-canonical-map.sx index 2b185748..5be21fc4 100644 --- a/examples/0543-packs-canonical-map.sx +++ b/examples/0543-packs-canonical-map.sx @@ -12,7 +12,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0545-packs-inline-for-element.sx b/examples/0545-packs-inline-for-element.sx index fdb5499a..1d1e220f 100644 --- a/examples/0545-packs-inline-for-element.sx +++ b/examples/0545-packs-inline-for-element.sx @@ -11,7 +11,7 @@ #import "modules/std.sx"; -Show :: protocol { show :: () -> string; } +Show :: protocol { show :: (self: *Self) -> string; } IntBox :: struct { v: i64; } StrBox :: struct { s: string; } impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } } diff --git a/examples/0547-packs-xx-pack-index-to-protocol.sx b/examples/0547-packs-xx-pack-index-to-protocol.sx index 92139d4c..b84a2976 100644 --- a/examples/0547-packs-xx-pack-index-to-protocol.sx +++ b/examples/0547-packs-xx-pack-index-to-protocol.sx @@ -11,7 +11,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } diff --git a/examples/0548-packs-xx-pack-index-two-elements.sx b/examples/0548-packs-xx-pack-index-two-elements.sx index de01b714..1d91d989 100644 --- a/examples/0548-packs-xx-pack-index-two-elements.sx +++ b/examples/0548-packs-xx-pack-index-two-elements.sx @@ -7,7 +7,7 @@ #import "modules/std.sx"; -VL :: protocol(T: Type) { get :: () -> T; } +VL :: protocol(T: Type) { get :: (self: *Self) -> T; } IntCell :: struct { v: i64; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } Doubler :: struct { n: i64; } diff --git a/examples/0820-protocols-same-name-method-own-wins.sx b/examples/0820-protocols-same-name-method-own-wins.sx index d2a8af22..323897f4 100644 --- a/examples/0820-protocols-same-name-method-own-wins.sx +++ b/examples/0820-protocols-same-name-method-own-wins.sx @@ -15,7 +15,7 @@ Box :: struct { m: i32; } Provider :: protocol { - get :: () -> Box; + get :: (self: *Self) -> Box; } Holder :: struct { val: i32 = 7; } diff --git a/examples/0821-protocols-same-name-method-ambiguous.sx b/examples/0821-protocols-same-name-method-ambiguous.sx index 2b5b5a2a..2bc20cc5 100644 --- a/examples/0821-protocols-same-name-method-ambiguous.sx +++ b/examples/0821-protocols-same-name-method-ambiguous.sx @@ -10,7 +10,7 @@ #import "0821-protocols-same-name-method-ambiguous/b.sx"; Provider :: protocol { - get :: () -> Box; + get :: (self: *Self) -> Box; } main :: () -> i32 { diff --git a/examples/0824-protocols-same-name-method-wrapped-own-wins.sx b/examples/0824-protocols-same-name-method-wrapped-own-wins.sx index a93390d0..66ddbe88 100644 --- a/examples/0824-protocols-same-name-method-wrapped-own-wins.sx +++ b/examples/0824-protocols-same-name-method-wrapped-own-wins.sx @@ -21,17 +21,17 @@ Holder :: struct { b: Box = ---; } Provider :: protocol { // discriminating wrapped/compound RETURNS - getp :: () -> *Box; - geto :: () -> ?Box; - gett :: () -> (Box, Box); - geta :: () -> [2]Box; + getp :: (self: *Self) -> *Box; + geto :: (self: *Self) -> ?Box; + gett :: (self: *Self) -> (Box, Box); + geta :: (self: *Self) -> [2]Box; // routing-only wrapped/compound PARAMS - sump :: (p: *Box) -> i32; - sumo :: (o: ?Box) -> i32; - sums :: (s: []Box) -> i32; - suma :: (a: [2]Box) -> i32; - sumt :: (t: (Box, Box)) -> i32; - sumn :: (n: *?[]Box) -> i32; + 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 { diff --git a/examples/0825-protocols-same-name-method-wrapped-ambiguous.sx b/examples/0825-protocols-same-name-method-wrapped-ambiguous.sx index f35ab3ea..daa09573 100644 --- a/examples/0825-protocols-same-name-method-wrapped-ambiguous.sx +++ b/examples/0825-protocols-same-name-method-wrapped-ambiguous.sx @@ -15,7 +15,7 @@ #import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx"; Provider :: protocol { - getp :: () -> *Box; + getp :: (self: *Self) -> *Box; } main :: () -> i32 { diff --git a/examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx b/examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx index 2bb9d088..29045917 100644 --- a/examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx +++ b/examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx @@ -15,7 +15,7 @@ Box :: struct { m: i32; } Holder :: struct { n: i32; } Tagged :: protocol(T: Type) { - tag :: () -> i32; + tag :: (self: *Self) -> i32; } impl Tagged(*Box) for Holder { diff --git a/examples/0829-packs-param-impl-mixed-pack-source-ambiguous.sx b/examples/0829-packs-param-impl-mixed-pack-source-ambiguous.sx index 0bfd0902..6e31687f 100644 --- a/examples/0829-packs-param-impl-mixed-pack-source-ambiguous.sx +++ b/examples/0829-packs-param-impl-mixed-pack-source-ambiguous.sx @@ -22,7 +22,7 @@ Block :: struct { tag: i32; } Sink :: protocol(T: Type) { - convert :: () -> T; + convert :: (self: *Self) -> T; } impl Sink(Block) for Closure(*Box, ..$args) -> $R { diff --git a/examples/0902-optionals-optional-all-null.sx b/examples/0902-optionals-optional-all-null.sx index 2b9cc9b5..83c76ccc 100644 --- a/examples/0902-optionals-optional-all-null.sx +++ b/examples/0902-optionals-optional-all-null.sx @@ -10,7 +10,7 @@ ProposedSize :: struct { } Sizable :: protocol { - size :: (proposal: ProposedSize) -> f32; + size :: (self: *Self, proposal: ProposedSize) -> f32; } Widget :: struct {} diff --git a/examples/0903-optionals-optional-roundtrip.sx b/examples/0903-optionals-optional-roundtrip.sx index 03e2fae2..07e92d9f 100644 --- a/examples/0903-optionals-optional-roundtrip.sx +++ b/examples/0903-optionals-optional-roundtrip.sx @@ -16,7 +16,7 @@ direct_size :: (proposal: ProposedSize) -> f32 { } Sizable :: protocol { - size :: (proposal: ProposedSize) -> f32; + size :: (self: *Self, proposal: ProposedSize) -> f32; } Widget :: struct {} diff --git a/examples/1164-diagnostics-inline-for-pack-rejections.sx b/examples/1164-diagnostics-inline-for-pack-rejections.sx index ca4a4529..362a6381 100644 --- a/examples/1164-diagnostics-inline-for-pack-rejections.sx +++ b/examples/1164-diagnostics-inline-for-pack-rejections.sx @@ -6,7 +6,7 @@ #import "modules/std.sx"; -Show :: protocol { show :: () -> string; } +Show :: protocol { show :: (self: *Self) -> string; } IntBox :: struct { v: i64; } impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } } diff --git a/examples/1165-diagnostics-generic-return-unbound.sx b/examples/1165-diagnostics-generic-return-unbound.sx index d9aef7c8..57632012 100644 --- a/examples/1165-diagnostics-generic-return-unbound.sx +++ b/examples/1165-diagnostics-generic-return-unbound.sx @@ -14,7 +14,7 @@ Foo :: struct { weird :: (self: *Foo) -> $T { 0 } } -Show2 :: protocol { show2 :: () -> string; } +Show2 :: protocol { show2 :: (self: *Self) -> string; } IntBox :: struct { v: i64; } impl Show2 for IntBox { show2 :: (self: *IntBox) -> string { "x" } diff --git a/examples/1190-diagnostics-protocol-missing-receiver.sx b/examples/1190-diagnostics-protocol-missing-receiver.sx new file mode 100644 index 00000000..d45c9861 --- /dev/null +++ b/examples/1190-diagnostics-protocol-missing-receiver.sx @@ -0,0 +1,12 @@ +// A protocol method that omits the explicit receiver (the old implicit form) +// is now a parse error — the receiver `self: *Self`/`self: Self` is required as +// the first parameter. This guards the diagnostic. +#import "modules/std.sx"; + +Show :: protocol { + show :: () -> string; // ERROR: missing `self` receiver +} + +main :: () -> i32 { + return 0; +} diff --git a/examples/expected/0418-protocols-explicit-receiver.exit b/examples/expected/0418-protocols-explicit-receiver.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/0418-protocols-explicit-receiver.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0418-protocols-explicit-receiver.stderr b/examples/expected/0418-protocols-explicit-receiver.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/0418-protocols-explicit-receiver.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0418-protocols-explicit-receiver.stdout b/examples/expected/0418-protocols-explicit-receiver.stdout new file mode 100644 index 00000000..4a632468 --- /dev/null +++ b/examples/expected/0418-protocols-explicit-receiver.stdout @@ -0,0 +1,3 @@ +measure=5x10 +scaled=15x30 +name=widget diff --git a/examples/expected/1190-diagnostics-protocol-missing-receiver.exit b/examples/expected/1190-diagnostics-protocol-missing-receiver.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1190-diagnostics-protocol-missing-receiver.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1190-diagnostics-protocol-missing-receiver.stderr b/examples/expected/1190-diagnostics-protocol-missing-receiver.stderr new file mode 100644 index 00000000..f3156483 --- /dev/null +++ b/examples/expected/1190-diagnostics-protocol-missing-receiver.stderr @@ -0,0 +1,5 @@ +error: protocol method must declare its receiver as the first parameter: `self: *Self` (or `self: Self`) + --> examples/1190-diagnostics-protocol-missing-receiver.sx:7:16 + | + 7 | show :: () -> string; // ERROR: missing `self` receiver + | ^^ diff --git a/examples/expected/1190-diagnostics-protocol-missing-receiver.stdout b/examples/expected/1190-diagnostics-protocol-missing-receiver.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1190-diagnostics-protocol-missing-receiver.stdout @@ -0,0 +1 @@ + diff --git a/library/modules/gpu/api.sx b/library/modules/gpu/api.sx index 33e5ba24..2d219761 100644 --- a/library/modules/gpu/api.sx +++ b/library/modules/gpu/api.sx @@ -11,47 +11,47 @@ GPU :: protocol { // Bind the GPU to a backend-specific render target (e.g. a // CAMetalLayer on iOS). pixel_w/pixel_h are the drawable's pixel // dimensions; call resize when they change. - init :: (target: *void, pixel_w: i32, pixel_h: i32) -> bool; - shutdown :: (); + init :: (self: *Self, target: *void, pixel_w: i32, pixel_h: i32) -> bool; + shutdown :: (self: *Self); - resize :: (pixel_w: i32, pixel_h: i32); + resize :: (self: *Self, pixel_w: i32, pixel_h: i32); - begin_frame :: (clear: ClearColor) -> bool; + begin_frame :: (self: *Self, clear: ClearColor) -> bool; // target_time is the host clock time at which the drawable should be // presented (units match the platform's CADisplayLink.targetTimestamp // on Apple). Metal forwards it to presentDrawable:atTime: to cap the // pipeline at one frame so the inset slide lands on the same vsync as // UIKit's keyboard view. GL backends ignore it. - end_frame :: (target_time: f64); + end_frame :: (self: *Self, target_time: f64); - create_shader :: (vsrc: string, fsrc: string) -> ShaderHandle; - create_buffer :: (size_bytes: i64) -> BufferHandle; - update_buffer :: (buf: BufferHandle, data: *void, size_bytes: i64); + create_shader :: (self: *Self, vsrc: string, fsrc: string) -> ShaderHandle; + create_buffer :: (self: *Self, size_bytes: i64) -> BufferHandle; + update_buffer :: (self: *Self, buf: BufferHandle, data: *void, size_bytes: i64); // Sub-buffer write at a byte offset. Required for Metal where re-using // the same buffer slice across multiple draws in a single command // encoder is a race: the GPU executes draws asynchronously and reads // shared-storage buffer contents at execution time, so the LAST writer // wins if every flush targets offset 0. Renderers that issue more than // one draw per frame must advance their write offset between flushes. - update_buffer_at :: (buf: BufferHandle, data: *void, size_bytes: i64, byte_offset: i64); - create_texture :: (w: i32, h: i32, format: TextureFormat, pixels: *void) -> TextureHandle; - update_texture_region :: (tex: TextureHandle, x: i32, y: i32, w: i32, h: i32, pixels: *void); + update_buffer_at :: (self: *Self, buf: BufferHandle, data: *void, size_bytes: i64, byte_offset: i64); + create_texture :: (self: *Self, w: i32, h: i32, format: TextureFormat, pixels: *void) -> TextureHandle; + update_texture_region :: (self: *Self, tex: TextureHandle, x: i32, y: i32, w: i32, h: i32, pixels: *void); // Release a GPU resource. Implementations release the backing object and // null the slot so the handle becomes inert. Calling with handle 0 or // an already-destroyed handle is a no-op. Handles are not re-used; the // backing List entry stays at its index with a null sentinel. - destroy_shader :: (sh: ShaderHandle); - destroy_buffer :: (buf: BufferHandle); - destroy_texture :: (tex: TextureHandle); + destroy_shader :: (self: *Self, sh: ShaderHandle); + destroy_buffer :: (self: *Self, buf: BufferHandle); + destroy_texture :: (self: *Self, tex: TextureHandle); - set_shader :: (sh: ShaderHandle); - set_vertex_buffer :: (buf: BufferHandle); - set_texture :: (slot: u32, tex: TextureHandle); - set_vertex_constants :: (slot: u32, data: *void, size_bytes: i64); - set_scissor :: (x: i32, y: i32, w: i32, h: i32); - disable_scissor :: (); + set_shader :: (self: *Self, sh: ShaderHandle); + set_vertex_buffer :: (self: *Self, buf: BufferHandle); + set_texture :: (self: *Self, slot: u32, tex: TextureHandle); + set_vertex_constants :: (self: *Self, slot: u32, data: *void, size_bytes: i64); + set_scissor :: (self: *Self, x: i32, y: i32, w: i32, h: i32); + disable_scissor :: (self: *Self); - draw_triangles :: (vertex_offset: i32, vertex_count: i32); + draw_triangles :: (self: *Self, vertex_offset: i32, vertex_count: i32); } diff --git a/library/modules/platform/api.sx b/library/modules/platform/api.sx index 7edf0dd3..b0a476ef 100644 --- a/library/modules/platform/api.sx +++ b/library/modules/platform/api.sx @@ -4,24 +4,24 @@ #import "modules/platform/types.sx"; Platform :: protocol { - init :: (title: [:0]u8, w: i32, h: i32) -> bool; + init :: (self: *Self, title: [:0]u8, w: i32, h: i32) -> bool; - run_frame_loop :: (frame_fn: Closure()); + run_frame_loop :: (self: *Self, frame_fn: Closure()); - poll_events :: () -> []Event; + poll_events :: (self: *Self) -> []Event; - begin_frame :: () -> FrameContext; - end_frame :: (); + begin_frame :: (self: *Self) -> FrameContext; + end_frame :: (self: *Self); - safe_insets :: () -> EdgeInsets; - keyboard :: () -> KeyboardState; - show_keyboard :: (); - hide_keyboard :: (); + safe_insets :: (self: *Self) -> EdgeInsets; + keyboard :: (self: *Self) -> KeyboardState; + show_keyboard :: (self: *Self); + hide_keyboard :: (self: *Self); // Request the run loop to stop. On iOS/Android this is a no-op // (mobile apps don't quit on user request); on SDL it tears down the // `while !quit` loop. - stop :: (); + stop :: (self: *Self); - shutdown :: (); + shutdown :: (self: *Self); } diff --git a/library/modules/std/core.sx b/library/modules/std/core.sx index 6c1dbf36..ce6fa5d8 100644 --- a/library/modules/std/core.sx +++ b/library/modules/std/core.sx @@ -60,8 +60,8 @@ string :: []u8 #builtin; // Bytes-level primitives carry the `_bytes` suffix so the typed // helpers in std/mem.sx own the bare names (`alloc(T, n)`, `free(s)`). Allocator :: protocol #inline { - alloc_bytes :: (size: i64) -> *void; - dealloc_bytes :: (ptr: *void); + alloc_bytes :: (self: *Self, size: i64) -> *void; + dealloc_bytes :: (self: *Self, ptr: *void); } // --- Io capability protocol (impls live in std/io.sx) --- @@ -98,12 +98,12 @@ ParkToken :: struct { } Io :: protocol #inline { - spawn_raw :: (entry: *void, arg: *void, opts: SpawnOpts) -> *void; - suspend_raw :: (park: ParkToken) -> !; - ready :: (park: ParkToken); - poll :: (deadline_ms: i64) -> i64; - now_ms :: () -> i64; - arm_timer :: (deadline_ms: i64, park: ParkToken) -> *void; + spawn_raw :: (self: *Self, entry: *void, arg: *void, opts: SpawnOpts) -> *void; + suspend_raw :: (self: *Self, park: ParkToken) -> !; + ready :: (self: *Self, park: ParkToken); + poll :: (self: *Self, deadline_ms: i64) -> i64; + now_ms :: (self: *Self) -> i64; + arm_timer :: (self: *Self, deadline_ms: i64, park: ParkToken) -> *void; } // --- Context --- @@ -127,5 +127,5 @@ Context :: struct { // and emits a direct call. Compile-time only — no vtable, no runtime // dispatch. Into :: protocol(Target: Type) { - convert :: () -> Target; + convert :: (self: *Self) -> Target; } diff --git a/library/modules/ui/animation.sx b/library/modules/ui/animation.sx index b1b4c353..c78a75bd 100755 --- a/library/modules/ui/animation.sx +++ b/library/modules/ui/animation.sx @@ -4,7 +4,7 @@ // --- Lerpable protocol (inline — static dispatch, no vtable) --- Lerpable :: protocol #inline { - lerp :: (b: Self, t: f32) -> Self; + lerp :: (self: *Self, b: Self, t: f32) -> Self; } // --- Easing Functions --- diff --git a/library/modules/ui/view.sx b/library/modules/ui/view.sx index cd94e335..09051b26 100755 --- a/library/modules/ui/view.sx +++ b/library/modules/ui/view.sx @@ -4,16 +4,16 @@ View :: protocol { // Measure: given a size proposal, return desired size - size_that_fits :: (proposal: ProposedSize) -> Size; + size_that_fits :: (self: *Self, proposal: ProposedSize) -> Size; // Place: position children within the given bounds - layout :: (bounds: Frame); + layout :: (self: *Self, bounds: Frame); // Render: emit render nodes - render :: (ctx: *RenderContext, frame: Frame); + render :: (self: *Self, ctx: *RenderContext, frame: Frame); // Event handling: return true if the event was consumed - handle_event :: (event: *Event, frame: Frame) -> bool; + handle_event :: (self: *Self, event: *Event, frame: Frame) -> bool; } // A child view with its computed frame (set during layout) diff --git a/readme.md b/readme.md index 28528f34..470fee2d 100644 --- a/readme.md +++ b/readme.md @@ -184,7 +184,7 @@ function declaration, an `impl` method definition, or a `::` type declaration (`i2 :: 5` and `i2 :: (n) { … }` are rejected just like `i2 := 5`). **Member-name positions are exempt**: a struct *field*, a union *tag*, and a protocol *method-signature* may be a bare reserved spelling (`struct { i2: i64 }`, -`union { u8: … }`, `protocol { i2 :: () -> i64 }`) — they are reached via `obj.name`, +`union { u8: … }`, `protocol { i2 :: (self: *Self) -> i64 }`) — they are reached via `obj.name`, so they never mis-lower. The bare exemption covers only the identifier-classified reserved names (`i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`, `isize`, `Any`); `f32` and `f64` are lexer keywords, so even in a member slot they @@ -334,7 +334,7 @@ Closures capture by value. Bare functions auto-promote to closures when needed. ```sx Drawable :: protocol { - draw :: (x: i32, y: i32); + draw :: (self: *Self, x: i32, y: i32); // receiver is explicit + required } impl Drawable for Circle { @@ -345,11 +345,15 @@ shape : Drawable = xx my_circle; // type erasure via xx shape.draw(10, 20); // dynamic dispatch ``` +Every protocol method declares its receiver explicitly as the first parameter +(`self: *Self` or `self: Self`), matching the `impl` signature; the annotation is +erased before dispatch, so the call site is unchanged. + `#inline` protocols store function pointers directly (no vtable indirection): ```sx Allocator :: protocol #inline { - alloc :: (size: i64) -> *void; - dealloc :: (ptr: *void); + alloc :: (self: *Self, size: i64) -> *void; + dealloc :: (self: *Self, ptr: *void); } ``` diff --git a/specs.md b/specs.md index 8bf3ddbe..e310aeec 100644 --- a/specs.md +++ b/specs.md @@ -45,7 +45,7 @@ reserved spellings — `i1`..`i64`, `u1`..`u64`, `bool`, `string`, `cstring`, `v member-name slots require an identifier token; a bare `f32` / `f64` is therefore rejected at parse (`expected field name in struct`) even in a member position. Use the backtick there too — `` struct { `f32: i64; } `` / `` union { `f64: … } `` / -`` protocol { `f32 :: () -> i64; } `` work as field / tag / method names. +`` protocol { `f32 :: (self: *Self) -> i64; } `` work as field / tag / method names. ```sx i2 := 2.5; // ERROR: 'i2' is a reserved type name and cannot be used as an identifier @@ -563,12 +563,12 @@ Protocols define a set of method signatures that types can implement. They enabl #### Declaration ```sx Allocator :: protocol #inline { - alloc :: (size: i64) -> *void; - dealloc :: (ptr: *void); + alloc :: (self: *Self, size: i64) -> *void; + dealloc :: (self: *Self, ptr: *void); } ``` -Protocol methods have an **implicit receiver** — no `self` in the protocol signature. The compiler adds `*Self` automatically. The `#inline` modifier embeds function pointers directly in the protocol value (no vtable indirection). +Protocol methods declare their receiver **explicitly** as the first parameter — `self: *Self` (or `self: Self`) — matching the corresponding `impl` method signature. This is **required**: a protocol method whose first parameter is not `self: *Self`/`self: Self` is a parse error. (It removes the old implicit-receiver ambiguity over whether the first listed parameter was the receiver or an extra argument.) The receiver annotation is validated then erased — the dispatch ABI is unchanged, so existing `impl` blocks and call sites are unaffected. The `#inline` modifier embeds function pointers directly in the protocol value (no vtable indirection). #### `#inline` vs default layout @@ -675,8 +675,8 @@ s : Sizable = xx @w; // identical to `xx w` — borrows w Protocol methods can have bodies. `self` dispatches through the vtable (dynamic dispatch): ```sx Writer :: protocol { - write :: (data: string) -> i64; // required - write_line :: (data: string) -> i64 { // default + write :: (self: *Self, data: string) -> i64; // required + write_line :: (self: *Self, data: string) -> i64 { // default n := self.write(data); n + self.write("\n"); } @@ -688,7 +688,7 @@ Default methods are used unless overridden in the impl. Default methods calling #### `Self` Type `Self` is a contextual keyword in protocol declarations — resolves to the concrete type in impls: ```sx -Eq :: protocol { eq :: (other: Self) -> bool; } +Eq :: protocol { eq :: (self: *Self, other: Self) -> bool; } impl Eq for Point { eq :: (self: *Point, other: Point) -> bool { @@ -755,7 +755,7 @@ in `modules/std.sx`: ```sx Into :: protocol(Target: Type) { - convert :: () -> Target; + convert :: (self: *Self) -> Target; } ``` diff --git a/src/parser.zig b/src/parser.zig index dff7dca2..3b27d812 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1223,6 +1223,27 @@ pub const Parser = struct { } try self.expect(.r_paren); + // Every protocol method must declare its receiver EXPLICITLY as the + // first parameter — `self: *Self` (or `self: Self`) — matching how + // `impl` methods and ordinary methods are written. This removes the + // old implicit-receiver ambiguity (was the first listed param the + // receiver, or an extra arg?). The receiver is validated and then + // stripped here, so downstream lowering sees only the EXTRA-arg + // params, exactly as it did under the implicit form. + if (param_names.items.len == 0 or !std.mem.eql(u8, param_names.items[0], "self")) { + return self.fail("protocol method must declare its receiver as the first parameter: `self: *Self` (or `self: Self`)"); + } + { + const rtype = param_types.items[0]; + const is_self_val = rtype.data == .type_expr and std.mem.eql(u8, rtype.data.type_expr.name, "Self"); + const is_self_ptr = rtype.data == .pointer_type_expr and + rtype.data.pointer_type_expr.pointee_type.data == .type_expr and + std.mem.eql(u8, rtype.data.pointer_type_expr.pointee_type.data.type_expr.name, "Self"); + if (!is_self_val and !is_self_ptr) { + return self.fail("protocol method receiver must be typed `*Self` or `Self`"); + } + } + // Optional return type var return_type: ?*Node = null; if (self.current.tag == .arrow) { @@ -1238,12 +1259,19 @@ pub const Parser = struct { if (self.current.tag == .semicolon) self.advance(); } + // Strip the receiver (index 0) — the method's stored params are the + // extra args only. + const all_param_types = try param_types.toOwnedSlice(self.allocator); + const all_param_names = try param_names.toOwnedSlice(self.allocator); + const all_param_name_spans = try param_name_spans.toOwnedSlice(self.allocator); + const all_param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator); + try methods.append(self.allocator, .{ .name = method_name, - .params = try param_types.toOwnedSlice(self.allocator), - .param_names = try param_names.toOwnedSlice(self.allocator), - .param_name_spans = try param_name_spans.toOwnedSlice(self.allocator), - .param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator), + .params = all_param_types[1..], + .param_names = all_param_names[1..], + .param_name_spans = all_param_name_spans[1..], + .param_name_is_raw = all_param_name_is_raw[1..], .return_type = return_type, .default_body = default_body, }); diff --git a/src/sema.zig b/src/sema.zig index 3f87ac64..2370b411 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -2205,7 +2205,7 @@ test "sema: method-return slice + .ptr index + tagged-enum element" { const source = "Event :: enum { none; click: i64; }" ++ - "Plat :: protocol { poll :: () -> []Event; }" ++ + "Plat :: protocol { poll :: (self: *Self) -> []Event; }" ++ "go :: (p: *Plat) { evs := p.poll(); e := evs.ptr[0]; }"; var parser = parser_mod.Parser.init(alloc, source); const root = try parser.parse();