lang: require explicit receiver in protocol method declarations

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).
This commit is contained in:
agra
2026-06-21 11:02:16 +03:00
parent eb93c63c45
commit 6b0ebdd92b
66 changed files with 249 additions and 146 deletions

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { Show :: protocol {
show :: () -> string; show :: (self: *Self) -> string;
} }
A :: struct { x: i64; } A :: struct { x: i64; }
B :: struct { s: string; } B :: struct { s: string; }

View File

@@ -10,11 +10,11 @@ mul :: (a: i32, b: i32) -> i32 { a * b }
// P4 edge: Chained default→default calls // P4 edge: Chained default→default calls
Chained :: protocol { Chained :: protocol {
base :: (msg: string) -> i32; base :: (self: *Self, msg: string) -> i32;
wrap :: (msg: string) -> i32 { wrap :: (self: *Self, msg: string) -> i32 {
self.base(msg) + 1 self.base(msg) + 1
} }
double_wrap :: (msg: string) -> i32 { double_wrap :: (self: *Self, msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg) self.wrap(msg) + self.wrap(msg)
} }
} }

View File

@@ -28,11 +28,11 @@ apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
// P4 edge: Chained default→default calls // P4 edge: Chained default→default calls
Chained :: protocol { Chained :: protocol {
base :: (msg: string) -> i32; base :: (self: *Self, msg: string) -> i32;
wrap :: (msg: string) -> i32 { wrap :: (self: *Self, msg: string) -> i32 {
self.base(msg) + 1 self.base(msg) + 1
} }
double_wrap :: (msg: string) -> i32 { double_wrap :: (self: *Self, msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg) self.wrap(msg) + self.wrap(msg)
} }
} }

View File

@@ -9,12 +9,12 @@ Point :: struct { x, y: i32; }
add :: (a: i32, b: i32) -> i32 { a + b } add :: (a: i32, b: i32) -> i32 { a + b }
Counter :: protocol { Counter :: protocol {
inc :: (); inc :: (self: *Self);
get :: () -> i32; get :: (self: *Self) -> i32;
} }
Summable :: protocol { Summable :: protocol {
sum :: () -> i32; sum :: (self: *Self) -> i32;
} }
SimpleCounter :: struct { val: i32; } SimpleCounter :: struct { val: i32; }

View File

