plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
4df1312
— Peter 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 +29 -2
- plum-core/tests/parser_test.rs +61 -0
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
|
-
|
|
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
|
-
|
|
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 = "\
|