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:
@@ -7,7 +7,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Show :: protocol {
|
||||
show :: () -> string;
|
||||
show :: (self: *Self) -> string;
|
||||
}
|
||||
A :: struct { x: i64; }
|
||||
B :: struct { s: string; }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyProtocol :: protocol {
|
||||
get_value :: () -> i64;
|
||||
get_value :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
MyImpl :: struct { value: i64; }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Sizable :: protocol {
|
||||
size :: () -> i64;
|
||||
size :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
Widget :: struct { value: i64; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Sizable :: protocol {
|
||||
size :: () -> i64;
|
||||
size :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
Leaf :: struct { value: i64; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Sizable :: protocol {
|
||||
size :: () -> i64;
|
||||
size :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
Leaf :: struct { value: i64; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Fmt :: enum { a; b; }
|
||||
|
||||
Proto :: protocol {
|
||||
take_fmt :: (f: Fmt);
|
||||
take_fmt :: (self: *Self, f: Fmt);
|
||||
}
|
||||
|
||||
Impl :: struct {}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Proto :: protocol {
|
||||
get :: () -> *u8;
|
||||
get :: (self: *Self) -> *u8;
|
||||
}
|
||||
|
||||
Impl :: struct {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
GPU :: protocol {
|
||||
ping :: () -> i64;
|
||||
ping :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
Impl :: struct {}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
47
examples/0418-protocols-explicit-receiver.sx
Normal file
47
examples/0418-protocols-explicit-receiver.sx
Normal file
@@ -0,0 +1,47 @@
|
||||
// Protocol methods declare their receiver EXPLICITLY as the first parameter —
|
||||
// `self: *Self` (or `self: Self`) — matching the `impl` method signature. This
|
||||
// is required (the old implicit-receiver form is a parse error). The receiver
|
||||
// annotation is validated then erased, so dispatch and call sites are unchanged:
|
||||
// a method with N declared extra args is still called with N args.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Size :: struct { w: i32; h: i32; }
|
||||
|
||||
// `self: *Self` receiver; one no-arg method and one with extra args.
|
||||
Measurable :: protocol #inline {
|
||||
measure :: (self: *Self) -> Size;
|
||||
scaled :: (self: *Self, factor: i32) -> Size;
|
||||
}
|
||||
|
||||
// `self: Self` (by-value) receiver is also accepted.
|
||||
Named :: protocol {
|
||||
name :: (self: Self) -> string;
|
||||
}
|
||||
|
||||
Widget :: struct { base: i32; }
|
||||
|
||||
impl Measurable for Widget {
|
||||
measure :: (self: *Widget) -> Size { return Size.{ w = self.base, h = self.base * 2 }; }
|
||||
scaled :: (self: *Widget, factor: i32) -> Size {
|
||||
return Size.{ w = self.base * factor, h = self.base * 2 * factor };
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for Widget {
|
||||
name :: (self: Widget) -> string { return "widget"; }
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
w : Widget = .{ base = 5 };
|
||||
p : *Widget = @w;
|
||||
|
||||
m : Measurable = xx p;
|
||||
s := m.measure(); // 0 extra args
|
||||
s2 := m.scaled(3); // 1 extra arg
|
||||
print("measure={}x{}\n", s.w, s.h); // 5x10
|
||||
print("scaled={}x{}\n", s2.w, s2.h); // 15x30
|
||||
|
||||
n : Named = xx p;
|
||||
print("name={}\n", n.name()); // widget
|
||||
return 0;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Greeter :: protocol {
|
||||
greet :: () -> i64;
|
||||
greet :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
Dog :: struct { age: i64; }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> T;
|
||||
get :: (self: *Self) -> T;
|
||||
}
|
||||
|
||||
IntCell :: struct { v: i64; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> T;
|
||||
get :: (self: *Self) -> T;
|
||||
}
|
||||
|
||||
IntCell :: struct { v: i64; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> T;
|
||||
get :: (self: *Self) -> T;
|
||||
}
|
||||
|
||||
IntCell :: struct { v: i64; }
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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) } }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
Box :: struct { m: i32; }
|
||||
|
||||
Provider :: protocol {
|
||||
get :: () -> Box;
|
||||
get :: (self: *Self) -> Box;
|
||||
}
|
||||
|
||||
Holder :: struct { val: i32 = 7; }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#import "0821-protocols-same-name-method-ambiguous/b.sx";
|
||||
|
||||
Provider :: protocol {
|
||||
get :: () -> Box;
|
||||
get :: (self: *Self) -> Box;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx";
|
||||
|
||||
Provider :: protocol {
|
||||
getp :: () -> *Box;
|
||||
getp :: (self: *Self) -> *Box;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,7 +10,7 @@ ProposedSize :: struct {
|
||||
}
|
||||
|
||||
Sizable :: protocol {
|
||||
size :: (proposal: ProposedSize) -> f32;
|
||||
size :: (self: *Self, proposal: ProposedSize) -> f32;
|
||||
}
|
||||
|
||||
Widget :: struct {}
|
||||
|
||||
@@ -16,7 +16,7 @@ direct_size :: (proposal: ProposedSize) -> f32 {
|
||||
}
|
||||
|
||||
Sizable :: protocol {
|
||||
size :: (proposal: ProposedSize) -> f32;
|
||||
size :: (self: *Self, proposal: ProposedSize) -> f32;
|
||||
}
|
||||
|
||||
Widget :: struct {}
|
||||
|
||||
@@ -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) } }
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
12
examples/1190-diagnostics-protocol-missing-receiver.sx
Normal file
12
examples/1190-diagnostics-protocol-missing-receiver.sx
Normal 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;
|
||||
}
|
||||
1
examples/expected/0418-protocols-explicit-receiver.exit
Normal file
1
examples/expected/0418-protocols-explicit-receiver.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
measure=5x10
|
||||
scaled=15x30
|
||||
name=widget
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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
|
||||
| ^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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)
|
||||
|
||||
12
readme.md
12
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);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
16
specs.md
16
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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user