plum

#treesitter#compiler#wasm

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

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


d849deb — Peter John 2026-11-01T23:07:45+05:30
test: dedupe core/cli Rust tests (144 -> 106), table-driven formatter/loader cases
plum-cli/tests/test_scope_test.rs CHANGED
@@ -30,16 +30,6 @@ fn scratch(name: &str) -> std::path::PathBuf {
30
30
 
31
31
  const TWO: &str = "import std/Str\nimport std/Int\n\ntest \"one\"\n assert 1 == 1\n\ntest \"two\"\n assert 2 == 2\n";
32
32
 
33
- #[test]
34
- fn fileRunsOnlyItsOwnTests() {
35
- let dir = scratch("file");
36
- let f = dir.join("two.plum");
37
- std::fs::write(&f, TWO).unwrap();
38
- let (out, ok) = runPlum(&[f.to_str().unwrap()]);
39
- assert!(ok, "{out}");
40
- assert!(out.contains("2 passed, 0 failed"), "{out}");
41
- }
42
-
43
33
  #[test]
44
34
  fn directoryRunsSumOfOwnTests() {
45
35
  let dir = scratch("dir");
plum-core/tests/formatter_test.rs CHANGED
@@ -1,21 +1,6 @@
1
1
  #![allow(non_snake_case)]
2
2
  use plum_core::formatSource;
3
3
 
4
- #[test]
5
- fn formatsSimpleFunction() {
6
- let input = "fun main() =\n x = 1\n";
7
- let result = formatSource(input).expect("formatSource should succeed");
8
- assert!(result.contains("main()"));
9
- assert!(result.contains("x ="));
10
- }
11
-
12
- #[test]
13
- fn formatsBinaryOperatorSpacing() {
14
- let input = "fun add(a: Int, b: Int) -> Int =\n a+b\n";
15
- let result = formatSource(input).expect("formatSource should succeed");
16
- assert!(result.contains("a + b"));
17
- }
18
-
19
4
  #[test]
20
5
  fn rejectsSyntaxError() {
21
6
  let input = "@@@@invalid@@@@\n";
@@ -23,14 +8,6 @@ fn rejectsSyntaxError() {
23
8
  assert!(result.is_err());
24
9
  }
25
10
 
26
- #[test]
27
- fn idempotentOnAlreadyFormatted() {
28
- let input = "fun main() =\n x = 1 + 2\n";
29
- let first = formatSource(input).expect("first pass");
30
- let second = formatSource(&first).expect("second pass");
31
- assert_eq!(first, second, "formatting should be idempotent");
32
- }
33
-
34
11
  /// Regression test for a real bug: Topiary inserts NO whitespace between
35
12
  /// adjacent leaves unless a query says to, so keywords with no explicit
36
13
  /// spacing rule glue to the next token ("module" + "test" -> "moduletest").
@@ -266,29 +243,28 @@ fn formatsZigStyleErrors() {
266
243
  assert_eq!(formatSource(&h).expect("second"), h, "idempotent");
267
244
  }
268
245
 
269
- fn assertFormatStable(input: &str) {
270
- let once = formatSource(input).expect("first pass");
271
- assert_eq!(once, input);
272
- let twice = formatSource(&once).expect("second pass");
273
- assert_eq!(twice, once);
274
- }
275
-
276
246
  #[test]
277
- fn formatStructWithBody() {
247
+ fn alreadyFormattedSourceIsUnchanged() {
278
- assertFormatStable(
248
+ for input in [
249
+ "fun main() =\n x = 1 + 2\n",
279
250
  "struct Vec2(x: Float, y: Float) =\n ZERO = Vec2(x: 0.0, y: 0.0)\n\n fun len(self) -> Float =\n self.x\n",
251
+ "struct Vec2(x: Float, y: Float)\n\nfun f() -> Int =\n 1\n",
252
+ "record Pair[K, V](key: K, val: V)\n",
253
+ "record Pair[K, V](key: K, val: V) =\n fun first(self) -> K =\n self.key\n",
280
- );
254
+ ] {
255
+ let once = formatSource(input).expect("first pass");
256
+ assert_eq!(once, input);
257
+ let twice = formatSource(&once).expect("second pass");
258
+ assert_eq!(twice, once);
281
- }
259
+ }
282
-
283
- #[test]
284
- fn formatStructNoBody() {
285
- assertFormatStable("struct Vec2(x: Float, y: Float)\n\nfun f() -> Int =\n 1\n");
286
260
  }
287
261
 
288
262
  #[test]
289
- fn formatRecordGeneric() {
263
+ fn spacesOperatorsAndSimpleFunctions() {
264
+ let r = formatSource("fun add(a: Int, b: Int) -> Int =\n a+b\n").expect("format");
290
- assertFormatStable("record Pair[K, V](key: K, val: V)\n");
265
+ assert!(r.contains("a + b"), "{r}");
266
+ let r = formatSource("fun main() =\n x = 1\n").expect("format");
291
- assertFormatStable("record Pair[K, V](key: K, val: V) =\n fun first(self) -> K =\n self.key\n");
267
+ assert!(r.contains("main()") && r.contains("x ="), "{r}");
292
268
  }
293
269
 
294
270
  #[test]
plum-core/tests/loader_test.rs CHANGED
@@ -252,21 +252,19 @@ fun main(u: Unit) -> Unit =
252
252
  }
253
253
 
254
254
  #[test]
255
- fn double_question_rejected() {
255
+ fn removedSyntaxIsAParseError() {
256
+ for src in [
257
+ "fun f(u: Unit) -> Unit??\n u\n",
258
+ "fun f(u: Unit) -> Unit? ! [X] =\n u\n",
259
+ "PI = 3\n",
260
+ "extend Foo =\n fun f() -> Unit =\n todo\n",
261
+ ] {
256
- let dir = TempDir::new().unwrap();
262
+ let dir = TempDir::new().unwrap();
257
- let entry = dir.path().join("main.plum");
263
+ let entry = dir.path().join("main.plum");
258
- fs::write(&entry, "fun f(u: Unit) -> Unit??\n u\n").unwrap();
264
+ fs::write(&entry, src).unwrap();
259
- let err = loadAndMerge(&entry, dir.path()).unwrap_err();
265
+ let err = loadAndMerge(&entry, dir.path()).unwrap_err();
260
- assert!(err.contains("parse error"), "{err}");
266
+ assert!(err.contains("parse error"), "{src}: {err}");
261
- }
267
+ }
262
-
263
- #[test]
264
- fn nullable_bang_rejected() {
265
- let dir = TempDir::new().unwrap();
266
- let entry = dir.path().join("main.plum");
267
- fs::write(&entry, "fun f(u: Unit) -> Unit? ! [X] =\n u\n").unwrap();
268
- let err = loadAndMerge(&entry, dir.path()).unwrap_err();
269
- assert!(err.contains("parse error"), "{err}");
270
268
  }
271
269
 
272
270
  #[test]
@@ -304,24 +302,6 @@ fn assoc_const_names_are_per_type_across_modules() {
304
302
  assert!(names.contains(&"Alpha.X".to_string()) && names.contains(&"Beta.X".to_string()), "{names:?}");
305
303
  }
306
304
 
307
- #[test]
308
- fn toplevel_const_removed() {
309
- let dir = TempDir::new().unwrap();
310
- let entry = dir.path().join("main.plum");
311
- fs::write(&entry, "PI = 3\n").unwrap();
312
- let err = loadAndMerge(&entry, dir.path()).unwrap_err();
313
- assert!(err.contains("parse error"), "{err}");
314
- }
315
-
316
- #[test]
317
- fn extend_removed() {
318
- let dir = TempDir::new().unwrap();
319
- let entry = dir.path().join("main.plum");
320
- fs::write(&entry, "extend Foo =\n fun f() -> Unit =\n todo\n").unwrap();
321
- let err = loadAndMerge(&entry, dir.path()).unwrap_err();
322
- assert!(err.contains("parse error"), "{err}");
323
- }
324
-
325
305
  #[test]
326
306
  fn primitive_builtin_std_only() {
327
307
  // A built-in's API can only be declared in std.
plum-core/tests/parser_test.rs CHANGED
@@ -85,31 +85,6 @@ enum Cat =
85
85
  assert_eq!(set_name.type_param, Some("Cat".to_string()));
86
86
  }
87
87
 
88
- #[test]
89
- fn nestedEnumMethodBecomesTopLevelFnItemWithTypeParamSet() {
90
- let src = "\
91
- enum Step =
92
- | ReadMin
93
- | ReadMax
94
-
95
- fun toNumber(self) -> Int =
96
- match self
97
- ReadMin => 0
98
- ReadMax => 1
99
- ";
100
- let source = parse(src);
101
- assert_eq!(source.items.len(), 2, "enum + 1 nested method, in order");
102
- let Item::Enum(e) = &source.items[0] else {
103
- panic!("expected an Enum item first")
104
- };
105
- assert_eq!(e.name, "Step");
106
- let Item::Fn(to_number) = &source.items[1] else {
107
- panic!("expected toNumber immediately after the enum")
108
- };
109
- assert_eq!(to_number.name, "toNumber");
110
- assert_eq!(to_number.type_param, Some("Step".to_string()));
111
- }
112
-
113
88
  #[test]
114
89
  fn enumDiscriminantValuesParseIntoParamsAndVariantValues() {
115
90
  let src = "\
@@ -135,26 +110,6 @@ enum Step(n: Int) =
135
110
  assert_eq!(e.variants[1].values, vec![Expr::Int(1)]);
136
111
  }
137
112
 
138
- #[test]
139
- fn classAndEnumWithNoNestedMethodsProduceNoExtraFnItems() {
140
- let src = "\
141
- enum Dog =
142
- | Dog(name: Str)
143
-
144
- enum Flag =
145
- | On
146
- | Off
147
- ";
148
- let source = parse(src);
149
- assert_eq!(
150
- source.items.len(),
151
- 2,
152
- "no nested methods means no extra Item::Fn entries"
153
- );
154
- assert!(matches!(source.items[0], Item::Enum(_)));
155
- assert!(matches!(source.items[1], Item::Enum(_)));
156
- }
157
-
158
113
  #[test]
159
114
  fn closureLiteralParsesWithParamsAndBody() {
160
115
  let src = "\
@@ -178,28 +133,6 @@ fun useClosure() -> Bool =
178
133
  assert_eq!(closure.body.stmts.len(), 1);
179
134
  }
180
135
 
181
- #[test]
182
- fn closureLiteralParsesWithNoParams() {
183
- let src = "\
184
- fun useClosure() -> Bool =
185
- cb = ||
186
- true
187
- cb()
188
- ";
189
- let source = parse(src);
190
- let f = onlyFn(&source);
191
- let FnBody::Block(block) = &f.body else {
192
- panic!("expected a block body")
193
- };
194
- let Stmt::Assign(assign) = &block.stmts[0] else {
195
- panic!("expected an assign statement")
196
- };
197
- let Expr::Closure(closure) = &assign.values[0] else {
198
- panic!("expected a closure expression")
199
- };
200
- assert!(closure.params.is_empty());
201
- }
202
-
203
136
  #[test]
204
137
  fn fnWithoutReceiverButWithReturnTypeHasNoTypeParam() {
205
138
  let src = "fun main() -> Int =\n 0\n";
@@ -215,18 +148,6 @@ fn fnWithoutReceiverButWithReturnTypeHasNoTypeParam() {
215
148
  );
216
149
  }
217
150
 
218
- #[test]
219
- fn nestedMethodWithReturnTypeHasCorrectTypeParam() {
220
- let src = "enum Cat =\n | Cat(name: Str)\n\n fun toStr() -> Str =\n \"x\"\n";
221
- let source = parse(src);
222
- let f = onlyFn(&source);
223
- assert_eq!(f.type_param, Some("Cat".to_string()));
224
- assert_eq!(
225
- f.returns.as_ref().map(|t| t.name.clone()),
226
- Some("Str".to_string())
227
- );
228
- }
229
-
230
151
  #[test]
231
152
  fn fnValueTypeParamParsesWithPositionalTypesAndReturn() {
232
153
  let src = "fun each(cb: fn(Int) -> Bool) -> Bool =\n true\n";
@@ -266,18 +187,6 @@ fn bound_syntax_rejected() {
266
187
  assert!(hasParseError("fun f[T: Show](x: T) -> T =\n x\n"));
267
188
  }
268
189
 
269
- #[test]
270
- fn fnValueTypeParamParsesWithNoReturn() {
271
- let src = "fun each(cb: fn(Int)) -> Bool =\n true\n";
272
- let source = parse(src);
273
- let f = onlyFn(&source);
274
- let ParamType::Fn(param_types, ret) = &f.params[0].ty else {
275
- panic!("expected ParamType::Fn")
276
- };
277
- assert_eq!(param_types.len(), 1);
278
- assert!(ret.is_none());
279
- }
280
-
281
190
  // ---- errors: `!T`, `T?`, postfix `?`, `crash` ----
282
191
 
283
192
  fn parseHasError(src: &str) -> bool {
@@ -306,29 +215,11 @@ fn test_tag_union_types_are_syntax_errors() {
306
215
  assert!(parseHasError("fun f() -> Int ! [A, B] = 1\n"));
307
216
  }
308
217
 
309
- #[test]
310
- fn test_slice_type_still_parses() {
311
- let src = parse("fun f(x: []Byte) -> Int = 1\n");
312
- match &onlyFn(&src).params[0].ty {
313
- ParamType::Type(t) => assert_eq!(t.name, "[]Byte"),
314
- _ => panic!(),
315
- }
316
- }
317
-
318
218
  #[test]
319
219
  fn test_double_question_expression_is_a_syntax_error() {
320
220
  assert!(parseHasError("fun f(a: Int, b: Int) -> Int = a ?? b\n"));
321
221
  }
322
222
 
323
- #[test]
324
- fn elvis_parses_to_an_elvis_catch() {
325
- let src = parse("fun f(a: Int?, b: Int) -> Int = a ?: b\n");
326
- match bodyExpr(onlyFn(&src)) {
327
- Expr::Catch(c) => assert!(c.elvis),
328
- other => panic!("got {:?}", other),
329
- }
330
- }
331
-
332
223
  #[test]
333
224
  fn elvis_is_right_associative() {
334
225
  let src = parse("fun f(a: Int?, b: Int?) -> Int = a ?: b ?: 1\n");
@@ -379,12 +270,6 @@ fn int_min_literal() {
379
270
  assert!(matches!(bodyExpr(onlyFn(&src)), Expr::Int(i64::MIN)));
380
271
  }
381
272
 
382
- #[test]
383
- fn inline_if_expression() {
384
- let src = parse("fun f(c: Bool) -> Int = if c then 1 else 2\n");
385
- assert!(matches!(bodyExpr(onlyFn(&src)), Expr::InlineIf(_)));
386
- }
387
-
388
273
  #[test]
389
274
  fn inline_if_chain() {
390
275
  let src = parse("fun f(a: Bool, b: Bool) -> Int = if a then 1 else if b then 2 else 3\n");
@@ -410,24 +295,6 @@ fn question_binds_tight() {
410
295
  assert!(matches!(bodyExpr(g), Expr::Binary(_)), "got {:?}", bodyExpr(g));
411
296
  }
412
297
 
413
- #[test]
414
- fn test_try_plain() {
415
- let src = parse("fun f() -> Int = try g()\n");
416
- assert!(matches!(bodyExpr(onlyFn(&src)), Expr::Try(_)));
417
- }
418
-
419
- #[test]
420
- fn test_crash_stmt() {
421
- let src = parse("fun f() -> Int =\n crash \"boom\"\n");
422
- match &onlyFn(&src).body {
423
- FnBody::Block(b) => match &b.stmts[0] {
424
- Stmt::Crash(Expr::String(_)) => {}
425
- other => panic!("expected Crash(String), got {other:?}"),
426
- },
427
- _ => panic!(),
428
- }
429
- }
430
-
431
298
  fn firstExprDebug(src: &str) -> String {
432
299
  let s = parse(src);
433
300
  format!("{:?}", onlyFn(&s).body)
@@ -656,12 +523,6 @@ fn catch_error_value_and_block_forms() {
656
523
  assert!(matches!(&c.handler, CatchHandler::Default(Expr::Var(_))));
657
524
  }
658
525
 
659
- #[test]
660
- fn return_error_statement() {
661
- let s = parse("fun f() -> !Int =\n if x\n return error.Bad\n 1\n");
662
- assert!(matches!(bodyExpr(onlyFn(&s)), Expr::Int(1)));
663
- }
664
-
665
526
  #[test]
666
527
  fn pattern_if_conditions_parse() {
667
528
  let src = parse("enum Sh =\n | Circle(r: Int)\n | Rect(w: Int, h: Int)\n\nfun f(s: Sh) -> Int =\n if Circle(r) := s\n return r\n else if Rect(w, _) := s, w > 1, w < 9\n return w\n 0\n");
@@ -720,15 +581,6 @@ fn pairArgDesugarsToPairCall() {
720
581
  }
721
582
  }
722
583
 
723
- #[test]
724
- fn pairArgMixedWithPositional() {
725
- let args = firstCallArgs("fun main() =\n x := f(1, \"a\" => 2)\n");
726
- assert_eq!(args.len(), 2);
727
- assert_eq!(args[0], Arg::Positional(Expr::Int(1)));
728
- let p = pairArgs(&args[1]);
729
- assert_eq!(p[1], Arg::Positional(Expr::Int(2)));
730
- }
731
-
732
584
  #[test]
733
585
  fn pairArgTrailingComma() {
734
586
  let args = firstCallArgs("fun main() =\n x := f(\"a\" => 1,)\n");
@@ -736,15 +588,6 @@ fn pairArgTrailingComma() {
736
588
  pairArgs(&args[0]);
737
589
  }
738
590
 
739
- #[test]
740
- fn pairKeyAnyExpression() {
741
- let args = firstCallArgs("fun main() =\n x := f(1 => \"x\", k => \"y\", n + 1 => \"z\")\n");
742
- assert_eq!(args.len(), 3);
743
- for a in &args {
744
- pairArgs(a);
745
- }
746
- }
747
-
748
591
  #[test]
749
592
  fn pairArgCallCarriesPairPosition() {
750
593
  let args = firstCallArgs("fun main() =\n x := f(1, \"a\" => 2)\n");
@@ -772,15 +615,6 @@ fn digitNameCallsAreCallsNotConsts() {
772
615
  assert_eq!(args.len(), 1);
773
616
  }
774
617
 
775
- #[test]
776
- fn screamingConstsStayConsts() {
777
- let src = parse("fun f() =\n a := MAX\n b := V2.K1\n c := BUCKET_COUNT\n");
778
- match &onlyFn(&src).body {
779
- FnBody::Block(b) => assert_eq!(b.stmts.len(), 3),
780
- o => panic!("{o:?}"),
781
- }
782
- }
783
-
784
618
  #[test]
785
619
  fn parseStructNoBody() {
786
620
  let s = parse("struct Vec2(x: Float, y: Float)");
@@ -793,6 +627,7 @@ fn parseStructNoBody() {
793
627
  assert_eq!(e.variants[0].name, "Vec2");
794
628
  assert_eq!(e.variants[0].field_names, vec!["x", "y"]);
795
629
  assert_eq!(e.variants[0].fields[0].name, "Float");
630
+ assert!(e.variants[0].member.is_none());
796
631
  }
797
632
 
798
633
  #[test]
@@ -828,18 +663,6 @@ fn parseRecordFnField() {
828
663
  assert_eq!(f[1].name, "T");
829
664
  }
830
665
 
831
- #[test]
832
- fn struct_no_body_then_fun() {
833
- let s = parse("struct Aa(x: Int)\nfun f() -> Int = 1");
834
- assert_eq!(s.items.len(), 2);
835
- }
836
-
837
- #[test]
838
- fn struct_body_comment_before_fn() {
839
- let s = parse("struct Vec2(x: Float) =\n # c\n fun f(self) -> Float = self.x\n");
840
- assert_eq!(onlyEnum(&s).name, "Vec2");
841
- }
842
-
843
666
  #[test]
844
667
  fn value_enum_rejected() {
845
668
  assert!(hasParseError("value enum Vv =\n | Vv(x: Int)\n"));
@@ -897,12 +720,6 @@ fn unionEnumMultiLineGenericMember() {
897
720
  assert_eq!(n.generics[0].name, "T");
898
721
  }
899
722
 
900
- #[test]
901
- fn one_line_then_fun() {
902
- let source = parse("enum Sh = Ci | Re\nfun f() -> Int = 1");
903
- assert_eq!(source.items.len(), 2);
904
- }
905
-
906
723
  #[test]
907
724
  fn one_line_generic_member() {
908
725
  let source = parseRaw("enum Js = JsNull | List[Js] | Map[Str, Js]");
@@ -914,14 +731,6 @@ fn one_line_generic_member() {
914
731
  assert_eq!(memberOf(&e.variants[2]).generics[0].name, "Str");
915
732
  }
916
733
 
917
- #[test]
918
- fn discriminant_unchanged() {
919
- let source = parse("enum Step(n: Int) =\n | ReadMin(10)\n");
920
- let e = onlyEnum(&source);
921
- assert_eq!(e.variants[0].values.len(), 1);
922
- assert!(e.variants[0].member.is_none());
923
- }
924
-
925
734
  #[test]
926
735
  fn inline_payload_still_parses() {
927
736
  let source = parse("enum Sh =\n | Circle(r: Float)\n | Dot(Int)\n");
@@ -931,13 +740,6 @@ fn inline_payload_still_parses() {
931
740
  assert!(e.variants[1].member.is_none());
932
741
  }
933
742
 
934
- #[test]
935
- fn structHasNoMember() {
936
- let source = parse("struct Vec2(x: Float, y: Float)");
937
- let e = onlyEnum(&source);
938
- assert!(e.variants[0].member.is_none());
939
- }
940
-
941
743
  fn firstAssign(src: &str) -> plum_core::ast::Assign {
942
744
  let source = parse(src);
943
745
  let f = onlyFn(&source);
plum-core/tests/unions_test.rs CHANGED
@@ -25,11 +25,6 @@ fn ty(n: &str) -> Type {
25
25
  Type { name: n.to_string(), generics: vec![] }
26
26
  }
27
27
 
28
- #[test]
29
- fn hiddenVariantName() {
30
- assert_eq!(hiddenVariant("Shape", "Circle"), "Shape__Circle");
31
- }
32
-
33
28
  #[test]
34
29
  fn resolvesMemberAndTag() {
35
30
  let mut s = parse("struct Circle(r: Float)\nenum Shape = Circle | Red");
@@ -117,11 +112,3 @@ fn builtinMember() {
117
112
  assert_eq!(enumOf(&s, "Num").variants[0].name, "Num__Int");
118
113
  }
119
114
 
120
- #[test]
121
- fn legacyErrorsReported() {
122
- let s = parse("enum Sh =\n | Circle(r: Float)\nenum User =\n | User(n: Int)\n");
123
- let errs = legacyErrors(&s);
124
- assert_eq!(errs.len(), 2);
125
- assert!(errs[0].starts_with("inline variant payloads are removed: declare record Circle(...)"));
126
- assert!(errs[1].contains("enum User has a single variant named User"));
127
- }