Skip to content

Commit 204adee

Browse files
committed
mut
1 parent 1f09543 commit 204adee

8 files changed

Lines changed: 594 additions & 110 deletions

File tree

README.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- **Functions**: First-class functions with support for closures, lambdas, and higher-order programming
1717
- **Enums and Match**: Generic enums with unit and single-payload variants, plus exhaustive enum-focused `match`
1818
- **Traits**: Compile-time traits with explicit `impl Trait for Type` declarations and generic bounds like `T: Writer + Flushable`
19+
- **Receiver Mutability**: Methods and trait requirements distinguish read-only `self` from mutating `mut self`
1920
- **Type Checking**: Ensures type correctness at parse-time with detailed error messages
2021
- **Type Inference**: Planned for a cleaner developer experience
2122
- **String Interpolation and Concatenation**: Upcoming for intuitive string operations
@@ -101,7 +102,7 @@ struct Point {
101102
x: int;
102103
y: int;
103104
104-
function set_x(self, x: int): void {
105+
function set_x(mut self, x: int): void {
105106
self.x = x;
106107
}
107108
@@ -117,6 +118,21 @@ function main(): void {
117118
}
118119
```
119120

121+
### Receiver Mutability
122+
```skunk
123+
struct Counter {
124+
value: int;
125+
126+
function bump(mut self): void {
127+
self.value = self.value + 1;
128+
}
129+
130+
function get(self): int {
131+
return self.value;
132+
}
133+
}
134+
```
135+
120136
### Arrays
121137
```skunk
122138
function main(): void {
@@ -149,7 +165,7 @@ Const V1 notes:
149165
- `*const T` and `[]const T` make the pointee or slice elements read-only
150166
- `[]T` is assignable to `[]const T`, but not the other way around
151167
- `const dst: []int` still allows `dst[i] = ...` because the binding is const, not the slice contents
152-
- method calls through const-qualified receivers are not supported yet
168+
- const-qualified receivers may call read-only `self` methods but not `mut self` methods
153169
- const struct fields are not supported yet
154170

155171
### Generic Enums and Match
@@ -179,22 +195,22 @@ function main(): void {
179195
### Traits and Bounds
180196
```skunk
181197
trait Writer {
182-
function write(self, value: int): int;
198+
function write(mut self, value: int): int;
183199
}
184200
185201
trait Resettable {
186-
function reset(self): void;
202+
function reset(mut self): void;
187203
}
188204
189205
struct Counter {
190206
value: int;
191207
192-
function write(self, value: int): int {
208+
function write(mut self, value: int): int {
193209
self.value = self.value + value;
194210
return self.value;
195211
}
196212
197-
function reset(self): void {
213+
function reset(mut self): void {
198214
self.value = 0;
199215
}
200216
}
@@ -211,6 +227,13 @@ Traits V1 notes:
211227
- traits are compile-time constraints only; there are no trait objects or runtime interface values yet
212228
- `impl Writer, Flushable for TextWriter {}` is sugar for multiple explicit impls
213229
- impl targets are concrete types only for now; generic impl targets are not supported yet
230+
- trait implementations must match receiver mutability, so `mut self` and `self` are distinct
231+
232+
Receiver mutability notes:
233+
- `self` means the method may read receiver state but may not mutate it
234+
- `mut self` means the method may mutate receiver state
235+
- `*const T` may call only `self` methods
236+
- mutable receivers may call both `self` and `mut self` methods
214237

215238
### Pointers and Allocators
216239
```skunk

src/ast.rs

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ pub enum Type {
254254
parameters: Vec<Type>,
255255
return_type: Box<Type>,
256256
},
257-
SkSelf, // special type for member functions
257+
MutSelf, // special type for mut member functions
258+
SkSelf, // special type for member functions
258259
}
259260

260261
enum TypePrefix {
@@ -1164,12 +1165,13 @@ impl PestImpl {
11641165
Rule::_self => {
11651166
let mut inner_pairs = pair.into_inner();
11661167
let mut self_type = Type::SkSelf;
1167-
if inner_pairs
1168-
.next()
1169-
.is_some_and(|pair| pair.as_rule() == Rule::const_kw)
1170-
{
1171-
self_type = Type::BindingConst {
1172-
inner: Box::new(self_type),
1168+
if let Some(qualifier) = inner_pairs.next() {
1169+
self_type = match qualifier.as_rule() {
1170+
Rule::mut_kw => Type::MutSelf,
1171+
Rule::const_kw => Type::BindingConst {
1172+
inner: Box::new(self_type),
1173+
},
1174+
other => panic!("unexpected self qualifier rule {:?}", other),
11731175
};
11741176
}
11751177
Vec::from([("self".to_string(), self_type)])
@@ -1355,6 +1357,7 @@ pub fn type_to_string(t: &Type) -> String {
13551357
.join(", "),
13561358
type_to_string(return_type)
13571359
),
1360+
Type::MutSelf => "mut self".to_string(),
13581361
Type::SkSelf => "self".to_string(),
13591362
Type::Custom(v) => v.to_string(),
13601363
}
@@ -1477,6 +1480,14 @@ pub fn is_const_view(t: &Type) -> bool {
14771480
matches!(t, Type::Const { .. })
14781481
}
14791482

1483+
pub fn is_self_type(t: &Type) -> bool {
1484+
matches!(unwrap_binding_const(t), Type::SkSelf | Type::MutSelf)
1485+
}
1486+
1487+
pub fn is_mut_self_type(t: &Type) -> bool {
1488+
matches!(unwrap_binding_const(t), Type::MutSelf)
1489+
}
1490+
14801491
pub fn unwrap_binding_const(t: &Type) -> &Type {
14811492
match t {
14821493
Type::BindingConst { inner } => inner,
@@ -2734,6 +2745,39 @@ mod tests {
27342745
println!("{:?}", parse(source_code))
27352746
}
27362747

2748+
#[test]
2749+
fn test_struct_member_function_with_mut_self() {
2750+
let source_code = r#"
2751+
struct Point {
2752+
function f(mut self, i:int) {
2753+
}
2754+
}
2755+
"#;
2756+
2757+
assert_eq!(
2758+
Node::Program {
2759+
statements: vec![
2760+
Node::StructDeclaration {
2761+
name: "Point".to_string(),
2762+
fields: vec![],
2763+
functions: vec![Node::FunctionDeclaration {
2764+
name: "f".to_string(),
2765+
parameters: vec![
2766+
("self".to_string(), Type::MutSelf),
2767+
("i".to_string(), Type::Int),
2768+
],
2769+
return_type: Type::Void,
2770+
body: vec![],
2771+
lambda: false,
2772+
}],
2773+
},
2774+
Node::EOI,
2775+
],
2776+
},
2777+
parse(source_code)
2778+
);
2779+
}
2780+
27372781
#[test]
27382782
fn test_struct_static_function() {
27392783
let source_code = r#"
@@ -3158,6 +3202,34 @@ mod tests {
31583202
assert_eq!(expected_ast, parse(source_code));
31593203
}
31603204

3205+
#[test]
3206+
fn test_trait_method_with_mut_self() {
3207+
let source_code = r#"
3208+
trait Writer {
3209+
function write(mut self, value: int): int;
3210+
}
3211+
"#;
3212+
3213+
let expected_ast = Node::Program {
3214+
statements: vec![
3215+
Node::TraitDeclaration {
3216+
name: "Writer".to_string(),
3217+
methods: vec![TraitMethodSignature {
3218+
name: "write".to_string(),
3219+
parameters: vec![
3220+
("self".to_string(), Type::MutSelf),
3221+
("value".to_string(), Type::Int),
3222+
],
3223+
return_type: Type::Int,
3224+
}],
3225+
},
3226+
Node::EOI,
3227+
],
3228+
};
3229+
3230+
assert_eq!(expected_ast, parse(source_code));
3231+
}
3232+
31613233
#[test]
31623234
fn test_generic_function_bounds() {
31633235
let source_code = r#"

src/compiler.rs

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3171,7 +3171,7 @@ pub fn compile_to_llvm_ir(program: &Node) -> Result<String, String> {
31713171
let mut parameter_types = Vec::new();
31723172
let mut compile_params = Vec::new();
31733173
for (param_name, param_type) in parameters {
3174-
if ast::unwrap_binding_const(param_type) == &Type::SkSelf {
3174+
if ast::is_self_type(param_type) {
31753175
continue;
31763176
}
31773177
parameter_types.push(llvm_type(param_type, &structs, &enums)?);
@@ -3685,7 +3685,7 @@ mod tests {
36853685
x: int;
36863686
y: int;
36873687
3688-
function set_x(self, x: int): void {
3688+
function set_x(mut self, x: int): void {
36893689
self.x = x;
36903690
}
36913691
@@ -3706,6 +3706,89 @@ mod tests {
37063706
assert_eq!(stdout, "12\n");
37073707
}
37083708

3709+
#[test]
3710+
fn runs_compiled_mut_self_method_program() {
3711+
let stdout = compile_and_run(
3712+
r#"
3713+
struct Counter {
3714+
value: int;
3715+
3716+
function bump(mut self): void {
3717+
self.value = self.value + 1;
3718+
}
3719+
3720+
function get(self): int {
3721+
return self.value;
3722+
}
3723+
}
3724+
3725+
function main(): void {
3726+
counter: Counter = Counter { value: 9 };
3727+
counter.bump();
3728+
print(counter.get());
3729+
}
3730+
"#,
3731+
)
3732+
.unwrap();
3733+
3734+
assert_eq!(stdout, "10\n");
3735+
}
3736+
3737+
#[test]
3738+
fn rejects_calling_mut_self_method_on_const_binding() {
3739+
let result = compile_and_run(
3740+
r#"
3741+
struct Counter {
3742+
value: int;
3743+
3744+
function bump(mut self): void {
3745+
self.value = self.value + 1;
3746+
}
3747+
}
3748+
3749+
function main(): void {
3750+
const counter: Counter = Counter { value: 0 };
3751+
counter.bump();
3752+
}
3753+
"#,
3754+
);
3755+
3756+
assert!(result.is_err());
3757+
assert!(result
3758+
.unwrap_err()
3759+
.contains("cannot call mutating method through const or immutable receiver"));
3760+
}
3761+
3762+
#[test]
3763+
fn runs_compiled_const_pointer_readonly_method_program() {
3764+
let stdout = compile_and_run(
3765+
r#"
3766+
struct Point {
3767+
x: int;
3768+
3769+
function get(self): int {
3770+
return self.x;
3771+
}
3772+
}
3773+
3774+
function print_point(point: *const Point): void {
3775+
print(point.get());
3776+
}
3777+
3778+
function main(): void {
3779+
heap: Allocator = System::allocator();
3780+
point: *Point = Point::create(heap);
3781+
point.x = 7;
3782+
print_point(point);
3783+
heap.destroy(point);
3784+
}
3785+
"#,
3786+
)
3787+
.unwrap();
3788+
3789+
assert_eq!(stdout, "7\n");
3790+
}
3791+
37093792
#[test]
37103793
fn runs_compiled_slice_program() {
37113794
let stdout = compile_and_run(
@@ -4260,22 +4343,22 @@ mod tests {
42604343
let stdout = compile_and_run(
42614344
r#"
42624345
trait Writer {
4263-
function write(self, value: int): int;
4346+
function write(mut self, value: int): int;
42644347
}
42654348
42664349
trait Resettable {
4267-
function reset(self): void;
4350+
function reset(mut self): void;
42684351
}
42694352
42704353
struct Counter {
42714354
value: int;
42724355
4273-
function write(self, value: int): int {
4356+
function write(mut self, value: int): int {
42744357
self.value = self.value + value;
42754358
return self.value;
42764359
}
42774360
4278-
function reset(self): void {
4361+
function reset(mut self): void {
42794362
self.value = 0;
42804363
}
42814364
}
@@ -4302,17 +4385,45 @@ mod tests {
43024385
}
43034386

43044387
#[test]
4305-
fn rejects_call_when_trait_bound_is_not_implemented() {
4388+
fn rejects_trait_impl_with_receiver_mutability_mismatch() {
43064389
let result = compile_and_run(
43074390
r#"
43084391
trait Writer {
4309-
function write(self, value: int): int;
4392+
function write(mut self, value: int): int;
43104393
}
43114394
43124395
struct Counter {
43134396
value: int;
43144397
43154398
function write(self, value: int): int {
4399+
return self.value + value;
4400+
}
4401+
}
4402+
4403+
impl Writer for Counter {}
4404+
4405+
function main(): void {}
4406+
"#,
4407+
);
4408+
4409+
assert!(result.is_err());
4410+
assert!(result
4411+
.unwrap_err()
4412+
.contains("trait method `Writer.write` expects"));
4413+
}
4414+
4415+
#[test]
4416+
fn rejects_call_when_trait_bound_is_not_implemented() {
4417+
let result = compile_and_run(
4418+
r#"
4419+
trait Writer {
4420+
function write(mut self, value: int): int;
4421+
}
4422+
4423+
struct Counter {
4424+
value: int;
4425+
4426+
function write(mut self, value: int): int {
43164427
self.value = self.value + value;
43174428
return self.value;
43184429
}

0 commit comments

Comments
 (0)