@@ -28,7 +28,7 @@ Tag :: union {
// Protocol method SIGNATURE spelled with a reserved type name — bare is legal. // Protocol method SIGNATURE spelled with a reserved type name — bare is legal.
Speaker :: protocol { Speaker :: protocol {
i2 :: () -> i64; i2 :: (self: *Self) -> i64;
} }
Dog :: struct { n: i64; } Dog :: struct { n: i64; }

View File

@@ -5,7 +5,7 @@
#import "modules/math"; #import "modules/math";
Lerpable :: protocol #inline { Lerpable :: protocol #inline {
lerp :: (b: Self, t: f32) -> Self; lerp :: (self: *Self, b: Self, t: f32) -> Self;
} }
Size :: struct { Size :: struct {

View File

@@ -4,7 +4,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
MyProtocol :: protocol { MyProtocol :: protocol {
get_value :: () -> i64; get_value :: (self: *Self) -> i64;
} }
MyImpl :: struct { value: i64; } MyImpl :: struct { value: i64; }

View File

@@ -3,7 +3,7 @@
// `#inline` erasure. // `#inline` erasure.
Lerpable :: protocol #inline { Lerpable :: protocol #inline {
lerp :: (b: Self, t: f32) -> Self; lerp :: (self: *Self, b: Self, t: f32) -> Self;
} }
impl Lerpable for f32 { impl Lerpable for f32 {

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Sizable :: protocol { Sizable :: protocol {
size :: () -> i64; size :: (self: *Self) -> i64;
} }
Widget :: struct { value: i64; } Widget :: struct { value: i64; }

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Sizable :: protocol { Sizable :: protocol {
size :: () -> i64; size :: (self: *Self) -> i64;
} }
Leaf :: struct { value: i64; } Leaf :: struct { value: i64; }

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Sizable :: protocol { Sizable :: protocol {
size :: () -> i64; size :: (self: *Self) -> i64;
} }
Leaf :: struct { value: i64; } Leaf :: struct { value: i64; }

View File

@@ -5,10 +5,10 @@
#import "modules/std.sx"; #import "modules/std.sx";
Drawable :: protocol { Drawable :: protocol {
draw :: () -> i32; draw :: (self: *Self) -> i32;
name :: () -> string; name :: (self: *Self) -> string;
layout :: (x: i32) -> i32; layout :: (self: *Self, x: i32) -> i32;
handle :: (event: i32) -> bool; handle :: (self: *Self, event: i32) -> bool;
} }
Circle :: struct { radius: i32; } Circle :: struct { radius: i32; }

View File

@@ -6,7 +6,7 @@
Fmt :: enum { a; b; } Fmt :: enum { a; b; }
Proto :: protocol { Proto :: protocol {
take_fmt :: (f: Fmt); take_fmt :: (self: *Self, f: Fmt);
} }
Impl :: struct {} Impl :: struct {}

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Proto :: protocol { Proto :: protocol {
get :: () -> *u8; get :: (self: *Self) -> *u8;
} }
Impl :: struct { Impl :: struct {

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
GPU :: protocol { GPU :: protocol {
ping :: () -> i64; ping :: (self: *Self) -> i64;
} }
Impl :: struct {} Impl :: struct {}

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
StrCell :: struct { s: string; } StrCell :: struct { s: string; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -9,7 +9,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -203,12 +203,12 @@ c_abs :: (n: i32) -> i32 extern libc "abs";
// --- Protocol declarations (Phase 1: static dispatch only) --- // --- Protocol declarations (Phase 1: static dispatch only) ---
Counter :: protocol { Counter :: protocol {
inc :: (); inc :: (self: *Self);
get :: () -> i32; get :: (self: *Self) -> i32;
} }
Summable :: protocol { Summable :: protocol {
sum :: () -> i32; sum :: (self: *Self) -> i32;
} }
SimpleCounter :: struct { val: i32; } SimpleCounter :: struct { val: i32; }
@@ -226,8 +226,8 @@ impl Summable for Point {
// Phase 2: #inline protocol for dynamic dispatch // Phase 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline { Adder :: protocol #inline {
add :: (n: i32); add :: (self: *Self, n: i32);
value :: () -> i32; value :: (self: *Self) -> i32;
} }
Accumulator :: struct { Accumulator :: struct {
@@ -250,8 +250,8 @@ impl Adder for Doubler {
// Phase 4: default methods // Phase 4: default methods
Repeater :: protocol { Repeater :: protocol {
say :: (msg: string); say :: (self: *Self, msg: string);
say_twice :: (msg: string) { say_twice :: (self: *Self, msg: string) {
self.say(msg); self.say(msg);
self.say(msg); self.say(msg);
} }
@@ -270,11 +270,11 @@ impl Repeater for Printer {
// P4 edge: Chained default→default calls // P4 edge: Chained default→default calls
Chained :: protocol { Chained :: protocol {
base :: (msg: string) -> i32; base :: (self: *Self, msg: string) -> i32;
wrap :: (msg: string) -> i32 { wrap :: (self: *Self, msg: string) -> i32 {
self.base(msg) + 1 self.base(msg) + 1
} }
double_wrap :: (msg: string) -> i32 { double_wrap :: (self: *Self, msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg) self.wrap(msg) + self.wrap(msg)
} }
} }
@@ -291,7 +291,7 @@ impl Chained for ChainImpl {
// Phase 5: Self type // Phase 5: Self type
Eq :: protocol { Eq :: protocol {
eq :: (other: Self) -> bool; eq :: (self: *Self, other: Self) -> bool;
} }
impl Eq for Point { impl Eq for Point {
@@ -301,7 +301,7 @@ impl Eq for Point {
} }
Cloneable :: protocol { Cloneable :: protocol {
clone :: () -> Self; clone :: (self: *Self) -> Self;
} }
impl Cloneable for Point { impl Cloneable for Point {
@@ -324,7 +324,7 @@ are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
} }
Hashable :: protocol { Hashable :: protocol {
hash :: () -> i64; hash :: (self: *Self) -> i64;
} }
impl Hashable for Point { impl Hashable for Point {

View File

@@ -9,12 +9,12 @@ Point :: struct { x, y: i32; }
add :: (a: i32, b: i32) -> i32 { a + b } add :: (a: i32, b: i32) -> i32 { a + b }
Counter :: protocol { Counter :: protocol {
inc :: (); inc :: (self: *Self);
get :: () -> i32; get :: (self: *Self) -> i32;
} }
Summable :: protocol { Summable :: protocol {
sum :: () -> i32; sum :: (self: *Self) -> i32;
} }
SimpleCounter :: struct { val: i32; } SimpleCounter :: struct { val: i32; }
@@ -32,8 +32,8 @@ impl Summable for Point {
// Phase 2: #inline protocol for dynamic dispatch // Phase 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline { Adder :: protocol #inline {
add :: (n: i32); add :: (self: *Self, n: i32);
value :: () -> i32; value :: (self: *Self) -> i32;
} }
Accumulator :: struct { Accumulator :: struct {

View File

@@ -24,7 +24,7 @@ Keycode :: enum { unknown; escape; enter; }
KeyData :: struct { key: Keycode; } KeyData :: struct { key: Keycode; }
Event :: enum { none; key_up: KeyData; } Event :: enum { none; key_up: KeyData; }
Plat :: protocol { one_event :: () -> Event; } Plat :: protocol { one_event :: (self: *Self) -> Event; }
Impl :: struct { dummy: i64; } Impl :: struct { dummy: i64; }
impl Plat for Impl { impl Plat for Impl {

View File

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

View File

@@ -24,7 +24,7 @@
// User-defined parameterised protocol + an impl, so has_impl can // User-defined parameterised protocol + an impl, so has_impl can
// confirm parameterised matching works with a known-true case. // confirm parameterised matching works with a known-true case.
Wrap :: protocol(Target: Type) { Wrap :: protocol(Target: Type) {
wrap :: () -> Target; wrap :: (self: *Self) -> Target;
} }
impl Wrap(i64) for i32 { impl Wrap(i64) for i32 {

View File

@@ -9,7 +9,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Greeter :: protocol { Greeter :: protocol {
greet :: () -> i64; greet :: (self: *Self) -> i64;
} }
Dog :: struct { age: i64; } Dog :: struct { age: i64; }

View File

@@ -10,7 +10,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> T; get :: (self: *Self) -> T;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> T; get :: (self: *Self) -> T;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl Box(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl Box(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -8,7 +8,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> T; get :: (self: *Self) -> T;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> i64; get :: (self: *Self) -> i64;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
Dbl :: struct { n: i64; } Dbl :: struct { n: i64; }

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> T; get :: (self: *Self) -> T;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
StrCell :: struct { s: string; } StrCell :: struct { s: string; }

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: protocol(T: Type) { Box :: protocol(T: Type) {
get :: () -> T; get :: (self: *Self) -> T;
} }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }

View File

@@ -9,7 +9,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { show :: () -> string; } Show :: protocol { show :: (self: *Self) -> string; }
A :: struct { x: i64; } A :: struct { x: i64; }
B :: struct { s: string; } B :: struct { s: string; }
impl Show for A { show :: (self: *A) -> string => "A"; } impl Show for A { show :: (self: *A) -> string => "A"; }

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { show :: () -> string; } Show :: protocol { show :: (self: *Self) -> string; }
A :: struct {} A :: struct {}
impl Show for A { show :: (self: *A) -> string => "A"; } impl Show for A { show :: (self: *A) -> string => "A"; }

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { show :: () -> string; } Show :: protocol { show :: (self: *Self) -> string; }
A :: struct {} A :: struct {}
B :: struct { s: string; } B :: struct { s: string; }
impl Show for A { show :: (self: *A) -> string => "A"; } impl Show for A { show :: (self: *A) -> string => "A"; }

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
StrCell :: struct { s: string; } StrCell :: struct { s: string; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
StrCell :: struct { s: string; } StrCell :: struct { s: string; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -4,7 +4,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -12,7 +12,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -11,7 +11,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { show :: () -> string; } Show :: protocol { show :: (self: *Self) -> string; }
IntBox :: struct { v: i64; } IntBox :: struct { v: i64; }
StrBox :: struct { s: string; } StrBox :: struct { s: string; }
impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } } impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } }

View File

@@ -11,7 +11,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
VL :: protocol(T: Type) { get :: () -> T; } VL :: protocol(T: Type) { get :: (self: *Self) -> T; }
IntCell :: struct { v: i64; } IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Doubler :: struct { n: i64; } Doubler :: struct { n: i64; }

View File

@@ -15,7 +15,7 @@
Box :: struct { m: i32; } Box :: struct { m: i32; }
Provider :: protocol { Provider :: protocol {
get :: () -> Box; get :: (self: *Self) -> Box;
} }
Holder :: struct { val: i32 = 7; } Holder :: struct { val: i32 = 7; }

View File

@@ -10,7 +10,7 @@
#import "0821-protocols-same-name-method-ambiguous/b.sx"; #import "0821-protocols-same-name-method-ambiguous/b.sx";
Provider :: protocol { Provider :: protocol {
get :: () -> Box; get :: (self: *Self) -> Box;
} }
main :: () -> i32 { main :: () -> i32 {

View File

@@ -21,17 +21,17 @@ Holder :: struct { b: Box = ---; }
Provider :: protocol { Provider :: protocol {
// discriminating wrapped/compound RETURNS // discriminating wrapped/compound RETURNS
getp :: () -> *Box; getp :: (self: *Self) -> *Box;
geto :: () -> ?Box; geto :: (self: *Self) -> ?Box;
gett :: () -> (Box, Box); gett :: (self: *Self) -> (Box, Box);
geta :: () -> [2]Box; geta :: (self: *Self) -> [2]Box;
// routing-only wrapped/compound PARAMS // routing-only wrapped/compound PARAMS
sump :: (p: *Box) -> i32; sump :: (self: *Self, p: *Box) -> i32;
sumo :: (o: ?Box) -> i32; sumo :: (self: *Self, o: ?Box) -> i32;
sums :: (s: []Box) -> i32; sums :: (self: *Self, s: []Box) -> i32;
suma :: (a: [2]Box) -> i32; suma :: (self: *Self, a: [2]Box) -> i32;
sumt :: (t: (Box, Box)) -> i32; sumt :: (self: *Self, t: (Box, Box)) -> i32;
sumn :: (n: *?[]Box) -> i32; sumn :: (self: *Self, n: *?[]Box) -> i32;
} }
impl Provider for Holder { impl Provider for Holder {

View File

@@ -15,7 +15,7 @@
#import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx"; #import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx";
Provider :: protocol { Provider :: protocol {
getp :: () -> *Box; getp :: (self: *Self) -> *Box;
} }
main :: () -> i32 { main :: () -> i32 {

View File

@@ -15,7 +15,7 @@ Box :: struct { m: i32; }
Holder :: struct { n: i32; } Holder :: struct { n: i32; }
Tagged :: protocol(T: Type) { Tagged :: protocol(T: Type) {
tag :: () -> i32; tag :: (self: *Self) -> i32;
} }
impl Tagged(*Box) for Holder { impl Tagged(*Box) for Holder {

View File

@@ -22,7 +22,7 @@
Block :: struct { tag: i32; } Block :: struct { tag: i32; }
Sink :: protocol(T: Type) { Sink :: protocol(T: Type) {
convert :: () -> T; convert :: (self: *Self) -> T;
} }
impl Sink(Block) for Closure(*Box, ..$args) -> $R { impl Sink(Block) for Closure(*Box, ..$args) -> $R {

View File

@@ -10,7 +10,7 @@ ProposedSize :: struct {
} }
Sizable :: protocol { Sizable :: protocol {
size :: (proposal: ProposedSize) -> f32; size :: (self: *Self, proposal: ProposedSize) -> f32;
} }
Widget :: struct {} Widget :: struct {}

View File

@@ -16,7 +16,7 @@ direct_size :: (proposal: ProposedSize) -> f32 {
} }
Sizable :: protocol { Sizable :: protocol {
size :: (proposal: ProposedSize) -> f32; size :: (self: *Self, proposal: ProposedSize) -> f32;
} }
Widget :: struct {} Widget :: struct {}

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
Show :: protocol { show :: () -> string; } Show :: protocol { show :: (self: *Self) -> string; }
IntBox :: struct { v: i64; } IntBox :: struct { v: i64; }
impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } } impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } }

View File

@@ -14,7 +14,7 @@ Foo :: struct {
weird :: (self: *Foo) -> $T { 0 } weird :: (self: *Foo) -> $T { 0 }
} }
Show2 :: protocol { show2 :: () -> string; } Show2 :: protocol { show2 :: (self: *Self) -> string; }
IntBox :: struct { v: i64; } IntBox :: struct { v: i64; }
impl Show2 for IntBox { impl Show2 for IntBox {
show2 :: (self: *IntBox) -> string { "x" } show2 :: (self: *IntBox) -> string { "x" }

View File

@@ -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;
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
measure=5x10
scaled=15x30
name=widget

View File

@@ -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
| ^^

View File

@@ -11,47 +11,47 @@ GPU :: protocol {
// Bind the GPU to a backend-specific render target (e.g. a // Bind the GPU to a backend-specific render target (e.g. a
// CAMetalLayer on iOS). pixel_w/pixel_h are the drawable's pixel // CAMetalLayer on iOS). pixel_w/pixel_h are the drawable's pixel
// dimensions; call resize when they change. // dimensions; call resize when they change.
init :: (target: *void, pixel_w: i32, pixel_h: i32) -> bool; init :: (self: *Self, target: *void, pixel_w: i32, pixel_h: i32) -> bool;
shutdown :: (); 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 // target_time is the host clock time at which the drawable should be
// presented (units match the platform's CADisplayLink.targetTimestamp // presented (units match the platform's CADisplayLink.targetTimestamp
// on Apple). Metal forwards it to presentDrawable:atTime: to cap the // 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 // pipeline at one frame so the inset slide lands on the same vsync as
// UIKit's keyboard view. GL backends ignore it. // 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_shader :: (self: *Self, vsrc: string, fsrc: string) -> ShaderHandle;
create_buffer :: (size_bytes: i64) -> BufferHandle; create_buffer :: (self: *Self, size_bytes: i64) -> BufferHandle;
update_buffer :: (buf: BufferHandle, data: *void, size_bytes: i64); update_buffer :: (self: *Self, buf: BufferHandle, data: *void, size_bytes: i64);
// Sub-buffer write at a byte offset. Required for Metal where re-using // Sub-buffer write at a byte offset. Required for Metal where re-using
// the same buffer slice across multiple draws in a single command // the same buffer slice across multiple draws in a single command
// encoder is a race: the GPU executes draws asynchronously and reads // encoder is a race: the GPU executes draws asynchronously and reads
// shared-storage buffer contents at execution time, so the LAST writer // shared-storage buffer contents at execution time, so the LAST writer
// wins if every flush targets offset 0. Renderers that issue more than // wins if every flush targets offset 0. Renderers that issue more than
// one draw per frame must advance their write offset between flushes. // one draw per frame must advance their write offset between flushes.
update_buffer_at :: (buf: BufferHandle, data: *void, size_bytes: i64, byte_offset: i64); update_buffer_at :: (self: *Self, buf: BufferHandle, data: *void, size_bytes: i64, byte_offset: i64);
create_texture :: (w: i32, h: i32, format: TextureFormat, pixels: *void) -> TextureHandle; create_texture :: (self: *Self, 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_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 // Release a GPU resource. Implementations release the backing object and
// null the slot so the handle becomes inert. Calling with handle 0 or // 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 // 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. // backing List entry stays at its index with a null sentinel.
destroy_shader :: (sh: ShaderHandle); destroy_shader :: (self: *Self, sh: ShaderHandle);
destroy_buffer :: (buf: BufferHandle); destroy_buffer :: (self: *Self, buf: BufferHandle);
destroy_texture :: (tex: TextureHandle); destroy_texture :: (self: *Self, tex: TextureHandle);
set_shader :: (sh: ShaderHandle); set_shader :: (self: *Self, sh: ShaderHandle);
set_vertex_buffer :: (buf: BufferHandle); set_vertex_buffer :: (self: *Self, buf: BufferHandle);
set_texture :: (slot: u32, tex: TextureHandle); set_texture :: (self: *Self, slot: u32, tex: TextureHandle);
set_vertex_constants :: (slot: u32, data: *void, size_bytes: i64); set_vertex_constants :: (self: *Self, slot: u32, data: *void, size_bytes: i64);
set_scissor :: (x: i32, y: i32, w: i32, h: i32); set_scissor :: (self: *Self, x: i32, y: i32, w: i32, h: i32);
disable_scissor :: (); disable_scissor :: (self: *Self);
draw_triangles :: (vertex_offset: i32, vertex_count: i32); draw_triangles :: (self: *Self, vertex_offset: i32, vertex_count: i32);
} }

View File

@@ -4,24 +4,24 @@
#import "modules/platform/types.sx"; #import "modules/platform/types.sx";
Platform :: protocol { 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; begin_frame :: (self: *Self) -> FrameContext;
end_frame :: (); end_frame :: (self: *Self);
safe_insets :: () -> EdgeInsets; safe_insets :: (self: *Self) -> EdgeInsets;
keyboard :: () -> KeyboardState; keyboard :: (self: *Self) -> KeyboardState;
show_keyboard :: (); show_keyboard :: (self: *Self);
hide_keyboard :: (); hide_keyboard :: (self: *Self);
// Request the run loop to stop. On iOS/Android this is a no-op // 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 // (mobile apps don't quit on user request); on SDL it tears down the
// `while !quit` loop. // `while !quit` loop.
stop :: (); stop :: (self: *Self);
shutdown :: (); shutdown :: (self: *Self);
} }

View File

@@ -60,8 +60,8 @@ string :: []u8 #builtin;
// Bytes-level primitives carry the `_bytes` suffix so the typed // Bytes-level primitives carry the `_bytes` suffix so the typed
// helpers in std/mem.sx own the bare names (`alloc(T, n)`, `free(s)`). // helpers in std/mem.sx own the bare names (`alloc(T, n)`, `free(s)`).
Allocator :: protocol #inline { Allocator :: protocol #inline {
alloc_bytes :: (size: i64) -> *void; alloc_bytes :: (self: *Self, size: i64) -> *void;
dealloc_bytes :: (ptr: *void); dealloc_bytes :: (self: *Self, ptr: *void);
} }
// --- Io capability protocol (impls live in std/io.sx) --- // --- Io capability protocol (impls live in std/io.sx) ---
@@ -98,12 +98,12 @@ ParkToken :: struct {
} }
Io :: protocol #inline { Io :: protocol #inline {
spawn_raw :: (entry: *void, arg: *void, opts: SpawnOpts) -> *void; spawn_raw :: (self: *Self, entry: *void, arg: *void, opts: SpawnOpts) -> *void;
suspend_raw :: (park: ParkToken) -> !; suspend_raw :: (self: *Self, park: ParkToken) -> !;
ready :: (park: ParkToken); ready :: (self: *Self, park: ParkToken);
poll :: (deadline_ms: i64) -> i64; poll :: (self: *Self, deadline_ms: i64) -> i64;
now_ms :: () -> i64; now_ms :: (self: *Self) -> i64;
arm_timer :: (deadline_ms: i64, park: ParkToken) -> *void; arm_timer :: (self: *Self, deadline_ms: i64, park: ParkToken) -> *void;
} }
// --- Context --- // --- Context ---
@@ -127,5 +127,5 @@ Context :: struct {
// and emits a direct call. Compile-time only — no vtable, no runtime // and emits a direct call. Compile-time only — no vtable, no runtime
// dispatch. // dispatch.
Into :: protocol(Target: Type) { Into :: protocol(Target: Type) {
convert :: () -> Target; convert :: (self: *Self) -> Target;
} }

View File

@@ -4,7 +4,7 @@
// --- Lerpable protocol (inline — static dispatch, no vtable) --- // --- Lerpable protocol (inline — static dispatch, no vtable) ---
Lerpable :: protocol #inline { Lerpable :: protocol #inline {
lerp :: (b: Self, t: f32) -> Self; lerp :: (self: *Self, b: Self, t: f32) -> Self;
} }
// --- Easing Functions --- // --- Easing Functions ---

View File

@@ -4,16 +4,16 @@
View :: protocol { View :: protocol {
// Measure: given a size proposal, return desired size // 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 // Place: position children within the given bounds
layout :: (bounds: Frame); layout :: (self: *Self, bounds: Frame);
// Render: emit render nodes // 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 // 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) // A child view with its computed frame (set during layout)

View File

@@ -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 (`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 positions are exempt**: a struct *field*, a union *tag*, and a protocol
*method-signature* may be a bare reserved spelling (`struct { i2: i64 }`, *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 so they never mis-lower. The bare exemption covers only the identifier-classified
reserved names (`i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`, 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 `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 ```sx
Drawable :: protocol { Drawable :: protocol {
draw :: (x: i32, y: i32); draw :: (self: *Self, x: i32, y: i32); // receiver is explicit + required
} }
impl Drawable for Circle { impl Drawable for Circle {
@@ -345,11 +345,15 @@ shape : Drawable = xx my_circle; // type erasure via xx
shape.draw(10, 20); // dynamic dispatch 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): `#inline` protocols store function pointers directly (no vtable indirection):
```sx ```sx
Allocator :: protocol #inline { Allocator :: protocol #inline {
alloc :: (size: i64) -> *void; alloc :: (self: *Self, size: i64) -> *void;
dealloc :: (ptr: *void); dealloc :: (self: *Self, ptr: *void);
} }
``` ```

View File

@@ -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 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 rejected at parse (`expected field name in struct`) even in a member position. Use
the backtick there too — `` struct { `f32: i64; } `` / `` union { `f64: … } `` / 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 ```sx
i2 := 2.5; // ERROR: 'i2' is a reserved type name and cannot be used as an identifier 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 #### Declaration
```sx ```sx
Allocator :: protocol #inline { Allocator :: protocol #inline {
alloc :: (size: i64) -> *void; alloc :: (self: *Self, size: i64) -> *void;
dealloc :: (ptr: *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 #### `#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): Protocol methods can have bodies. `self` dispatches through the vtable (dynamic dispatch):
```sx ```sx
Writer :: protocol { Writer :: protocol {
write :: (data: string) -> i64; // required write :: (self: *Self, data: string) -> i64; // required
write_line :: (data: string) -> i64 { // default write_line :: (self: *Self, data: string) -> i64 { // default
n := self.write(data); n := self.write(data);
n + self.write("\n"); n + self.write("\n");
} }
@@ -688,7 +688,7 @@ Default methods are used unless overridden in the impl. Default methods calling
#### `Self` Type #### `Self` Type
`Self` is a contextual keyword in protocol declarations — resolves to the concrete type in impls: `Self` is a contextual keyword in protocol declarations — resolves to the concrete type in impls:
```sx ```sx
Eq :: protocol { eq :: (other: Self) -> bool; } Eq :: protocol { eq :: (self: *Self, other: Self) -> bool; }
impl Eq for Point { impl Eq for Point {
eq :: (self: *Point, other: Point) -> bool { eq :: (self: *Point, other: Point) -> bool {
@@ -755,7 +755,7 @@ in `modules/std.sx`:
```sx ```sx
Into :: protocol(Target: Type) { Into :: protocol(Target: Type) {
convert :: () -> Target; convert :: (self: *Self) -> Target;
} }
``` ```

View File

@@ -1223,6 +1223,27 @@ pub const Parser = struct {
} }
try self.expect(.r_paren); 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 // Optional return type
var return_type: ?*Node = null; var return_type: ?*Node = null;
if (self.current.tag == .arrow) { if (self.current.tag == .arrow) {
@@ -1238,12 +1259,19 @@ pub const Parser = struct {
if (self.current.tag == .semicolon) self.advance(); 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, .{ try methods.append(self.allocator, .{
.name = method_name, .name = method_name,
.params = try param_types.toOwnedSlice(self.allocator), .params = all_param_types[1..],
.param_names = try param_names.toOwnedSlice(self.allocator), .param_names = all_param_names[1..],
.param_name_spans = try param_name_spans.toOwnedSlice(self.allocator), .param_name_spans = all_param_name_spans[1..],
.param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator), .param_name_is_raw = all_param_name_is_raw[1..],
.return_type = return_type, .return_type = return_type,
.default_body = default_body, .default_body = default_body,
}); });

View File

@@ -2205,7 +2205,7 @@ test "sema: method-return slice + .ptr index + tagged-enum element" {
const source = const source =
"Event :: enum { none; click: i64; }" ++ "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]; }"; "go :: (p: *Plat) { evs := p.poll(); e := evs.ptr[0]; }";
var parser = parser_mod.Parser.init(alloc, source); var parser = parser_mod.Parser.init(alloc, source);
const root = try parser.parse(); const root = try parser.parse();