plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
2b8194c
— Peter John
2026-08-09T21:03:00+05:30
refactor(plum-core): drop dead receiver-vs-return-type disambiguation from parse_fn
- plum-core/src/parser.rs +14 -31
- plum-core/tests/formatter_test.rs +1 -1
- plum-core/tests/loader_test.rs +4 -4
- plum-core/tests/parser_test.rs +2 -2
plum-core/src/parser.rs
CHANGED
|
@@ -243,52 +243,35 @@ impl<'a> AstParser<'a> {
|
|
|
243
243
|
// ---- functions --------------------------------------------------------
|
|
244
244
|
|
|
245
245
|
fn parse_fn(&self, node: Node) -> Fn {
|
|
246
|
-
// fn: fn_identifier
|
|
246
|
+
// fn: "fun" fn_identifier "(" param,* ")" ("->" type)? "=" body_or_expr
|
|
247
|
-
// Named children: fn_identifier,
|
|
247
|
+
// Named children: fn_identifier, param*, type?, body/expr
|
|
248
|
+
//
|
|
249
|
+
// `type_param` (the method's receiver, e.g. `Cat` in a method nested inside
|
|
250
|
+
// `type Cat = ...`) is never set here — a bare `fn` node has no receiver of
|
|
251
|
+
// its own; `parse_source`'s `collect_nested_fns` forces it afterward for any
|
|
252
|
+
// `fn` nested inside a `class`/`enum` body. There is no top-level
|
|
253
|
+
// `<Receiver>` annotation syntax to parse.
|
|
248
254
|
let mut cursor = node.walk();
|
|
249
255
|
let named: Vec<Node> = node.named_children(&mut cursor).collect();
|
|
250
256
|
|
|
251
257
|
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
|
|
252
258
|
|
|
253
|
-
// The `<Cat>` receiver annotation and the function's `-> Type` return
|
|
254
|
-
// annotation both have kind "type" now, so they can't be told apart by
|
|
255
|
-
// kind alone. They're disambiguated by field instead: the grammar's
|
|
256
|
-
// `type` field (`field("type", optional(alias($.fn_type, $.type)))`)
|
|
257
|
-
// wraps only the receiver's own alias node, so `child_by_field_name`
|
|
258
|
-
// resolves it unambiguously.
|
|
259
|
-
//
|
|
260
|
-
// Note: the grammar's `returns` field, by contrast, wraps the whole
|
|
261
|
-
// `optional(seq("->", $.type))` — so `child_by_field_name("returns")`
|
|
262
|
-
// resolves to the anonymous `"->"` token (the first element of that
|
|
263
|
-
// seq), not the `type` node, and can't be used to find the return
|
|
264
|
-
// type here. Instead the return type is derived as "whichever `type`
|
|
265
|
-
// named child isn't the receiver".
|
|
266
|
-
let receiver_node = node.child_by_field_name("type");
|
|
267
|
-
|
|
268
|
-
// type param — the `<Cat>` node has kind "type" and contains a type_identifier
|
|
269
|
-
let type_param = receiver_node
|
|
270
|
-
.and_then(|t| t.named_child(0))
|
|
271
|
-
.map(|n| self.text(n));
|
|
272
|
-
|
|
273
259
|
let params: Vec<Param> = named
|
|
274
260
|
.iter()
|
|
275
261
|
.filter(|n| n.kind() == "param")
|
|
276
262
|
.map(|n| self.parse_param(*n))
|
|
277
263
|
.collect();
|
|
278
264
|
|
|
265
|
+
// The grammar's `returns` field wraps the whole `optional(seq("->", $.type))`,
|
|
266
|
+
// so `child_by_field_name("returns")` resolves to the anonymous `"->"` token,
|
|
267
|
+
// not the `type` node — find the `type`-kind named child instead.
|
|
279
268
|
let returns = named
|
|
280
269
|
.iter()
|
|
281
|
-
.find(|n| n.kind() == "type"
|
|
270
|
+
.find(|n| n.kind() == "type")
|
|
282
271
|
.map(|n| self.parse_type(*n));
|
|
283
272
|
|
|
284
273
|
// body is the last named child — it is either a `body` node (block)
|
|
285
|
-
// or an expression node when the body is a single expression.
|
|
274
|
+
// or an expression node when the body is a single expression.
|
|
286
|
-
// `<Cat>` receiver annotation and the `returns` type both have kind
|
|
287
|
-
// "type" now (return_type no longer exists as a separate node kind),
|
|
288
|
-
// but that's fine: neither can ever be the LAST named child when a
|
|
289
|
-
// body is present, since `body`/the trailing expression always comes
|
|
290
|
-
// after them in the grammar — so this match doesn't need to
|
|
291
|
-
// distinguish the two "type" cases from each other, only from `body`.
|
|
292
275
|
let body = named.last().and_then(|last| {
|
|
293
276
|
match last.kind() {
|
|
294
277
|
// Skip non-body trailing nodes
|
|
@@ -301,7 +284,7 @@ impl<'a> AstParser<'a> {
|
|
|
301
284
|
}
|
|
302
285
|
}).unwrap_or(FnBody::Block(Block { stmts: vec![] }));
|
|
303
286
|
|
|
304
|
-
Fn { name, type_param, params, returns, body }
|
|
287
|
+
Fn { name, type_param: None, params, returns, body }
|
|
305
288
|
}
|
|
306
289
|
|
|
307
290
|
fn parse_const(&self, node: Node) -> Const {
|
plum-core/tests/formatter_test.rs
CHANGED
|
@@ -10,7 +10,7 @@ fn formats_simple_function() {
|
|
|
10
10
|
|
|
11
11
|
#[test]
|
|
12
12
|
fn formats_binary_operator_spacing() {
|
|
13
|
-
let input = "fun add
|
|
13
|
+
let input = "fun add(a: Int, b: Int) -> Int =\n a+b\n";
|
|
14
14
|
let result = format_source(input).expect("format_source should succeed");
|
|
15
15
|
assert!(result.contains("a + b"));
|
|
16
16
|
}
|
plum-core/tests/loader_test.rs
CHANGED
|
@@ -109,8 +109,8 @@ module fixtures
|
|
|
109
109
|
type Cat =
|
|
110
110
|
age: Int
|
|
111
111
|
|
|
112
|
-
fun length
|
|
112
|
+
fun length(self) -> Int =
|
|
113
|
-
|
|
113
|
+
self.age
|
|
114
114
|
");
|
|
115
115
|
let entry = write_lib_file(&lib_path, "main", "\
|
|
116
116
|
module fixtures
|
|
@@ -120,8 +120,8 @@ import cat
|
|
|
120
120
|
type Box =
|
|
121
121
|
items: Int
|
|
122
122
|
|
|
123
|
-
fun length
|
|
123
|
+
fun length(self) -> Int =
|
|
124
|
-
|
|
124
|
+
self.items
|
|
125
125
|
|
|
126
126
|
fun main() -> Int =
|
|
127
127
|
0
|
plum-core/tests/parser_test.rs
CHANGED
|
@@ -142,8 +142,8 @@ fn fn_without_receiver_but_with_return_type_has_no_type_param() {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
#[test]
|
|
145
|
-
fn
|
|
145
|
+
fn nested_method_with_return_type_has_correct_type_param() {
|
|
146
|
-
let src = "fun toStr
|
|
146
|
+
let src = "type Cat =\n name: Str\n\n fun toStr() -> Str =\n \"x\"\n";
|
|
147
147
|
let source = parse(src);
|
|
148
148
|
let f = only_fn(&source);
|
|
149
149
|
assert_eq!(f.type_param, Some("Cat".to_string()));
|