plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


2b8194cPeter 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 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 type? "(" param,* ")" ("->" type)? "=" body_or_expr
246
+ // fn: "fun" fn_identifier "(" param,* ")" ("->" type)? "=" body_or_expr
247
- // Named children: fn_identifier, type?, param*, type?, body/expr
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" && Some(**n) != receiver_node)
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. The
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<Int>(a: Int, b: Int) -> Int =\n a+b\n";
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<Cat>(self) -> Int =
112
+ fun length(self) -> Int =
113
- self.age
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<Box>(self) -> Int =
123
+ fun length(self) -> Int =
124
- self.items
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 fn_with_receiver_and_return_type_has_correct_type_param() {
145
+ fn nested_method_with_return_type_has_correct_type_param() {
146
- let src = "fun toStr<Cat>() -> Str =\n \"x\"\n";
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()));