plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
2ccae10
— Peter John
2026-07-24T11:18:44+05:30
fix(plum-core): stop return type from being mistaken for method receiver
- plum-core/src/parser.rs +22 -6
- plum-core/tests/parser_test.rs +18 -0
plum-core/src/parser.rs
CHANGED
|
@@ -183,17 +183,30 @@ impl<'a> AstParser<'a> {
|
|
|
183
183
|
// ---- functions --------------------------------------------------------
|
|
184
184
|
|
|
185
185
|
fn parse_fn(&self, node: Node) -> Fn {
|
|
186
|
-
// fn: fn_identifier type? "(" param,* ")" ("->"
|
|
186
|
+
// fn: fn_identifier type? "(" param,* ")" ("->" type)? "=" body_or_expr
|
|
187
|
-
// Named children: fn_identifier, type?, param*,
|
|
187
|
+
// Named children: fn_identifier, type?, param*, type?, body/expr
|
|
188
188
|
let mut cursor = node.walk();
|
|
189
189
|
let named: Vec<Node> = node.named_children(&mut cursor).collect();
|
|
190
190
|
|
|
191
191
|
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
|
|
192
192
|
|
|
193
|
+
// The `<Cat>` receiver annotation and the function's `-> Type` return
|
|
194
|
+
// annotation both have kind "type" now, so they can't be told apart by
|
|
195
|
+
// kind alone. They're disambiguated by field instead: the grammar's
|
|
196
|
+
// `type` field (`field("type", optional(alias($.fn_type, $.type)))`)
|
|
197
|
+
// wraps only the receiver's own alias node, so `child_by_field_name`
|
|
198
|
+
// resolves it unambiguously.
|
|
199
|
+
//
|
|
200
|
+
// Note: the grammar's `returns` field, by contrast, wraps the whole
|
|
201
|
+
// `optional(seq("->", $.type))` — so `child_by_field_name("returns")`
|
|
202
|
+
// resolves to the anonymous `"->"` token (the first element of that
|
|
203
|
+
// seq), not the `type` node, and can't be used to find the return
|
|
204
|
+
// type here. Instead the return type is derived as "whichever `type`
|
|
205
|
+
// named child isn't the receiver".
|
|
206
|
+
let receiver_node = node.child_by_field_name("type");
|
|
207
|
+
|
|
193
208
|
// type param — the `<Cat>` node has kind "type" and contains a type_identifier
|
|
194
|
-
let type_param =
|
|
209
|
+
let type_param = receiver_node
|
|
195
|
-
.iter()
|
|
196
|
-
.find(|n| n.kind() == "type")
|
|
197
210
|
.and_then(|t| t.named_child(0))
|
|
198
211
|
.map(|n| self.text(n));
|
|
199
212
|
|
|
@@ -203,7 +216,10 @@ impl<'a> AstParser<'a> {
|
|
|
203
216
|
.map(|n| self.parse_param(*n))
|
|
204
217
|
.collect();
|
|
205
218
|
|
|
219
|
+
let returns = named
|
|
220
|
+
.iter()
|
|
206
|
-
|
|
221
|
+
.find(|n| n.kind() == "type" && Some(**n) != receiver_node)
|
|
222
|
+
.map(|n| self.parse_type(*n));
|
|
207
223
|
|
|
208
224
|
// body is the last named child — it is either a `body` node (block)
|
|
209
225
|
// or an expression node when the body is a single expression. The
|
plum-core/tests/parser_test.rs
CHANGED
|
@@ -47,6 +47,24 @@ useClosure() -> Bool =
|
|
|
47
47
|
assert!(closure.params.is_empty());
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
#[test]
|
|
51
|
+
fn fn_without_receiver_but_with_return_type_has_no_type_param() {
|
|
52
|
+
let src = "main() -> Int =\n 0\n";
|
|
53
|
+
let source = parse(src);
|
|
54
|
+
let f = only_fn(&source);
|
|
55
|
+
assert_eq!(f.type_param, None, "return type must not be mistaken for a method receiver");
|
|
56
|
+
assert_eq!(f.returns.as_ref().map(|t| t.name.clone()), Some("Int".to_string()));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[test]
|
|
60
|
+
fn fn_with_receiver_and_return_type_has_correct_type_param() {
|
|
61
|
+
let src = "toStr<Cat>() -> Str =\n \"x\"\n";
|
|
62
|
+
let source = parse(src);
|
|
63
|
+
let f = only_fn(&source);
|
|
64
|
+
assert_eq!(f.type_param, Some("Cat".to_string()));
|
|
65
|
+
assert_eq!(f.returns.as_ref().map(|t| t.name.clone()), Some("Str".to_string()));
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
#[test]
|
|
51
69
|
fn fn_value_type_param_parses_with_positional_types_and_return() {
|
|
52
70
|
let src = "each(cb: fn(Int) -> Bool) -> Bool =\n True\n";
|