plum

#treesitter#compiler#wasm

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

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


4df1312Peter John 2026-08-09T18:38:33+05:30
feat(plum-core): lift nested type/enum methods into top-level Fn items
plum-core/src/parser.rs CHANGED
@@ -44,9 +44,19 @@ impl<'a> AstParser<'a> {
44
44
  match child.kind() {
45
45
  "module" => module = Some(self.parse_module(child)),
46
46
  "import" => imports.push(self.parse_import(child)),
47
+ "class" => {
47
- "class" => items.push(Item::Class(self.parse_class(child))),
48
+ let c = self.parse_class(child);
49
+ let nested = self.collect_nested_fns(child, &c.name);
50
+ items.push(Item::Class(c));
51
+ items.extend(nested.into_iter().map(Item::Fn));
52
+ }
48
53
  "trait" => items.push(Item::Trait(self.parse_trait(child))),
54
+ "enum" => {
49
- "enum" => items.push(Item::Enum(self.parse_enum(child))),
55
+ let e = self.parse_enum(child);
56
+ let nested = self.collect_nested_fns(child, &e.name);
57
+ items.push(Item::Enum(e));
58
+ items.extend(nested.into_iter().map(Item::Fn));
59
+ }
50
60
  "fn" => items.push(Item::Fn(self.parse_fn(child))),
51
61
  "const" => items.push(Item::Const(self.parse_const(child))),
52
62
  _ => {}
@@ -70,6 +80,23 @@ impl<'a> AstParser<'a> {
70
80
 
71
81
  // ---- class / trait / enum ---------------------------------------------
72
82
 
83
+ /// Collects any `fn` named children nested directly inside a class/enum body and
84
+ /// parses each as an ordinary top-level `Fn`, with `type_param` forced to `owner`
85
+ /// regardless of whatever the nested `fn` itself parsed (a nested method's receiver
86
+ /// is implicit from its enclosing declaration; if it also carries its own explicit,
87
+ /// redundant `<Receiver>` annotation, that's simply overridden, not treated as a
88
+ /// conflict/error).
89
+ fn collect_nested_fns(&self, node: Node, owner: &str) -> Vec<Fn> {
90
+ self.children_of_kind(node, "fn")
91
+ .into_iter()
92
+ .map(|n| {
93
+ let mut f = self.parse_fn(n);
94
+ f.type_param = Some(owner.to_string());
95
+ f
96
+ })
97
+ .collect()
98
+ }
99
+
73
100
  fn parse_class(&self, node: Node) -> Class {
74
101
  // class: "type" type_identifier generics? ("(" type_identifier,* ")")? "=" body
75
102
  // Named children in order: type_identifier (name), generics? (declaration), type_identifier* (implements), field*
plum-core/tests/parser_test.rs CHANGED
@@ -18,6 +18,67 @@ fn only_trait(source: &Source) -> &Trait {
18
18
  source.items.iter().find_map(|i| match i { Item::Trait(t) => Some(t), _ => None }).expect("expected a Trait item")
19
19
  }
20
20
 
21
+ #[test]
22
+ fn nested_class_methods_become_top_level_fn_items_with_type_param_set() {
23
+ let src = "\
24
+ type Cat =
25
+ name: Str
26
+
27
+ fun getName(self) -> Str =
28
+ self.name
29
+
30
+ fun setName(self, n: Str) =
31
+ todo
32
+ ";
33
+ let source = parse(src);
34
+ assert_eq!(source.items.len(), 3, "class + 2 nested methods, in order");
35
+ let Item::Class(class) = &source.items[0] else { panic!("expected a Class item first") };
36
+ assert_eq!(class.name, "Cat");
37
+ let Item::Fn(get_name) = &source.items[1] else { panic!("expected getName immediately after the class") };
38
+ assert_eq!(get_name.name, "getName");
39
+ assert_eq!(get_name.type_param, Some("Cat".to_string()));
40
+ let Item::Fn(set_name) = &source.items[2] else { panic!("expected setName immediately after getName") };
41
+ assert_eq!(set_name.name, "setName");
42
+ assert_eq!(set_name.type_param, Some("Cat".to_string()));
43
+ }
44
+
45
+ #[test]
46
+ fn nested_enum_method_becomes_top_level_fn_item_with_type_param_set() {
47
+ let src = "\
48
+ enum Step =
49
+ | ReadMin
50
+ | ReadMax
51
+
52
+ fun toNumber(self) -> Int =
53
+ match self
54
+ ReadMin => 0
55
+ ReadMax => 1
56
+ ";
57
+ let source = parse(src);
58
+ assert_eq!(source.items.len(), 2, "enum + 1 nested method, in order");
59
+ let Item::Enum(e) = &source.items[0] else { panic!("expected an Enum item first") };
60
+ assert_eq!(e.name, "Step");
61
+ let Item::Fn(to_number) = &source.items[1] else { panic!("expected toNumber immediately after the enum") };
62
+ assert_eq!(to_number.name, "toNumber");
63
+ assert_eq!(to_number.type_param, Some("Step".to_string()));
64
+ }
65
+
66
+ #[test]
67
+ fn class_and_enum_with_no_nested_methods_produce_no_extra_fn_items() {
68
+ let src = "\
69
+ type Dog =
70
+ name: Str
71
+
72
+ enum Bool =
73
+ | True
74
+ | False
75
+ ";
76
+ let source = parse(src);
77
+ assert_eq!(source.items.len(), 2, "no nested methods means no extra Item::Fn entries");
78
+ assert!(matches!(source.items[0], Item::Class(_)));
79
+ assert!(matches!(source.items[1], Item::Enum(_)));
80
+ }
81
+
21
82
  #[test]
22
83
  fn closure_literal_parses_with_params_and_body() {
23
84
  let src = "\