plum

#treesitter#compiler#wasm

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

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


f822aa8 — Peter John 2026-11-05T23:28:10+05:30
Compiler cleanup phase 6: tests in Plum
Files changed (132) hide show
  1. compiler/core/tests/core/for_next_test.rs +0 -98
  2. compiler/core/tests/core/fused_closure_generic_test.rs +0 -28
  3. compiler/core/tests/core/main.rs +0 -2
  4. compiler/tests/core/for_next.plum +68 -0
  5. compiler/tests/core/fused_closure_generic.plum +23 -0
  6. compiler/tests/tail_calls.plum +23 -0
  7. compiler/tests/wasm_codegen/e2e_mem/match_arm_rename_still_valid_wasm.plum +22 -0
  8. compiler/tests/wasm_codegen/e2e_mem/plain_spread_still_copies.plum +15 -0
  9. compiler/tests/wasm_codegen/e2e_mem/unique_self_field_write.plum +18 -0
  10. compiler/tests/wasm_codegen/e2e_mem/unique_self_spread_return.plum +15 -0
  11. compiler/tests/wasm_codegen/e2e_mem/unique_spread_hazard_falls_back_to_copy_1.plum +18 -0
  12. compiler/tests/wasm_codegen/e2e_mem/unique_spread_hazard_falls_back_to_copy_2.plum +17 -0
  13. compiler/tests/wasm_codegen/e2e_mem/unique_spread_reassign_local.plum +16 -0
  14. compiler/tests/wasm_codegen/e2e_mem/unique_spread_sets_fields.plum +15 -0
  15. compiler/tests/wasm_codegen/e2e_mem/unique_spread_swap_reads_old_values.plum +16 -0
  16. compiler/tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum +22 -0
  17. compiler/tests/wasm_codegen/e2e_records/cons_list_length_map_sum.plum +29 -0
  18. compiler/tests/wasm_codegen/e2e_records/deep_nested_variant_match.plum +20 -0
  19. compiler/tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum +21 -0
  20. compiler/tests/wasm_codegen/e2e_records/empty_init_with_annotation.plum +24 -0
  21. compiler/tests/wasm_codegen/e2e_records/enum_init_versus_variant_name.plum +25 -0
  22. compiler/tests/wasm_codegen/e2e_records/equality_is_identity_of_the_variant_value.plum +17 -0
  23. compiler/{wasm-codegen/tests/wasm_codegen/e2e_records/empty_record_embed.rs → tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum} +10 -13
  24. compiler/tests/wasm_codegen/e2e_records/fixed_param_init.plum +19 -0
  25. compiler/tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum +27 -0
  26. compiler/tests/wasm_codegen/e2e_records/generic_inferred_from_args.plum +25 -0
  27. compiler/tests/wasm_codegen/e2e_records/inherited_fields_methods_and_init_order.plum +26 -0
  28. compiler/tests/wasm_codegen/e2e_records/match_variant_fields.plum +17 -0
  29. compiler/tests/wasm_codegen/e2e_records/million_cells_built_and_mapped_in_constant_stack.plum +24 -0
  30. compiler/tests/wasm_codegen/e2e_records/nested_enum_match.plum +23 -0
  31. compiler/tests/wasm_codegen/e2e_records/nested_literal_construction.plum +28 -0
  32. compiler/tests/wasm_codegen/e2e_records/override_calls_original.plum +24 -0
  33. compiler/tests/wasm_codegen/e2e_records/reassign_enum_local_across_variants.plum +33 -0
  34. compiler/tests/wasm_codegen/e2e_records/rest_runs_after_build_and_nested_construction.plum +22 -0
  35. compiler/tests/wasm_codegen/e2e_records/ship_example_values.plum +26 -0
  36. compiler/tests/wasm_codegen/e2e_records/through_a_base_value.plum +24 -0
  37. compiler/tests/wasm_codegen/e2e_records/tree_insert_and_size.plum +20 -0
  38. compiler/tests/wasm_codegen/e2e_records/variant_name_wins.plum +24 -0
  39. compiler/tests/wasm_codegen/e2e_tail/big_link_built_and_summed.plum +17 -0
  40. compiler/tests/wasm_codegen/e2e_tail/break_in_else_if_chain_in_while.plum +20 -0
  41. compiler/tests/wasm_codegen/e2e_tail/break_in_if_in_match_arm_in_while.plum +30 -0
  42. compiler/tests/wasm_codegen/e2e_tail/break_in_match_arm_direct.plum +37 -0
  43. compiler/tests/wasm_codegen/e2e_tail/closure_body_tail_call_million.plum +12 -0
  44. compiler/tests/wasm_codegen/e2e_tail/closure_captures_iteration_value.plum +14 -0
  45. compiler/tests/wasm_codegen/e2e_tail/closure_not_recursive_unchanged.plum +10 -0
  46. compiler/tests/wasm_codegen/e2e_tail/closure_tail_call_runs.plum +14 -0
  47. compiler/tests/wasm_codegen/e2e_tail/continue_in_if_in_match_arm_in_while.plum +33 -0
  48. compiler/tests/wasm_codegen/e2e_tail/method_self_tail.plum +14 -0
  49. compiler/tests/wasm_codegen/e2e_tail/mutual_tail_million.plum +14 -0
  50. compiler/tests/wasm_codegen/e2e_tail/nested_loops_with_match_in_inner.plum +39 -0
  51. compiler/tests/wasm_codegen/e2e_tail/non_tail_union_wrap_not_tail.plum +17 -0
  52. compiler/tests/wasm_codegen/e2e_tail/param_self_tail.plum +17 -0
  53. compiler/tests/wasm_codegen/e2e_tail/question_in_tail_not_tail_call.plum +16 -0
  54. compiler/tests/wasm_codegen/e2e_tail/recursive_closure_captures_outer.plum +10 -0
  55. compiler/tests/wasm_codegen/e2e_tail/recursive_local_closure_million.plum +9 -0
  56. compiler/tests/wasm_codegen/e2e_tail/result_mutual_million.plum +20 -0
  57. compiler/tests/wasm_codegen/e2e_tail/result_self_loop_million.plum +17 -0
  58. compiler/tests/wasm_codegen/e2e_tail/result_self_loop_returns_err.plum +15 -0
  59. compiler/tests/wasm_codegen/e2e_tail/self_calling_closure_typed_params_runs.plum +9 -0
  60. compiler/tests/wasm_codegen/e2e_tail/self_calling_closure_untyped_params_runs.plum +9 -0
  61. compiler/tests/wasm_codegen/e2e_tail/self_tail_args_swap.plum +16 -0
  62. compiler/tests/wasm_codegen/e2e_tail/self_tail_million.plum +20 -0
  63. compiler/tests/wasm_codegen/e2e_tail/self_tail_through_nested_branches.plum +21 -0
  64. compiler/tests/wasm_codegen/e2e_tail/tail_call_in_match_arm.plum +15 -0
  65. compiler/tests/wasm_codegen/e2e_tail/trmc_link_map_100k.plum +25 -0
  66. compiler/tests/wasm_codegen/e2e_types/adder_two_levels.plum +14 -0
  67. compiler/tests/wasm_codegen/e2e_types/bang_unwraps_and_chains.plum +17 -0
  68. compiler/tests/wasm_codegen/e2e_types/bare_tag_fixed_by_later_arg.plum +17 -0
  69. compiler/tests/wasm_codegen/e2e_types/bare_tag_fixed_by_other_link_arg.plum +18 -0
  70. compiler/tests/wasm_codegen/e2e_types/bare_variants_in_if_branches_take_fn_return_type.plum +26 -0
  71. compiler/tests/wasm_codegen/e2e_types/bare_variants_in_match_arms_take_fn_return_type.plum +18 -0
  72. compiler/tests/wasm_codegen/e2e_types/chained_err_value_is_first.plum +25 -0
  73. compiler/tests/wasm_codegen/e2e_types/chained_question_returns_first_err.plum +26 -0
  74. compiler/tests/wasm_codegen/e2e_types/closure_coerced_into_optional_return.plum +12 -0
  75. compiler/tests/wasm_codegen/e2e_types/closure_into_ref_option_and_result.plum +23 -0
  76. compiler/tests/wasm_codegen/e2e_types/closure_passes_none_to_an_option_param.plum +18 -0
  77. compiler/tests/wasm_codegen/e2e_types/closure_returning_none_into_optional.plum +12 -0
  78. compiler/tests/wasm_codegen/e2e_types/curried_closure_runs.plum +13 -0
  79. compiler/tests/wasm_codegen/e2e_types/err_field_is_error.plum +15 -0
  80. compiler/tests/wasm_codegen/e2e_types/field_less_variant_called_like_a_constructor.plum +23 -0
  81. compiler/tests/wasm_codegen/e2e_types/field_less_variant_of_a_generic_enum_called_like_a_constructor.plum +22 -0
  82. compiler/tests/wasm_codegen/e2e_types/fn_field_returning_optional.plum +15 -0
  83. compiler/tests/wasm_codegen/e2e_types/g1_typed_param.plum +13 -0
  84. compiler/tests/wasm_codegen/e2e_types/g2_local_closure_inferred_from_first_call.plum +12 -0
  85. compiler/tests/wasm_codegen/e2e_types/g5_returned_closure.plum +12 -0
  86. compiler/tests/wasm_codegen/e2e_types/inline_if_joins_bare_variant_instantiations.plum +19 -0
  87. compiler/tests/wasm_codegen/e2e_types/inlined_constant_call_keeps_return_type.plum +18 -0
  88. compiler/tests/wasm_codegen/e2e_types/list_literal_element_pinned_by_a_later_element.plum +37 -0
  89. compiler/tests/wasm_codegen/e2e_types/map_err_closure_param_shadows_top_level_fn.plum +17 -0
  90. compiler/tests/wasm_codegen/e2e_types/map_err_on_call_result.plum +18 -0
  91. compiler/tests/wasm_codegen/e2e_types/map_err_then_unwrap_or_fuses.plum +16 -0
  92. compiler/tests/wasm_codegen/e2e_types/nested_generic.plum +21 -0
  93. compiler/tests/wasm_codegen/e2e_types/none_as_generic_param_default.plum +16 -0
  94. compiler/tests/wasm_codegen/e2e_types/none_as_param_default.plum +16 -0
  95. compiler/tests/wasm_codegen/e2e_types/ok_unit_and_expression_position.plum +24 -0
  96. compiler/tests/wasm_codegen/e2e_types/option_question_returns_none.plum +18 -0
  97. compiler/tests/wasm_codegen/e2e_types/reused_cell_is_read_through_its_local.plum +23 -0
  98. compiler/tests/wasm_codegen/e2e_types/seq_with_optional_next.plum +19 -0
  99. compiler/tests/wasm_codegen/e2e_types/single_letter_types_and_variants.plum +24 -0
  100. compiler/tests/wasm_codegen/e2e_types/specialised_arm_keeps_result_type.plum +26 -0
  101. compiler/tests/wasm_codegen/e2e_types/static_method_of_generic_type_not_pinning_t.plum +12 -0
  102. compiler/tests/wasm_codegen/e2e_types/static_on_builtin.plum +11 -0
  103. compiler/tests/wasm_codegen/e2e_types/static_on_builtin_method_and_json_cycle.plum +17 -0
  104. compiler/tests/wasm_codegen/e2e_types/static_on_type_param.plum +14 -0
  105. compiler/tests/wasm_codegen/e2e_types/struct_payload_in_some.plum +18 -0
  106. compiler/tests/wasm_codegen/e2e_types/unannotated_local_of_list_pinned_by_a_later_element.plum +39 -0
  107. compiler/tests/wasm_codegen/e2e_types/unit_closure_local_called_as_a_statement.plum +15 -0
  108. compiler/tests/wasm_codegen/e2e_types/untyped_closure_passes_none_to_an_option_param.plum +18 -0
  109. compiler/tests/wasm_codegen/e2e_types/user_type_shadows_std_pair_imported_by_std.plum +14 -0
  110. compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs +0 -119
  111. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/enum_init.rs +1 -68
  112. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/inline_variants.rs +1 -73
  113. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/mod.rs +0 -2
  114. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_embed.rs +0 -79
  115. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs +1 -74
  116. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/unboxed_variants.rs +1 -167
  117. compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/closure_tail.rs +1 -37
  118. compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/error_tail.rs +0 -27
  119. compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/loop_break.rs +0 -148
  120. compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/mod.rs +0 -2
  121. compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/tail.rs +1 -150
  122. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/closure_optional.rs +0 -39
  123. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/closure_types.rs +0 -50
  124. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/list_and_shadow.rs +0 -81
  125. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs +0 -3
  126. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/question.rs +0 -144
  127. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/synthetic_sites.rs +1 -108
  128. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/type_param_static.rs +0 -83
  129. docs/backlog.md +1 -1
  130. docs/plans/2026-11-05-compiler-cleanup-phase5-6.md +1 -1
  131. docs/plans/2026-11-05-compiler-cleanup-phase5-6.progress.md +26 -0
  132. docs/plans/2026-11-05-compiler-cleanup-phase5-6.results.md +36 -0
compiler/core/tests/core/for_next_test.rs DELETED
@@ -1,98 +0,0 @@
1
- #![allow(non_snake_case)]
2
- //! The one loop form `for x := range v`: `v: Int` counts 0..v; any other `v`
3
- //! whose type has `next(self) -> Option[Pair[T, Self]]` iterates (None = done, else
4
- //! `Some(Pair(value, rest))`, the iterator rebound to `rest` each step).
5
- use plum_test_support::runInt;
6
-
7
- #[test]
8
- fn range_int_counts_from_zero() {
9
- let src = "\
10
- import std/Int
11
-
12
- fun main() -> Int =
13
- acc := 0
14
- for i := range 3
15
- acc = acc * 10 + i + 1
16
- acc
17
- ";
18
- // binds 0, 1, 2
19
- assert_eq!(runInt("for_next_int", src), 123);
20
- }
21
-
22
- #[test]
23
- fn range_list_binds_elements() {
24
- let src = "\
25
- import std/Int
26
- import std/List
27
-
28
- fun main() -> Int =
29
- acc := 0
30
- for x := range [5, 6]
31
- acc = acc * 10 + x
32
- acc
33
- ";
34
- assert_eq!(runInt("for_next_list", src), 56);
35
- }
36
-
37
- #[test]
38
- fn list_next_steps() {
39
- let src = "\
40
- import std/Int
41
- import std/List
42
- import std/Pair
43
- import std/Option
44
-
45
- fun main() -> Int =
46
- xs := [5, 6]
47
- p := xs.next()!
48
- q := p.second.next()!
49
- r := q.second.next()
50
- p.first * 100 + q.first * 10 + {if r.isNone() then 1 else 0}
51
- ";
52
- assert_eq!(runInt("for_next_list_next", src), 561);
53
- }
54
-
55
- #[test]
56
- fn range_user_struct_with_next() {
57
- let src = "\
58
- import std/Int
59
- import std/Pair
60
- import std/Option
61
-
62
- struct Countdown(n: Int) =
63
- fun next(self) -> Option[Pair[Int, Countdown]] =
64
- if self.n == 0 then None else Some(Pair(first: self.n, second: Countdown(n: self.n - 1)))
65
-
66
- fun main() -> Int =
67
- acc := 0
68
- for x := range Countdown(n: 3)
69
- acc = acc * 10 + x
70
- acc
71
- ";
72
- assert_eq!(runInt("for_next_user", src), 321);
73
- }
74
-
75
- #[test]
76
- fn range_next_supports_continue_and_break() {
77
- let src = "\
78
- import std/Int
79
- import std/Pair
80
- import std/Option
81
-
82
- struct Up(i: Int, n: Int) =
83
- fun next(self) -> Option[Pair[Int, Up]] =
84
- if self.i >= self.n then None else Some(Pair(first: self.i, second: Up(i: self.i + 1, n: self.n)))
85
-
86
- fun main() -> Int =
87
- acc := 0
88
- for x := range Up(i: 0, n: 10)
89
- if x == 1
90
- continue
91
- if x == 4
92
- break
93
- acc = acc * 10 + x
94
- acc
95
- ";
96
- // 0, (skip 1), 2, 3, stop at 4
97
- assert_eq!(runInt("for_next_ctl", src), 23);
98
- }
compiler/core/tests/core/fused_closure_generic_test.rs DELETED
@@ -1,28 +0,0 @@
1
- #![allow(non_snake_case)]
2
- //! A fused fn keeps a specialised-away closure parameter when it is the only thing that
3
- //! fixes a generic another kept closure parameter takes as its input: `map(andThen(..))`
4
- //! on `Option` fuses to `map$c0$andThen$c0$0(|t| t.name, u)`, whose `|t| ..` needs the
5
- //! `B` that only `andThen`'s closure binds. Dropping it left the fused fn uninstantiable
6
- //! ("unknown function" at codegen).
7
- use plum_test_support::runInt;
8
-
9
- #[test]
10
- fn option_and_then_then_map_compiles_and_runs() {
11
- let src = "\
12
- import std/Str
13
- import std/Int
14
- import std/Option
15
-
16
- struct Target(size: Int)
17
- struct User(name: Str, target: Option[Target])
18
-
19
- fun targetSize(u: Option[User]) -> Option[Int] =
20
- u.andThen(|v| v.target).map(|t| t.size)
21
-
22
- fun main() -> Int =
23
- full := User(name: \"u\", target: Some(Target(size: 40)))
24
- none: Option[User] := None
25
- targetSize(Some(full)).unwrapOr(0) + targetSize(none).unwrapOr(2)
26
- ";
27
- assert_eq!(runInt("fused_closure_generic", src), 42);
28
- }
compiler/core/tests/core/main.rs CHANGED
@@ -3,10 +3,8 @@ mod closure_spec_test;
3
3
  mod closure_types_parse_test;
4
4
  mod diag_test;
5
5
  mod drive_test;
6
- mod for_next_test;
7
6
  mod formatter_test;
8
7
  mod forms_list_range_test;
9
- mod fused_closure_generic_test;
10
8
  mod generics_test;
11
9
  mod ids_spans_test;
12
10
  mod inline_variants_test;
compiler/tests/core/for_next.plum ADDED
@@ -0,0 +1,68 @@
1
+ import std/Int
2
+ import std/List
3
+ import std/Pair
4
+ import std/Option
5
+
6
+ # The one loop form `for x := range v`: `v: Int` counts 0..v; any other `v`
7
+ # whose type has `next(self) -> Option[Pair[T, Self]]` iterates (None = done, else
8
+ # `Some(Pair(value, rest))`, the iterator rebound to `rest` each step).
9
+
10
+ struct Countdown(n: Int) =
11
+ fun next(self) -> Option[Pair[Int, Countdown]] =
12
+ if self.n == 0 then None else Some(Pair(first: self.n, second: Countdown(n: self.n - 1)))
13
+
14
+ struct Up(i: Int, n: Int) =
15
+ fun next(self) -> Option[Pair[Int, Up]] =
16
+ if self.i >= self.n then None else Some(Pair(first: self.i, second: Up(i: self.i + 1, n: self.n)))
17
+
18
+ fun rangeInt() -> Int =
19
+ acc := 0
20
+ for i := range 3
21
+ acc = acc * 10 + i + 1
22
+ acc
23
+
24
+ fun rangeList() -> Int =
25
+ acc := 0
26
+ for x := range [5, 6]
27
+ acc = acc * 10 + x
28
+ acc
29
+
30
+ fun listNext() -> Int =
31
+ xs := [5, 6]
32
+ p := xs.next()!
33
+ q := p.second.next()!
34
+ r := q.second.next()
35
+ p.first * 100 + q.first * 10 + {if r.isNone() then 1 else 0}
36
+
37
+ fun rangeUser() -> Int =
38
+ acc := 0
39
+ for x := range Countdown(n: 3)
40
+ acc = acc * 10 + x
41
+ acc
42
+
43
+ fun rangeCtl() -> Int =
44
+ acc := 0
45
+ for x := range Up(i: 0, n: 10)
46
+ if x == 1
47
+ continue
48
+ if x == 4
49
+ break
50
+ acc = acc * 10 + x
51
+ acc
52
+
53
+ # binds 0, 1, 2
54
+ test "range_int_counts_from_zero"
55
+ assert rangeInt() == 123
56
+
57
+ test "range_list_binds_elements"
58
+ assert rangeList() == 56
59
+
60
+ test "list_next_steps"
61
+ assert listNext() == 561
62
+
63
+ test "range_user_struct_with_next"
64
+ assert rangeUser() == 321
65
+
66
+ # 0, (skip 1), 2, 3, stop at 4
67
+ test "range_next_supports_continue_and_break"
68
+ assert rangeCtl() == 23
compiler/tests/core/fused_closure_generic.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Option
4
+
5
+ # A fused fn keeps a specialised-away closure parameter when it is the only thing that
6
+ # fixes a generic another kept closure parameter takes as its input: `map(andThen(..))`
7
+ # on `Option` fuses to `map$c0$andThen$c0$0(|t| t.name, u)`, whose `|t| ..` needs the
8
+ # `B` that only `andThen`'s closure binds. Dropping it left the fused fn uninstantiable
9
+ # ("unknown function" at codegen).
10
+
11
+ struct Target(size: Int)
12
+ struct User(name: Str, target: Option[Target])
13
+
14
+ fun targetSize(u: Option[User]) -> Option[Int] =
15
+ u.andThen(|v| v.target).map(|t| t.size)
16
+
17
+ fun fusedSum() -> Int =
18
+ full := User(name: "u", target: Some(Target(size: 40)))
19
+ none: Option[User] := None
20
+ targetSize(Some(full)).unwrapOr(0) + targetSize(none).unwrapOr(2)
21
+
22
+ test "option_and_then_then_map_compiles_and_runs"
23
+ assert fusedSum() == 42
compiler/tests/tail_calls.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Int
2
+
3
+ # `plum test` must run tail calls like a compiled program: each loop below is
4
+ # 1000000 deep (a non-tail loop of that depth traps), which overflows the stack unless the calls are real tail calls.
5
+
6
+ fun count(n: Int, acc: Int) -> Int = if n == 0 then acc else count(n - 1, acc + 1)
7
+
8
+ struct Counter(step: Int) =
9
+ fun count(self, n: Int, acc: Int) -> Int =
10
+ if n == 0 then acc else self.count(n - 1, acc + self.step)
11
+
12
+ fun viaClosure(n: Int, k: fn(Int) -> Int) -> Int = if n == 0 then 7 else k(n - 1)
13
+
14
+ fun goClosure(n: Int) -> Int = viaClosure(n, goClosure)
15
+
16
+ test "a self tail call 1000000 deep does not overflow the stack"
17
+ assert count(1000000, 0) == 1000000
18
+
19
+ test "a method tail call 1000000 deep does not overflow the stack"
20
+ assert Counter(step: 3).count(1000000, 0) == 3000000
21
+
22
+ test "a closure tail call 1000000 deep does not overflow the stack"
23
+ assert goClosure(1000000) == 7
compiler/tests/wasm_codegen/e2e_mem/match_arm_rename_still_valid_wasm.plum ADDED
@@ -0,0 +1,22 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Pa(x: Int)
7
+ struct Pb(y: Int, z: Int)
8
+
9
+ enum Pick = Wa(v: Pa) | Wb(v: Pb)
10
+
11
+ fun go(p: Pick) -> Int =
12
+ match p
13
+ Wa(a) => a.x
14
+ Wb(b) => b.y * 10 + b.z
15
+
16
+ fun runMain() -> Int =
17
+ go(Wa(v: Pa(x: 4))) * 100 + go(Wb(v: Pb(y: 5, z: 6)))
18
+
19
+ # moved from e2e_mem::unique_inplace::match_arm_rename_still_valid_wasm
20
+ # R5: arms binding differently named locals of different record types.
21
+ test "match arm rename still valid wasm"
22
+ assert runMain() == 456
compiler/tests/wasm_codegen/e2e_mem/plain_spread_still_copies.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int)
7
+
8
+ fun runMain() -> Int =
9
+ s := Cell(x: 1, y: 7)
10
+ t := Cell(..s, x: 5)
11
+ s.x * 1000 + t.x * 100 + t.y
12
+
13
+ # moved from e2e_mem::unique_inplace::plain_spread_still_copies
14
+ test "plain spread still copies"
15
+ assert runMain() == 1507
compiler/tests/wasm_codegen/e2e_mem/unique_self_field_write.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(mut x: Int) =
7
+ fun bump(self) -> Cell =
8
+ self.x = self.x + 1
9
+ self
10
+
11
+ fun runMain() -> Int =
12
+ s := Cell(x: 41)
13
+ t := s.bump()
14
+ t.x
15
+
16
+ # moved from e2e_mem::unique_inplace::unique_self_field_write
17
+ test "unique self field write"
18
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_mem/unique_self_spread_return.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int) =
7
+ fun step(self) -> Cell = Cell(..self, x: self.x + 1)
8
+
9
+ fun runMain() -> Int =
10
+ t := Cell(x: 1, y: 7).step().step()
11
+ t.x * 100 + t.y
12
+
13
+ # moved from e2e_mem::unique_inplace::unique_self_spread_return
14
+ test "unique self spread return"
15
+ assert runMain() == 307
compiler/tests/wasm_codegen/e2e_mem/unique_spread_hazard_falls_back_to_copy_1.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int) =
7
+ fun sum(self) -> Int = self.x + self.y
8
+
9
+ fun runMain() -> Int =
10
+ s := Cell(x: 1, y: 2)
11
+ t := Cell(..s, x: 10, y: s.sum())
12
+ t.x * 100 + t.y
13
+
14
+ # moved from e2e_mem::unique_inplace::unique_spread_hazard_falls_back_to_copy
15
+ # A later value that reads a field an earlier one overwrote, or uses the source
16
+ # any other way (here a method call), must still run (copy fallback).
17
+ test "unique spread hazard falls back to copy"
18
+ assert runMain() == 1003
compiler/tests/wasm_codegen/e2e_mem/unique_spread_hazard_falls_back_to_copy_2.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int, z: Int)
7
+
8
+ fun runMain() -> Int =
9
+ s := Cell(x: 1, y: 2, z: 3)
10
+ t := Cell(..s, x: 10, z: s.y + s.z)
11
+ t.x * 100 + t.y * 10 + t.z
12
+
13
+ # moved from e2e_mem::unique_inplace::unique_spread_hazard_falls_back_to_copy
14
+ # A later value that reads a field an earlier one overwrote, or uses the source
15
+ # any other way (here a method call), must still run (copy fallback).
16
+ test "unique spread hazard falls back to copy"
17
+ assert runMain() == 1025
compiler/tests/wasm_codegen/e2e_mem/unique_spread_reassign_local.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int)
7
+
8
+ fun runMain() -> Int =
9
+ s := Cell(x: 1, y: 7)
10
+ s = Cell(..s, x: s.x + 1)
11
+ s = Cell(..s, y: s.y + s.x)
12
+ s.x * 100 + s.y
13
+
14
+ # moved from e2e_mem::unique_inplace::unique_spread_reassign_local
15
+ test "unique spread reassign local"
16
+ assert runMain() == 209
compiler/tests/wasm_codegen/e2e_mem/unique_spread_sets_fields.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int, z: Int)
7
+
8
+ fun runMain() -> Int =
9
+ s := Cell(x: 1, y: 2, z: 3)
10
+ t := Cell(..s, z: 30, x: 10)
11
+ t.x * 10000 + t.y * 100 + t.z
12
+
13
+ # moved from e2e_mem::unique_inplace::unique_spread_sets_fields
14
+ test "unique spread sets fields"
15
+ assert runMain() == 100230
compiler/tests/wasm_codegen/e2e_mem/unique_spread_swap_reads_old_values.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int)
7
+
8
+ fun runMain() -> Int =
9
+ s := Cell(x: 1, y: 2)
10
+ t := Cell(..s, x: s.y, y: s.x)
11
+ t.x * 10 + t.y
12
+
13
+ # moved from e2e_mem::unique_inplace::unique_spread_swap_reads_old_values
14
+ # A value reading an already-overwritten field must still see the OLD value.
15
+ test "unique spread swap reads old values"
16
+ assert runMain() == 21
compiler/tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum ADDED
@@ -0,0 +1,22 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Actor(x: Int) =
7
+ fun go(self) -> Int = 1
8
+
9
+ struct Mid(Actor(x), m: Int) =
10
+ override fun go(self) -> Int = 2 + self.Actor.go()
11
+
12
+ struct Top(Mid(x, m), t: Int)
13
+
14
+ fun run(a: Actor) -> Int = a.go()
15
+
16
+ fun runMain() -> Int =
17
+ t := Top(x: 1, m: 2, t: 3)
18
+ run(t) * 10 + t.x
19
+
20
+ # moved from e2e_records::record_embed::base_through_embedded_struct
21
+ test "base through embedded struct"
22
+ assert runMain() == 31
compiler/tests/wasm_codegen/e2e_records/cons_list_length_map_sum.plum ADDED
@@ -0,0 +1,29 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Slice
4
+
5
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
6
+
7
+ fun length[T](xs: Link[T]) -> Int = match xs
8
+ Nil => 0
9
+ Cons(_, t) => 1 + length(t)
10
+
11
+ fun sum(xs: Link[Int]) -> Int = match xs
12
+ Nil => 0
13
+ Cons(h, t) => h + sum(t)
14
+
15
+ fun cons[T](h: T, t: Link[T]) -> Link[T] =
16
+ Cons(head: h, tail: t)
17
+
18
+ fun map(xs: Link[Int], f: fn(Int) -> Int) -> Link[Int] = match xs
19
+ Nil => Nil
20
+ Cons(h, t) => Cons(head: f(h), tail: map(t, f))
21
+
22
+ fun runMain() -> Int =
23
+ xs := cons(1, cons(2, cons(3, Nil)))
24
+ ys := map(xs, |n| n * 2)
25
+ sum(xs) * 100 + sum(ys) + length(ys) * 10000
26
+
27
+ # moved from e2e_records::inline_variants::cons_list_length_map_sum
28
+ test "cons list length map sum"
29
+ assert runMain() == 30612
compiler/tests/wasm_codegen/e2e_records/deep_nested_variant_match.plum ADDED
@@ -0,0 +1,20 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Tree = Leaf(v: Int) | Node(l: Tree, r: Tree)
6
+
7
+ fun depthSum(t: Tree, d: Int) -> Int =
8
+ match t
9
+ Leaf(v) => v * d
10
+ Node(l, r) => depthSum(l, d + 1) + depthSum(r, d + 1)
11
+
12
+ fun build(n: Int) -> Tree =
13
+ if n == 0 then Leaf(v: 1) else Node(l: build(n - 1), r: Leaf(v: n))
14
+
15
+ fun runMain() -> Int = depthSum(build(5), 1)
16
+
17
+ # moved from e2e_records::unboxed_variants::deep_nested_variant_match
18
+ # Leaves: 1 at depth 6, then Leaf(n) at depth 7 - n for n = 1..5.
19
+ test "deep nested variant match"
20
+ assert runMain() == 56
compiler/tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum ADDED
@@ -0,0 +1,21 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Stage(camScale: Int, tag: Int) =
7
+ fun init(self) =
8
+ self.camX: Int = 6
9
+
10
+ struct World(Stage(tag), score: Int) =
11
+ fun init(self) =
12
+ self.camScale = 5
13
+
14
+ fun runMain() -> Int =
15
+ s := Stage(camScale: 2, tag: 1)
16
+ w := World(tag: 3, score: 1)
17
+ s.camScale * 100 + w.camScale * 10 + w.camX + w.tag + s.camX
18
+
19
+ # moved from e2e_records::record_embed::embedded_record_standalone_and_unselected_set_by_init
20
+ test "embedded record standalone and unselected set by init"
21
+ assert runMain() == 265
compiler/tests/wasm_codegen/e2e_records/empty_init_with_annotation.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Str
2
+ import std/Int
3
+
4
+ enum Link[T] =
5
+ | Nil
6
+ | Cons(head: T, tail: Link[T])
7
+
8
+ fun init() -> Link[T] = Nil
9
+
10
+ fun length(self) -> Int = match self
11
+ Nil => 0
12
+ Cons(_, t) => 1 + t.length()
13
+
14
+ fun sum(self) -> Int = match self
15
+ Nil => 0
16
+ Cons(h, t) => h + t.sum()
17
+
18
+ fun runMain() -> Int =
19
+ xs: Link[Int] := Link()
20
+ xs.length() + 7
21
+
22
+ # moved from e2e_records::enum_init::empty_init_with_annotation
23
+ test "empty init with annotation"
24
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_records/enum_init_versus_variant_name.plum ADDED
@@ -0,0 +1,25 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Cell =
6
+ | Empty
7
+ | Full(count: Int, label: Str)
8
+
9
+ fun init(n: Int) -> Cell = if n <= 0 then Empty else Full(count: n, label: "n{n}")
10
+
11
+ fun size(self) -> Int =
12
+ match self
13
+ Empty => 0
14
+ Full(c, _) => c
15
+
16
+ fun runMain() -> Int =
17
+ a := Cell(5)
18
+ b := Cell(0)
19
+ c := Full(count: 9, label: "nine")
20
+ d := Full(label: "swapped", count: 2)
21
+ a.size() * 1000 + b.size() * 100 + c.size() * 10 + d.size()
22
+
23
+ # moved from e2e_records::unboxed_variants::enum_init_versus_variant_name
24
+ test "enum init versus variant name"
25
+ assert runMain() == 5092
compiler/tests/wasm_codegen/e2e_records/equality_is_identity_of_the_variant_value.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
6
+
7
+ fun runMain() -> Int =
8
+ a: Shape := Circle(r: 2)
9
+ b: Shape := a
10
+ c: Shape := Rect(w: 1, h: 2)
11
+ x := if a == b then 1 else 0
12
+ y := if a != c then 10 else 0
13
+ x + y
14
+
15
+ # moved from e2e_records::unboxed_variants::equality_is_identity_of_the_variant_value
16
+ test "equality is identity of the variant value"
17
+ assert runMain() == 11
compiler/{wasm-codegen/tests/wasm_codegen/e2e_records/empty_record_embed.rs → tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum} RENAMED
@@ -1,12 +1,8 @@
1
+ import std/Str
1
- use plum_test_support::{runInt, PRE};
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
2
5
 
3
- fn prog(body: &str) -> String {
4
- format!("{PRE}{body}")
5
- }
6
-
7
- #[test]
8
- fn field_less_struct_embedding_field_less_base_dispatches_in_a_slice() {
9
- let src = prog("\
10
6
  struct Actor() =
11
7
  fun act(self, n: Int) -> Int = n
12
8
  fun draw(self) -> Int = 1
@@ -18,7 +14,7 @@ struct Ship(Actor, speed: Int) =
18
14
  override fun act(self, n: Int) -> Int = n + self.speed
19
15
  override fun draw(self) -> Int = 100
20
16
 
21
- fun main() -> Int =
17
+ fun runMain() -> Int =
22
18
  xs: Slice[Actor] := Slice[Actor]()
23
19
  xs.push(Panel())
24
20
  xs.push(Ship(speed: 5))
@@ -26,7 +22,8 @@ fun main() -> Int =
26
22
  for i := range 2
27
23
  total = total + xs.at(i).draw() + xs.at(i).act(1000)
28
24
  total
25
+
29
- ");
26
+ # moved from e2e_records::empty_record_embed::field_less_struct_embedding_field_less_base_dispatches_in_a_slice
30
- // Panel: draw 10 + act 1000; Ship: draw 100 + act 1005.
27
+ # Panel: draw 10 + act 1000; Ship: draw 100 + act 1005.
28
+ test "field less struct embedding field less base dispatches in a slice"
31
- assert_eq!(runInt("slice", &src), 2115);
29
+ assert runMain() == 2115
32
- }
compiler/tests/wasm_codegen/e2e_records/fixed_param_init.plum ADDED
@@ -0,0 +1,19 @@
1
+ import std/Str
2
+ import std/Int
3
+
4
+ enum Pair2 =
5
+ | Zero
6
+ | Two(a: Int, b: Int)
7
+
8
+ fun init(a: Int, b: Int) -> Pair2 = Two(a: a, b: b)
9
+
10
+ fun total(self) -> Int = match self
11
+ Zero => 0
12
+ Two(a, b) => a + b
13
+
14
+ fun runMain() -> Int =
15
+ Pair2(3, 4).total()
16
+
17
+ # moved from e2e_records::enum_init::fixed_param_init
18
+ test "fixed param init"
19
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum ADDED
@@ -0,0 +1,27 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Actor(px: Int, py: Int) =
7
+ fun init(self) =
8
+ self.width: Int = 10
9
+ self.height: Int = self.width * 2
10
+ self.kids: Slice[Int] = Slice[Int]()
11
+ fun area(self) -> Int = self.width * self.height
12
+
13
+ struct Ship(Actor(px, py)) =
14
+ fun init(self) =
15
+ self.vx = 0
16
+
17
+ fun runMain() -> Int =
18
+ a := Ship(px: 0, py: 0)
19
+ b := Ship(px: 0, py: 0)
20
+ a.kids.push(1)
21
+ a.kids.push(2)
22
+ b.kids.push(9)
23
+ a.kids.length() * 10 + b.kids.length()
24
+
25
+ # moved from e2e_records::record_init::fresh_list_per_construction
26
+ test "fresh list per construction"
27
+ assert runMain() == 21
compiler/tests/wasm_codegen/e2e_records/generic_inferred_from_args.plum ADDED
@@ -0,0 +1,25 @@
1
+ import std/Str
2
+ import std/Int
3
+
4
+ enum Link[T] =
5
+ | Nil
6
+ | Cons(head: T, tail: Link[T])
7
+
8
+ fun init(a: T, b: T) -> Link[T] = Cons(head: a, tail: Cons(head: b, tail: Nil))
9
+
10
+ fun length(self) -> Int = match self
11
+ Nil => 0
12
+ Cons(_, t) => 1 + t.length()
13
+
14
+ fun sum(self) -> Int = match self
15
+ Nil => 0
16
+ Cons(h, t) => h + t.sum()
17
+
18
+ fun total(xs: Link[Int]) -> Int = xs.sum()
19
+
20
+ fun runMain() -> Int =
21
+ total(Link(5, 6)) + Link("a", "b").length()
22
+
23
+ # moved from e2e_records::enum_init::generic_inferred_from_args
24
+ test "generic inferred from args"
25
+ assert runMain() == 13
compiler/tests/wasm_codegen/e2e_records/inherited_fields_methods_and_init_order.plum ADDED
@@ -0,0 +1,26 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Stage(camScale: Int) =
7
+ fun init(self) =
8
+ self.root: Int = 10
9
+ self.camX: Int = self.camScale * 2
10
+
11
+ fun act(self, dt: Int) -> Int = self.root + dt
12
+
13
+ fun draw(self) -> Int = 100
14
+
15
+ struct World(Stage(camScale), score: Int) =
16
+ fun init(self) =
17
+ self.best: Int = self.root + self.camX + 1
18
+
19
+ fun runMain() -> Int =
20
+ w := World(camScale: 3, score: 4)
21
+ w.camScale + w.root * 10 + w.camX * 100 + w.score * 1000 + w.best * 10000 + w.act(5) * 100000
22
+
23
+ # moved from e2e_records::record_embed::inherited_fields_methods_and_init_order
24
+ # camScale 3, root 10, camX 6, score 4, best 17, act(5) = 15
25
+ test "inherited fields methods and init order"
26
+ assert runMain() == 1674703
compiler/tests/wasm_codegen/e2e_records/match_variant_fields.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
6
+
7
+ fun area(s: Shape) -> Int =
8
+ match s
9
+ Circle(r) => r * r
10
+ Rect(w, h) => w * h
11
+
12
+ fun runMain() -> Int =
13
+ area(Circle(r: 3)) * 100 + area(Rect(w: 2, h: 5))
14
+
15
+ # moved from e2e_records::unboxed_variants::match_variant_fields
16
+ test "match variant fields"
17
+ assert runMain() == 910
compiler/tests/wasm_codegen/e2e_records/million_cells_built_and_mapped_in_constant_stack.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Link = End | Duo(head: Int, tail: Link)
6
+
7
+ fun upTo(n: Int) -> Link =
8
+ if n == 0 then End else Duo(head: n, tail: upTo(n - 1))
9
+
10
+ fun inc(xs: Link) -> Link =
11
+ match xs
12
+ End => End
13
+ Duo(h, t) => Duo(head: h + 1, tail: inc(t))
14
+
15
+ fun sum(xs: Link, acc: Int) -> Int =
16
+ match xs
17
+ End => acc
18
+ Duo(h, t) => sum(t, acc + h)
19
+
20
+ fun runMain() -> Int = sum(inc(upTo(1000000)), 0)
21
+
22
+ # moved from e2e_records::unboxed_variants::million_cells_built_and_mapped_in_constant_stack
23
+ test "million cells built and mapped in constant stack"
24
+ assert runMain() == 500001500000
compiler/tests/wasm_codegen/e2e_records/nested_enum_match.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Link[T] = End | Duo(head: T, tail: Link[T])
6
+
7
+ fun sum(l: Link[Int]) -> Int =
8
+ match l
9
+ End => 0
10
+ Duo(head, tail) => head + sum(tail)
11
+
12
+ fun second(l: Link[Int]) -> Int =
13
+ match l
14
+ Duo(_, Duo(h, _)) => h
15
+ _ => -1
16
+
17
+ fun runMain() -> Int =
18
+ l: Link[Int] := Duo(head: 1, tail: Duo(head: 20, tail: Duo(head: 300, tail: End)))
19
+ sum(l) * 1000 + second(l)
20
+
21
+ # moved from e2e_records::unboxed_variants::nested_enum_match
22
+ test "nested enum match"
23
+ assert runMain() == 321020
compiler/tests/wasm_codegen/e2e_records/nested_literal_construction.plum ADDED
@@ -0,0 +1,28 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Slice
4
+
5
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
6
+
7
+ fun length[T](xs: Link[T]) -> Int = match xs
8
+ Nil => 0
9
+ Cons(_, t) => 1 + length(t)
10
+
11
+ fun sum(xs: Link[Int]) -> Int = match xs
12
+ Nil => 0
13
+ Cons(h, t) => h + sum(t)
14
+
15
+ fun cons[T](h: T, t: Link[T]) -> Link[T] =
16
+ Cons(head: h, tail: t)
17
+
18
+ fun map(xs: Link[Int], f: fn(Int) -> Int) -> Link[Int] = match xs
19
+ Nil => Nil
20
+ Cons(h, t) => Cons(head: f(h), tail: map(t, f))
21
+
22
+ fun runMain() -> Int =
23
+ xs := Cons(head: 1, tail: Cons(head: 2, tail: Cons(head: 3, tail: Nil)))
24
+ sum(xs)
25
+
26
+ # moved from e2e_records::inline_variants::nested_literal_construction
27
+ test "nested literal construction"
28
+ assert runMain() == 6
compiler/tests/wasm_codegen/e2e_records/override_calls_original.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Stage(camScale: Int) =
7
+ fun init(self) =
8
+ self.root: Int = 10
9
+ self.camX: Int = self.camScale * 2
10
+
11
+ fun act(self, dt: Int) -> Int = self.root + dt
12
+
13
+ fun draw(self) -> Int = 100
14
+
15
+ struct World(Stage(camScale), score: Int) =
16
+ override fun draw(self) -> Int = self.Stage.draw() + self.score
17
+
18
+ fun runMain() -> Int =
19
+ w := World(camScale: 1, score: 7)
20
+ w.draw() * 10 + w.act(1)
21
+
22
+ # moved from e2e_records::record_embed::override_calls_original
23
+ test "override calls original"
24
+ assert runMain() == 1081
compiler/tests/wasm_codegen/e2e_records/reassign_enum_local_across_variants.plum ADDED
@@ -0,0 +1,33 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+
5
+ enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
6
+
7
+ fun area(s: Shape) -> Int =
8
+ match s
9
+ Circle(r) => r * 3
10
+ Rect(w, h) => w * h
11
+
12
+ fun kind(s: Shape) -> Int =
13
+ match s
14
+ Circle(_) => 1
15
+ Rect(_, _) => 10
16
+
17
+ fun runMain() -> Int =
18
+ s: Shape := Circle(r: 1)
19
+ n := area(s)
20
+ s = Rect(w: 2, h: 5)
21
+ n = n * 100 + area(s)
22
+ k := 0
23
+ for i := range 4
24
+ if i % 2 == 0
25
+ s = Rect(w: 1, h: 1)
26
+ else
27
+ s = Circle(r: 1)
28
+ k = k + kind(s)
29
+ n * 100 + k
30
+
31
+ # moved from e2e_records::unboxed_variants::reassign_enum_local_across_variants
32
+ test "reassign enum local across variants"
33
+ assert runMain() == 31022
compiler/tests/wasm_codegen/e2e_records/rest_runs_after_build_and_nested_construction.plum ADDED
@@ -0,0 +1,22 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Inner(mut n: Int)
7
+
8
+ struct Outer(a: Int) =
9
+ fun init(self) =
10
+ self.inner = Inner(n: self.a + 1)
11
+ self.total = 0
12
+ self.bump()
13
+ fun bump(self) =
14
+ self.inner.n = self.inner.n * 10
15
+
16
+ fun runMain() -> Int =
17
+ o := Outer(a: 4)
18
+ o.inner.n + o.a
19
+
20
+ # moved from e2e_records::record_init::rest_runs_after_build_and_nested_construction
21
+ test "rest runs after build and nested construction"
22
+ assert runMain() == 54
compiler/tests/wasm_codegen/e2e_records/ship_example_values.plum ADDED
@@ -0,0 +1,26 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Actor(px: Int, py: Int) =
7
+ fun init(self) =
8
+ self.width: Int = 10
9
+ self.height: Int = self.width * 2
10
+ self.kids: Slice[Int] = Slice[Int]()
11
+ fun area(self) -> Int = self.width * self.height
12
+
13
+ struct Ship(Actor(px, py), tex: Int) =
14
+ fun init(self) =
15
+ self.height = self.width + 1
16
+ self.vx = 7
17
+ override fun area(self) -> Int = self.width * self.height + self.vx
18
+
19
+ fun runMain() -> Int =
20
+ s := Ship(px: 1, py: 2, tex: 3)
21
+ s.px + s.py * 10 + s.tex * 100 + s.area() * 1000
22
+
23
+ # moved from e2e_records::record_init::ship_example_values
24
+ # px 1 + py 20 + tex 300 + (10*11+7)*1000
25
+ test "ship example values"
26
+ assert runMain() == 117321
compiler/tests/wasm_codegen/e2e_records/through_a_base_value.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Actor(px: Int, width: Int) =
7
+ fun init(self) =
8
+ self.width = 10
9
+ fun area(self) -> Int = self.width * 2
10
+
11
+ struct Ship(Actor(px), tex: Int) =
12
+ override fun area(self) -> Int = self.width * 20
13
+
14
+ struct Rock(Actor(px))
15
+
16
+ fun total(a: Actor) -> Int = a.area() + a.px
17
+
18
+ fun runMain() -> Int =
19
+ total(Ship(px: 1, tex: 0)) * 1000 + total(Rock(px: 2))
20
+
21
+ # moved from e2e_records::record_init::through_a_base_value
22
+ # Ship: 10*20 + 1 = 201 ; Rock: 20 + 2 = 22
23
+ test "through a base value"
24
+ assert runMain() == 201022
compiler/tests/wasm_codegen/e2e_records/tree_insert_and_size.plum ADDED
@@ -0,0 +1,20 @@
1
+ import std/Str
2
+ import std/Int
3
+
4
+ enum Tree[T] = Empty | Leaf(v: Int) | Node(l: Tree[T], r: Tree[T])
5
+
6
+ fun size[T](t: Tree[T]) -> Int = match t
7
+ Empty => 0
8
+ Leaf(_) => 1
9
+ Node(l, r) => size(l) + size(r)
10
+
11
+ fun lf() -> Tree[Str] =
12
+ Leaf(v: 3)
13
+
14
+ fun runMain() -> Int =
15
+ t := Node(l: lf(), r: Node(l: lf(), r: Empty))
16
+ size(t)
17
+
18
+ # moved from e2e_records::inline_variants::tree_insert_and_size
19
+ test "tree insert and size"
20
+ assert runMain() == 2
compiler/tests/wasm_codegen/e2e_records/variant_name_wins.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Str
2
+ import std/Int
3
+
4
+ enum Link[T] =
5
+ | Nil
6
+ | Cons(head: T, tail: Link[T])
7
+
8
+ fun init(a: T, b: T) -> Link[T] = Cons(head: a, tail: Cons(head: b, tail: Nil))
9
+
10
+ fun length(self) -> Int = match self
11
+ Nil => 0
12
+ Cons(_, t) => 1 + t.length()
13
+
14
+ fun sum(self) -> Int = match self
15
+ Nil => 0
16
+ Cons(h, t) => h + t.sum()
17
+
18
+ fun runMain() -> Int =
19
+ xs := Cons(head: 5, tail: Link(1, 2))
20
+ xs.sum() * 10 + xs.length()
21
+
22
+ # moved from e2e_records::enum_init::variant_name_wins
23
+ test "variant name wins"
24
+ assert runMain() == 83
compiler/tests/wasm_codegen/e2e_tail/big_link_built_and_summed.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+
3
+ enum Link =
4
+ | Nil
5
+ | Cons(head: Int, tail: Link)
6
+
7
+ fun build(n: Int, acc: Link) -> Link = if n == 0 then acc else build(n - 1, Cons(n, acc))
8
+
9
+ fun sum(l: Link, acc: Int) -> Int = match l
10
+ Nil => acc
11
+ Cons(h, t) => sum(t, acc + h)
12
+
13
+ fun runMain() -> Int = sum(build(200000, Nil), 0)
14
+
15
+ # moved from e2e_tail::tail::big_link_built_and_summed
16
+ test "big link built and summed"
17
+ assert runMain() == 20000100000
compiler/tests/wasm_codegen/e2e_tail/break_in_else_if_chain_in_while.plum ADDED
@@ -0,0 +1,20 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun runMain() -> Int =
7
+ i := 0
8
+ while i < 50
9
+ if i == 100
10
+ i = i + 1000
11
+ else if i == 100
12
+ i = i + 2000
13
+ else if i == 7
14
+ break
15
+ i = i + 1
16
+ return i
17
+
18
+ # moved from e2e_tail::loop_break::break_in_else_if_chain_in_while
19
+ test "break in else if chain in while"
20
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_tail/break_in_if_in_match_arm_in_while.plum ADDED
@@ -0,0 +1,30 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Lst = Nil | Cons(head: Int, tail: Lst)
8
+
9
+ fun build(n: Int, acc: Lst) -> Lst =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun find(xs: Lst, target: Int) -> Int =
13
+ i := 0
14
+ cur := xs
15
+ while i < 50
16
+ match cur
17
+ Cons(h, t) =>
18
+ if h == target
19
+ break
20
+ cur = t
21
+ Nil =>
22
+ break
23
+ i = i + 1
24
+ return i
25
+
26
+ fun runMain() -> Int = find(build(10, Nil), 4) * 100 + find(build(10, Nil), 99)
27
+
28
+ # moved from e2e_tail::loop_break::break_in_if_in_match_arm_in_while
29
+ test "break in if in match arm in while"
30
+ assert runMain() == 310
compiler/tests/wasm_codegen/e2e_tail/break_in_match_arm_direct.plum ADDED
@@ -0,0 +1,37 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Lst = Nil | Cons(head: Int, tail: Lst)
8
+
9
+ fun build(n: Int, acc: Lst) -> Lst =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun count(xs: Lst) -> Int =
13
+ i := 0
14
+ cur := xs
15
+ while i < 50
16
+ match cur
17
+ Nil =>
18
+ break
19
+ Cons(h, t) =>
20
+ cur = t
21
+ i = i + 1
22
+ return i
23
+
24
+ fun wild(n: Int) -> Int =
25
+ i := 0
26
+ while i < 50
27
+ match i
28
+ _ =>
29
+ break
30
+ i = i + 1
31
+ return i + n
32
+
33
+ fun runMain() -> Int = count(build(5, Nil)) * 100 + wild(7)
34
+
35
+ # moved from e2e_tail::loop_break::break_in_match_arm_direct
36
+ test "break in match arm direct"
37
+ assert runMain() == 507
compiler/tests/wasm_codegen/e2e_tail/closure_body_tail_call_million.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+
3
+ fun countDown(n: Int) -> Int = if n == 0 then 0 else countDown(n - 1)
4
+
5
+ fun runMain() -> Int =
6
+ dec := |n: Int| if n == 0 then 7 else countDown(n - 1) + 0
7
+ fin := |n: Int| countDown(n)
8
+ fin(1000000) + dec(0)
9
+
10
+ # moved from e2e_tail::closure_tail::closure_body_tail_call_million
11
+ test "closure body tail call million"
12
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_tail/closure_captures_iteration_value.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun go(i: Int, f: fn(Int) -> Int) -> Int =
7
+ if i == 3 then f(100) else go(i + 1, |x| x + i)
8
+
9
+ fun runMain() -> Int = go(0, |x| x)
10
+
11
+ # moved from e2e_tail::tail::closure_captures_iteration_value
12
+ # R3: a closure made in one iteration keeps that iteration's parameter value.
13
+ test "closure captures iteration value"
14
+ assert runMain() == 102
compiler/tests/wasm_codegen/e2e_tail/closure_not_recursive_unchanged.plum ADDED
@@ -0,0 +1,10 @@
1
+ import std/Int
2
+
3
+ fun runMain() -> Int =
4
+ k := 10
5
+ f := |x| x + k
6
+ f(5)
7
+
8
+ # moved from e2e_tail::closure_tail::closure_not_recursive_unchanged
9
+ test "closure not recursive unchanged"
10
+ assert runMain() == 15
compiler/tests/wasm_codegen/e2e_tail/closure_tail_call_runs.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun run(n: Int, k: fn(Int) -> Int) -> Int = if n == 0 then 7 else k(n - 1)
7
+
8
+ fun go(n: Int) -> Int = run(n, go)
9
+
10
+ fun runMain() -> Int = go(1000000)
11
+
12
+ # moved from e2e_tail::tail::closure_tail_call_runs
13
+ test "closure tail call runs"
14
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_tail/continue_in_if_in_match_arm_in_while.plum ADDED
@@ -0,0 +1,33 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Lst = Nil | Cons(head: Int, tail: Lst)
8
+
9
+ fun build(n: Int, acc: Lst) -> Lst =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun sumOdd(xs: Lst) -> Int =
13
+ total := 0
14
+ i := 0
15
+ cur := xs
16
+ while i < 50
17
+ i = i + 1
18
+ match cur
19
+ Cons(h, t) =>
20
+ cur = t
21
+ if h % 2 == 0
22
+ continue
23
+ total = total + h
24
+ Nil =>
25
+ break
26
+ return total * 100 + i
27
+
28
+ fun runMain() -> Int = sumOdd(build(6, Nil))
29
+
30
+ # moved from e2e_tail::loop_break::continue_in_if_in_match_arm_in_while
31
+ # 1 + 3 + 5 = 9; seven iterations (six cons cells then Nil).
32
+ test "continue in if in match arm in while"
33
+ assert runMain() == 907
compiler/tests/wasm_codegen/e2e_tail/method_self_tail.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Counter(step: Int) =
7
+ fun count(self, n: Int, acc: Int) -> Int =
8
+ if n == 0 then acc else self.count(n - 1, acc + self.step)
9
+
10
+ fun runMain() -> Int = Counter(step: 3).count(1000000, 0)
11
+
12
+ # moved from e2e_tail::tail::method_self_tail
13
+ test "method self tail"
14
+ assert runMain() == 3000000
compiler/tests/wasm_codegen/e2e_tail/mutual_tail_million.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun isEven(n: Int) -> Int = if n == 0 then 1 else isOdd(n - 1)
7
+
8
+ fun isOdd(n: Int) -> Int = if n == 0 then 0 else isEven(n - 1)
9
+
10
+ fun runMain() -> Int = isEven(1000001) * 10 + isOdd(1000001)
11
+
12
+ # moved from e2e_tail::tail::mutual_tail_million
13
+ test "mutual tail million"
14
+ assert runMain() == 1
compiler/tests/wasm_codegen/e2e_tail/nested_loops_with_match_in_inner.plum ADDED
@@ -0,0 +1,39 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Lst = Nil | Cons(head: Int, tail: Lst)
8
+
9
+ fun build(n: Int, acc: Lst) -> Lst =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun runMain() -> Int =
13
+ outer := 0
14
+ hits := 0
15
+ while outer < 50
16
+ if outer == 3
17
+ break
18
+ cur := build(5, Nil)
19
+ j := 0
20
+ while j < 50
21
+ match cur
22
+ Cons(h, t) =>
23
+ cur = t
24
+ if h == 2
25
+ j = j + 1
26
+ continue
27
+ if h == 4
28
+ break
29
+ hits = hits + 1
30
+ Nil =>
31
+ break
32
+ j = j + 1
33
+ outer = outer + 1
34
+ return outer * 100 + hits
35
+
36
+ # moved from e2e_tail::loop_break::nested_loops_with_match_in_inner
37
+ # inner: h=1 hit, h=2 skipped, h=3 hit, h=4 break => 2 hits per outer pass, 3 passes.
38
+ test "nested loops with match in inner"
39
+ assert runMain() == 306
compiler/tests/wasm_codegen/e2e_tail/non_tail_union_wrap_not_tail.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+
3
+ import std/Option
4
+
5
+ fun inner(n: Int) -> Int = n + 1
6
+
7
+ fun outer(n: Int) -> Option[Int] = Some(inner(n))
8
+
9
+ fun runMain() -> Int =
10
+ v := outer(41)
11
+ v.unwrapOr(0)
12
+
13
+ # moved from e2e_tail::tail::non_tail_union_wrap_not_tail
14
+ # The callee returns Int but the fn returns Option[Int]: the call is wrapped on the way
15
+ # out, so it must stay a normal call and still produce the right value.
16
+ test "non tail union wrap not tail"
17
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_tail/param_self_tail.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ struct Cell(x: Int, y: Int)
7
+
8
+ fun bump(c: Cell, n: Int) -> Cell =
9
+ if n == 0 then c else bump(Cell(..c, x: c.x + 1), n - 1)
10
+
11
+ fun runMain() -> Int =
12
+ c := bump(Cell(x: 0, y: 5), 100000)
13
+ c.x * 10 + c.y
14
+
15
+ # moved from e2e_tail::tail::param_self_tail
16
+ test "param self tail"
17
+ assert runMain() == 1000005
compiler/tests/wasm_codegen/e2e_tail/question_in_tail_not_tail_call.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Int
2
+ import std/Result
3
+
4
+ enum NegError = Neg | Other
5
+
6
+ fun down(n: Int) -> Result[Int, NegError] =
7
+ if n == 0
8
+ return Ok(5)
9
+ v := down(n - 1)?
10
+ Ok(v)
11
+
12
+ fun runMain() -> Int = down(100).unwrapOr(99)
13
+
14
+ # moved from e2e_tail::error_tail::question_in_tail_not_tail_call
15
+ test "question in tail not tail call"
16
+ assert runMain() == 5
compiler/tests/wasm_codegen/e2e_tail/recursive_closure_captures_outer.plum ADDED
@@ -0,0 +1,10 @@
1
+ import std/Int
2
+
3
+ fun runMain() -> Int =
4
+ k := 3
5
+ go := |n, acc| if n == 0 then acc else go(n - 1, acc + k)
6
+ go(1000000, 0)
7
+
8
+ # moved from e2e_tail::closure_tail::recursive_closure_captures_outer
9
+ test "recursive closure captures outer"
10
+ assert runMain() == 3000000
compiler/tests/wasm_codegen/e2e_tail/recursive_local_closure_million.plum ADDED
@@ -0,0 +1,9 @@
1
+ import std/Int
2
+
3
+ fun runMain() -> Int =
4
+ go := |n, acc| if n == 0 then acc else go(n - 1, acc + n)
5
+ go(1000000, 0)
6
+
7
+ # moved from e2e_tail::closure_tail::recursive_local_closure_million
8
+ test "recursive local closure million"
9
+ assert runMain() == 500000500000
compiler/tests/wasm_codegen/e2e_tail/result_mutual_million.plum ADDED
@@ -0,0 +1,20 @@
1
+ import std/Int
2
+ import std/Result
3
+
4
+ enum NegError = Neg | Other
5
+
6
+ fun ping(n: Int) -> Result[Int, NegError] =
7
+ if n == 0
8
+ return Ok(1)
9
+ pong(n - 1)
10
+
11
+ fun pong(n: Int) -> Result[Int, NegError] =
12
+ if n == 0
13
+ return Ok(2)
14
+ ping(n - 1)
15
+
16
+ fun runMain() -> Int = ping(1000000).unwrapOr(99)
17
+
18
+ # moved from e2e_tail::error_tail::result_mutual_million
19
+ test "result mutual million"
20
+ assert runMain() == 1
compiler/tests/wasm_codegen/e2e_tail/result_self_loop_million.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+ import std/Result
3
+
4
+ enum NegError = Neg | Other
5
+
6
+ fun down(n: Int) -> Result[Int, NegError] =
7
+ if n < 0
8
+ return Err(Neg)
9
+ if n == 0
10
+ return Ok(5)
11
+ down(n - 1)
12
+
13
+ fun runMain() -> Int = down(1000000).unwrapOr(99)
14
+
15
+ # moved from e2e_tail::error_tail::result_self_loop_million
16
+ test "result self loop million"
17
+ assert runMain() == 5
compiler/tests/wasm_codegen/e2e_tail/result_self_loop_returns_err.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Int
2
+ import std/Result
3
+
4
+ enum NegError = Neg | Other
5
+
6
+ fun down(n: Int) -> Result[Int, NegError] =
7
+ if n == 0
8
+ return Err(Neg)
9
+ down(n - 1)
10
+
11
+ fun runMain() -> Int = down(1000000).unwrapOr(42)
12
+
13
+ # moved from e2e_tail::error_tail::result_self_loop_returns_err
14
+ test "result self loop returns err"
15
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_tail/self_calling_closure_typed_params_runs.plum ADDED
@@ -0,0 +1,9 @@
1
+ import std/Int
2
+
3
+ fun runMain() -> Int =
4
+ fact := |n: Int| if n <= 1 then 1 else n * fact(n - 1)
5
+ fact(5)
6
+
7
+ # moved from e2e_tail::closure_tail::self_calling_closure_typed_params_runs
8
+ test "self calling closure typed params runs"
9
+ assert runMain() == 120
compiler/tests/wasm_codegen/e2e_tail/self_calling_closure_untyped_params_runs.plum ADDED
@@ -0,0 +1,9 @@
1
+ import std/Int
2
+
3
+ fun runMain() -> Int =
4
+ fact := |n| if n <= 1 then 1 else n * fact(n - 1)
5
+ fact(5)
6
+
7
+ # moved from e2e_tail::closure_tail::self_calling_closure_untyped_params_runs
8
+ test "self calling closure untyped params runs"
9
+ assert runMain() == 120
compiler/tests/wasm_codegen/e2e_tail/self_tail_args_swap.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun gcd(a: Int, b: Int) -> Int = if b == 0 then a else gcd(b, a % b)
7
+
8
+ fun swapN(a: Int, b: Int, n: Int) -> Int =
9
+ if n == 0 then a * 1000 + b else swapN(b, a, n - 1)
10
+
11
+ fun runMain() -> Int = gcd(1071, 462) * 1000000 + swapN(1, 2, 999999)
12
+
13
+ # moved from e2e_tail::tail::self_tail_args_swap
14
+ # gcd = 21; odd swap count leaves (2, 1).
15
+ test "self tail args swap"
16
+ assert runMain() == 21002001
compiler/tests/wasm_codegen/e2e_tail/self_tail_million.plum ADDED
@@ -0,0 +1,20 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
8
+
9
+ fun build(n: Int, acc: Link[Int]) -> Link[Int] =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun sumAcc(xs: Link[Int], acc: Int) -> Int = match xs
13
+ Nil => acc
14
+ Cons(h, t) => sumAcc(t, acc + h)
15
+
16
+ fun runMain() -> Int = sumAcc(build(1000000, Nil), 0)
17
+
18
+ # moved from e2e_tail::tail::self_tail_million
19
+ test "self tail million"
20
+ assert runMain() == 500000500000
compiler/tests/wasm_codegen/e2e_tail/self_tail_through_nested_branches.plum ADDED
@@ -0,0 +1,21 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun f(n: Int, acc: Int) -> Int =
7
+ if n == 0 then acc
8
+ else if n % 3 == 0 then f(n - 1, acc + 1)
9
+ else if n % 3 == 1 then f(n - 1, acc + 10)
10
+ else f(n - 1, acc + 100)
11
+
12
+ fun g(n: Int, acc: Int) -> Int = match n
13
+ 0 => acc
14
+ k => if k % 2 == 0 then g(k - 1, acc + 2) else g(k - 1, acc + 1)
15
+
16
+ fun runMain() -> Int = f(30, 0) * 1000 + g(10, 0)
17
+
18
+ # moved from e2e_tail::tail::self_tail_through_nested_branches
19
+ # Match arms and else-if chains nest wasm blocks between the call and the loop.
20
+ test "self tail through nested branches"
21
+ assert runMain() == 1110015
compiler/tests/wasm_codegen/e2e_tail/tail_call_in_match_arm.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Int
2
+
3
+ enum Step =
4
+ | Go(n: Int)
5
+ | Stop
6
+
7
+ fun run(s: Step, acc: Int) -> Int = match s
8
+ Go(n) => run(if n == 0 then Stop else Go(n - 1), acc + 1)
9
+ Stop => acc
10
+
11
+ fun runMain() -> Int = run(Go(1000000), 0)
12
+
13
+ # moved from e2e_tail::tail::tail_call_in_match_arm
14
+ test "tail call in match arm"
15
+ assert runMain() == 1000001
compiler/tests/wasm_codegen/e2e_tail/trmc_link_map_100k.plum ADDED
@@ -0,0 +1,25 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+
7
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
8
+
9
+ fun build(n: Int, acc: Link[Int]) -> Link[Int] =
10
+ if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
11
+
12
+ fun sumAcc(xs: Link[Int], acc: Int) -> Int = match xs
13
+ Nil => acc
14
+ Cons(h, t) => sumAcc(t, acc + h)
15
+
16
+ fun mapInc(xs: Link[Int]) -> Link[Int] = match xs
17
+ Nil => Nil
18
+ Cons(h, t) => Cons(head: h + 1, tail: mapInc(t))
19
+
20
+ fun runMain() -> Int = sumAcc(mapInc(build(100000, Nil)), 0)
21
+
22
+ # moved from e2e_tail::tail::trmc_link_map_100k
23
+ # TRMC: a map over a native-variant list of 100k elements builds without stack growth.
24
+ test "trmc link map 100k"
25
+ assert runMain() == 5000150000
compiler/tests/wasm_codegen/e2e_types/adder_two_levels.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Int
2
+
3
+ fun adder(n: Int) -> fn(Int) -> fn(Int) -> Int =
4
+ |x| |y| x + y + n
5
+
6
+ fun runMain() -> Int =
7
+ f := adder(1)
8
+ g := f(2)
9
+ g(3)
10
+
11
+ # moved from e2e_types::closure_types::adder_two_levels
12
+ # `adder(1)(2)` (call on a call result) does not parse: the grammar has no call-on-call form.
13
+ test "adder two levels"
14
+ assert runMain() == 6
compiler/tests/wasm_codegen/e2e_types/bang_unwraps_and_chains.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun g(n: Int) -> Option[Pt] = Some(Pt(first: n, second: 2))
8
+
9
+ fun runMain() -> Int =
10
+ x := Some(3)!
11
+ y := g(4)!.first
12
+ r: Result[Int, Int] := Ok(5)
13
+ x + y * 10 + r! * 100
14
+
15
+ # moved from e2e_types::question::bang_unwraps_and_chains
16
+ test "bang unwraps and chains"
17
+ assert runMain() == 543
compiler/tests/wasm_codegen/e2e_types/bare_tag_fixed_by_later_arg.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+
3
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
4
+
5
+ fun len[T](xs: Link[T]) -> Int = match xs
6
+ Nil => 0
7
+ Cons(_, t) => 1 + len(t)
8
+
9
+ fun push[T](xs: Link[T], x: T) -> Link[T] = Cons(head: x, tail: xs)
10
+
11
+ fun runMain() -> Int =
12
+ len(push(push(Nil, 1), 2))
13
+
14
+ # moved from e2e_types::closure_types::bare_tag_fixed_by_later_arg
15
+ # A bare `Nil` whose `T` is fixed by a LATER argument gets the call's `Link[Int]`.
16
+ test "bare tag fixed by later arg"
17
+ assert runMain() == 2
compiler/tests/wasm_codegen/e2e_types/bare_tag_fixed_by_other_link_arg.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+
3
+ enum Link[T] = Nil | Cons(head: T, tail: Link[T])
4
+
5
+ fun len[T](xs: Link[T]) -> Int = match xs
6
+ Nil => 0
7
+ Cons(_, t) => 1 + len(t)
8
+
9
+ fun two[T](a: Link[T], b: Link[T]) -> Int = len(a) + len(b)
10
+
11
+ fun mk(n: Int) -> Link[Int] = Cons(head: n, tail: Nil)
12
+
13
+ fun runMain() -> Int =
14
+ two(Nil, mk(2)) * 10 + two(mk(1), Nil)
15
+
16
+ # moved from e2e_types::closure_types::bare_tag_fixed_by_other_link_arg
17
+ test "bare tag fixed by other link arg"
18
+ assert runMain() == 11
compiler/tests/wasm_codegen/e2e_types/bare_variants_in_if_branches_take_fn_return_type.plum ADDED
@@ -0,0 +1,26 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pickInline(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)
8
+
9
+ fun pickBlock(flag: Bool, n: Int) -> Result[Int, Int] =
10
+ if flag
11
+ Ok(n)
12
+ else
13
+ Err(100 + n)
14
+
15
+ fun maybe(flag: Bool, n: Int) -> Option[Int] = if flag then None else Some(n)
16
+
17
+ fun runMain() -> Int =
18
+ a := pickInline(false, 1).isErr()
19
+ b := pickBlock(true, 2).unwrapOr(0)
20
+ c := maybe(false, 3).unwrapOr(0)
21
+ d := maybe(true, 3).isNone()
22
+ if a && d then b * 10 + c else -1
23
+
24
+ # moved from e2e_types::question::bare_variants_in_if_branches_take_fn_return_type
25
+ test "bare variants in if branches take fn return type"
26
+ assert runMain() == 23
compiler/tests/wasm_codegen/e2e_types/bare_variants_in_match_arms_take_fn_return_type.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(n: Int) -> Result[Int, Int] = match n
8
+ 0 => Err(100)
9
+ _ => Ok(n)
10
+
11
+ fun runMain() -> Int =
12
+ match pick(0)
13
+ Ok(v) => v
14
+ Err(e) => e + pick(4).unwrapOr(0)
15
+
16
+ # moved from e2e_types::question::bare_variants_in_match_arms_take_fn_return_type
17
+ test "bare variants in match arms take fn return type"
18
+ assert runMain() == 104
compiler/tests/wasm_codegen/e2e_types/chained_err_value_is_first.plum ADDED
@@ -0,0 +1,25 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(n: Int) -> Result[Int, Int] =
8
+ if n > 0
9
+ return Ok(n)
10
+ Err(100 + n)
11
+
12
+ fun f(x: Int, y: Int, z: Int) -> Result[Int, Int] =
13
+ p := pick(x)?
14
+ q := pick(y)?
15
+ r := pick(z)?
16
+ Ok(p + q + r)
17
+
18
+ fun runMain() -> Int =
19
+ match f(1, -5, -7)
20
+ Ok(v) => v
21
+ Err(e) => e
22
+
23
+ # moved from e2e_types::question::chained_err_value_is_first
24
+ test "chained err value is first"
25
+ assert runMain() == 95
compiler/tests/wasm_codegen/e2e_types/chained_question_returns_first_err.plum ADDED
@@ -0,0 +1,26 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(n: Int) -> Result[Int, Int] =
8
+ if n > 0
9
+ return Ok(n)
10
+ Err(100 + n)
11
+
12
+ fun f(x: Int, y: Int, z: Int) -> Result[Int, Int] =
13
+ p := pick(x)?
14
+ q := pick(y)?
15
+ r := pick(z)?
16
+ Ok(p + q + r)
17
+
18
+ fun runMain() -> Int =
19
+ s := f(1, 2, 3).unwrapOr(0)
20
+ t := f(1, 0, 0)
21
+ bad := t.isErr()
22
+ if bad then s * 1000 + 1 else 0
23
+
24
+ # moved from e2e_types::question::chained_question_returns_first_err
25
+ test "chained question returns first err"
26
+ assert runMain() == 6001
compiler/tests/wasm_codegen/e2e_types/closure_coerced_into_optional_return.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+ import std/Option
3
+
4
+ fun ap(f: fn() -> Option[Int]) -> Int =
5
+ f().unwrapOr(0)
6
+
7
+ fun runMain() -> Int =
8
+ ap(|| Some(3))
9
+
10
+ # moved from e2e_types::closure_optional::closure_coerced_into_optional_return
11
+ test "closure coerced into optional return"
12
+ assert runMain() == 3
compiler/tests/wasm_codegen/e2e_types/closure_into_ref_option_and_result.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Int
2
+ import std/Str
3
+ import std/Option
4
+ import std/Result
5
+
6
+ enum BadError = Bad | Worse
7
+
8
+ fun s(f: fn(Int) -> Option[Str]) -> Str =
9
+ f(1).unwrapOr("none")
10
+
11
+ fun e(f: fn(Int) -> Result[Int, BadError]) -> Int =
12
+ f(1).unwrapOr(9)
13
+
14
+ fun runMain() -> Int =
15
+ b := s(|x| {if x > 0 then Some("yes") else None}).length()
16
+ b2 := s(|x| Some("ab")).length()
17
+ c := e(|x| Ok(x + 1))
18
+ d := e(|x| Err(Bad))
19
+ b * 1000 + b2 * 100 + c * 10 + d
20
+
21
+ # moved from e2e_types::closure_optional::closure_into_ref_option_and_result
22
+ test "closure into ref option and result"
23
+ assert runMain() == 3229
compiler/tests/wasm_codegen/e2e_types/closure_passes_none_to_an_option_param.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ import std/Option
7
+
8
+ fun runMain() -> Int =
9
+ rec := |o: Option[Int], acc: Int, n: Int|
10
+ if n <= 0 then acc else rec(None, acc + 1, n - 1)
11
+ rec(None, 0, 3)
12
+
13
+ # moved from e2e_types::synthetic_sites::closure_passes_none_to_an_option_param
14
+ # F-T9-2: a closure calling itself with `None` for an `Option[Int]` param. The checker
15
+ # types that `None` as `Option[Unit]`; the param's declared type picks the singleton, so the
16
+ # wasm is valid and the value right.
17
+ test "closure passes none to an option param"
18
+ assert runMain() == 3
compiler/tests/wasm_codegen/e2e_types/closure_returning_none_into_optional.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+ import std/Option
3
+
4
+ fun ap(f: fn() -> Option[Int]) -> Int =
5
+ f().unwrapOr(7)
6
+
7
+ fun runMain() -> Int =
8
+ ap(|| None)
9
+
10
+ # moved from e2e_types::closure_optional::closure_returning_none_into_optional
11
+ test "closure returning none into optional"
12
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_types/curried_closure_runs.plum ADDED
@@ -0,0 +1,13 @@
1
+ import std/Int
2
+
3
+ fun curry() -> fn(Int) -> fn(Int) -> Int =
4
+ |x| |y| x + y
5
+
6
+ fun runMain() -> Int =
7
+ add2 := curry()
8
+ f := add2(2)
9
+ f(3)
10
+
11
+ # moved from e2e_types::closure_types::curried_closure_runs
12
+ test "curried closure runs"
13
+ assert runMain() == 5
compiler/tests/wasm_codegen/e2e_types/err_field_is_error.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun runMain() -> Int =
8
+ r: Result[Int, Int] := Err(error: 5)
9
+ match r
10
+ Ok(v) => v
11
+ Err(e) => e
12
+
13
+ # moved from e2e_types::question::err_field_is_error
14
+ test "err field is error"
15
+ assert runMain() == 5
compiler/tests/wasm_codegen/e2e_types/field_less_variant_called_like_a_constructor.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ enum Maybe =
7
+ | Nothing
8
+ | Just(value: Int)
9
+
10
+ fun orZero(m: Maybe) -> Int = match m
11
+ Nothing => 0
12
+ Just(v) => v
13
+
14
+ fun runMain() -> Int =
15
+ a: Maybe := Nothing()
16
+ b: Maybe := Just(value: 40)
17
+ orZero(a) + orZero(b) + 2
18
+
19
+ # moved from e2e_types::synthetic_sites::field_less_variant_called_like_a_constructor
20
+ # `Nothing()` (call syntax on a field-less variant) is the variant's
21
+ # singleton, typed by the call node.
22
+ test "field less variant called like a constructor"
23
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_types/field_less_variant_of_a_generic_enum_called_like_a_constructor.plum ADDED
@@ -0,0 +1,22 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ enum Maybe[T] =
7
+ | Nothing
8
+ | Just(value: T)
9
+
10
+ fun orZero(m: Maybe[Int]) -> Int = match m
11
+ Nothing => 0
12
+ Just(v) => v
13
+
14
+ fun runMain() -> Int =
15
+ a: Maybe[Int] := Nothing()
16
+ b: Maybe[Int] := Just(value: 40)
17
+ orZero(a) + orZero(b) + 2
18
+
19
+ # moved from e2e_types::synthetic_sites::field_less_variant_of_a_generic_enum_called_like_a_constructor
20
+ # The same call syntax on a variant of a generic enum: the call has the enum's instantiated type.
21
+ test "field less variant of a generic enum called like a constructor"
22
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_types/fn_field_returning_optional.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Int
2
+ import std/Option
3
+
4
+ struct Gen(next: fn() -> Option[Int])
5
+
6
+ fun count(g: Gen) -> Int =
7
+ g.next().unwrapOr(0)
8
+
9
+ fun runMain() -> Int =
10
+ k := 5
11
+ count(Gen(next: || Some(k)))
12
+
13
+ # moved from e2e_types::closure_optional::fn_field_returning_optional
14
+ test "fn field returning optional"
15
+ assert runMain() == 5
compiler/tests/wasm_codegen/e2e_types/g1_typed_param.plum ADDED
@@ -0,0 +1,13 @@
1
+ import std/Int
2
+
3
+ struct Box(v: Int) =
4
+ fun get(self) -> Int = self.v
5
+
6
+ fun ap(f: fn(Box) -> Int) -> Int = f(Box(v: 2))
7
+
8
+ fun runMain() -> Int =
9
+ ap(|b: Box| b.get())
10
+
11
+ # moved from e2e_types::closure_types::g1_typed_param
12
+ test "g1 typed param"
13
+ assert runMain() == 2
compiler/tests/wasm_codegen/e2e_types/g2_local_closure_inferred_from_first_call.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+
3
+ struct Box(v: Int) =
4
+ fun get(self) -> Int = self.v
5
+
6
+ fun runMain() -> Int =
7
+ get := |b| b.get()
8
+ get(Box(v: 2))
9
+
10
+ # moved from e2e_types::closure_types::g2_local_closure_inferred_from_first_call
11
+ test "g2 local closure inferred from first call"
12
+ assert runMain() == 2
compiler/tests/wasm_codegen/e2e_types/g5_returned_closure.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+
3
+ fun adder(n: Int) -> fn(Int) -> Int =
4
+ |x| x + n
5
+
6
+ fun runMain() -> Int =
7
+ add1 := adder(1)
8
+ add1(2)
9
+
10
+ # moved from e2e_types::closure_types::g5_returned_closure
11
+ test "g5 returned closure"
12
+ assert runMain() == 3
compiler/tests/wasm_codegen/e2e_types/inline_if_joins_bare_variant_instantiations.plum ADDED
@@ -0,0 +1,19 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun runMain() -> Int =
8
+ flag := 3 > 2
9
+ o := if flag then Ok(4) else Err(5)
10
+ low := if flag then Err(1) else Ok(Pt(first: 1, second: 2))
11
+ a := o.unwrapOr(0)
12
+ match low
13
+ Ok(pt) => pt.first
14
+ Err(e) => a * 10 + e
15
+
16
+ # moved from e2e_types::question::inline_if_joins_bare_variant_instantiations
17
+ # An unannotated inline if: each bare variant pins the generic the other leaves open.
18
+ test "inline if joins bare variant instantiations"
19
+ assert runMain() == 41
compiler/tests/wasm_codegen/e2e_types/inlined_constant_call_keeps_return_type.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(flag: Bool, n: Int) -> Result[Pt, Int] = if flag then Ok(Pt(first: n, second: 1)) else Err(100 + n)
8
+
9
+ fun runMain() -> Int =
10
+ match pick(false, 1)
11
+ Ok(pt) => pt.first
12
+ Err(e) => e
13
+
14
+ # moved from e2e_types::question::inlined_constant_call_keeps_return_type
15
+ # Fusion inlines `pick(false, 1)` to a bare `Err(101)` and drops `Result[Pt, Int]`
16
+ # (plum-core simplify), so codegen builds `Ok`'s payload as Unit.
17
+ test "inlined constant call keeps return type"
18
+ assert runMain() == 101
compiler/tests/wasm_codegen/e2e_types/list_literal_element_pinned_by_a_later_element.plum ADDED
@@ -0,0 +1,37 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/List
4
+
5
+ enum Node[M] =
6
+ | Box(w: Int)
7
+ | Tag(msg: M, w: Int)
8
+ | Group(kids: List[Node[M]])
9
+ | Wrap(child: Node[M], k: Int)
10
+
11
+ fun total(self) -> Int = match self
12
+ Box(w) => w
13
+ Tag(_, w) => w
14
+ Group(kids) => Node.sumKids(kids)
15
+ Wrap(child, k) => Node.twice(child, k)
16
+
17
+ fun twice(child: Node[M], k: Int) -> Int = child.total() + child.total() + k
18
+
19
+ fun sumKids(kids: List[Node[M]]) -> Int = match kids
20
+ Nil => 0
21
+ Cons(head, tail) => head.total() + Node.sumKids(tail)
22
+
23
+ # A second instantiation, so a wrongly pinned element picks a different type.
24
+ fun other(n: Node[Int]) -> Int = n.total()
25
+
26
+ fun tagged(s: Str) -> Node[Str] = Tag(msg: s, w: 2)
27
+
28
+ fun view(s: Str) -> Node[Str] =
29
+ t := tagged(s)
30
+ Wrap(child: Group(kids: [Box(w: 1), t, Box(w: 3)]), k: 100)
31
+
32
+ fun runMain() -> Int =
33
+ view("x").total() + other(Box(w: 10))
34
+
35
+ # moved from e2e_types::list_and_shadow::list_literal_element_pinned_by_a_later_element
36
+ test "list literal element pinned by a later element"
37
+ assert runMain() == 122
compiler/tests/wasm_codegen/e2e_types/map_err_closure_param_shadows_top_level_fn.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun f(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)
8
+
9
+ fun runMain() -> Int =
10
+ match f(false, 1).mapErr(|e| e + 1000)
11
+ Ok(v) => v
12
+ Err(e) => e
13
+
14
+ # moved from e2e_types::question::map_err_closure_param_shadows_top_level_fn
15
+ # mapErr's closure param `f` must not take the arity of a top-level `fun f`.
16
+ test "map err closure param shadows top level fn"
17
+ assert runMain() == 1101
compiler/tests/wasm_codegen/e2e_types/map_err_on_call_result.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun fails() -> Result[Int, Int] =
8
+ r: Result[Int, Int] := Err(7)
9
+ r
10
+
11
+ fun runMain() -> Int =
12
+ match fails().mapErr(|e| e + 1000)
13
+ Ok(v) => v
14
+ Err(e) => e
15
+
16
+ # moved from e2e_types::question::map_err_on_call_result
17
+ test "map err on call result"
18
+ assert runMain() == 1007
compiler/tests/wasm_codegen/e2e_types/map_err_then_unwrap_or_fuses.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)
8
+
9
+ fun runMain() -> Int =
10
+ pick(true, 2).mapErr(|e| e + 1).unwrapOr(0)
11
+
12
+ # moved from e2e_types::question::map_err_then_unwrap_or_fuses
13
+ # Simplify inlines `pick(true, 2)` to `Ok(2)`; drive then fuses `unwrapOr` over `mapErr`
14
+ # into a fn generic in `E`, which a bare `Ok(2)` never binds (no instance is emitted).
15
+ test "map err then unwrap or fuses"
16
+ assert runMain() == 2
compiler/tests/wasm_codegen/e2e_types/nested_generic.plum ADDED
@@ -0,0 +1,21 @@
1
+ import std/Int
2
+ import std/Slice
3
+ import std/List
4
+
5
+ struct Pt(x: Int) =
6
+ fun make(n: Int) -> Pt = Pt(x: n + 1)
7
+
8
+ fun buildAll[T](ns: Slice[Int]) -> Slice[T] =
9
+ out := Slice[T]()
10
+ for n := range ns
11
+ out.push(T.make(n))
12
+ return out
13
+
14
+ fun runMain() -> Int =
15
+ ps: Slice[Pt] := buildAll([1, 2, 3].toSlice())
16
+ p := ps.get(2).unwrapOr(Pt(x: 0))
17
+ return p.x
18
+
19
+ # moved from e2e_types::type_param_static::nested_generic
20
+ test "nested generic"
21
+ assert runMain() == 4
compiler/tests/wasm_codegen/e2e_types/none_as_generic_param_default.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun g[T](x: T, o: Option[T] = None) -> Option[T] = o
8
+
9
+ fun runMain() -> Int =
10
+ a := g(1).unwrapOr(7)
11
+ b := g(1, Some(3)).unwrapOr(0)
12
+ a * 10 + b
13
+
14
+ # moved from e2e_types::question::none_as_generic_param_default
15
+ test "none as generic param default"
16
+ assert runMain() == 73
compiler/tests/wasm_codegen/e2e_types/none_as_param_default.plum ADDED
@@ -0,0 +1,16 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun f(n: Option[Int] = None) -> Option[Int] = n
8
+
9
+ fun runMain() -> Int =
10
+ a := f().unwrapOr(7)
11
+ b := f(Some(3)).unwrapOr(0)
12
+ a * 10 + b
13
+
14
+ # moved from e2e_types::question::none_as_param_default
15
+ test "none as param default"
16
+ assert runMain() == 73
compiler/tests/wasm_codegen/e2e_types/ok_unit_and_expression_position.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun g(n: Int) -> Option[Int] = if n > 0 then Some(n) else None
8
+
9
+ fun u(n: Int) -> Result[Unit, Int] =
10
+ if n > 0
11
+ return Ok(Unit)
12
+ Err(n)
13
+
14
+ fun h(n: Int) -> Option[Int] = Some(1 + g(n)?)
15
+
16
+ fun runMain() -> Int =
17
+ a := h(5).unwrapOr(0)
18
+ b := u(1).isOk()
19
+ c := u(0).isErr()
20
+ if b && c then a else -1
21
+
22
+ # moved from e2e_types::question::ok_unit_and_expression_position
23
+ test "ok unit and expression position"
24
+ assert runMain() == 6
compiler/tests/wasm_codegen/e2e_types/option_question_returns_none.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun g(n: Int) -> Option[Int] = if n > 0 then Some(n) else None
8
+
9
+ fun f(n: Int) -> Option[Int] =
10
+ v := g(n)?
11
+ Some(v * 2)
12
+
13
+ fun runMain() -> Int =
14
+ f(4).unwrapOr(-1) * 100 + f(0).unwrapOr(7)
15
+
16
+ # moved from e2e_types::question::option_question_returns_none
17
+ test "option question returns none"
18
+ assert runMain() == 807
compiler/tests/wasm_codegen/e2e_types/reused_cell_is_read_through_its_local.plum ADDED
@@ -0,0 +1,23 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ enum Link =
7
+ | Nil
8
+ | Cons(head: Int, tail: Link)
9
+
10
+ fun bump(xs: Link) -> Link = match xs
11
+ Nil => Nil
12
+ Cons(h, t) => Cons(head: h + 1, tail: t)
13
+
14
+ fun sum(xs: Link) -> Int = match xs
15
+ Nil => 0
16
+ Cons(h, t) => h + sum(t)
17
+
18
+ fun runMain() -> Int = sum(bump(Cons(head: 1, tail: Cons(head: 2, tail: Nil))))
19
+
20
+ # moved from e2e_types::synthetic_sites::reused_cell_is_read_through_its_local
21
+ # A rebuild that reuses the matched cell reads the cell's type off the local it names.
22
+ test "reused cell is read through its local"
23
+ assert runMain() == 4
compiler/tests/wasm_codegen/e2e_types/seq_with_optional_next.plum ADDED
@@ -0,0 +1,19 @@
1
+ import std/Int
2
+ import std/Option
3
+
4
+ struct Seq[T](next: fn(Int) -> Option[T]) =
5
+ fun upTo(n: Int) -> Seq[Int] = Seq(next: |i| {if i < n then Some(i * 2) else None})
6
+ fun sum(self) -> Int =
7
+ total := 0
8
+ i := 0
9
+ while Some(v) := self.next(i)
10
+ total = total + v
11
+ i = i + 1
12
+ total
13
+
14
+ fun runMain() -> Int =
15
+ Seq.upTo(4).sum()
16
+
17
+ # moved from e2e_types::closure_optional::seq_with_optional_next
18
+ test "seq with optional next"
19
+ assert runMain() == 12
compiler/tests/wasm_codegen/e2e_types/single_letter_types_and_variants.plum ADDED
@@ -0,0 +1,24 @@
1
+ import std/Int
2
+
3
+ struct P(x: Int)
4
+
5
+ enum Two = A | B
6
+
7
+ struct Cfg(n: Int) =
8
+ E = 5
9
+
10
+ fun pick(t: Two) -> Int = match t
11
+ A => 1
12
+ B => 20
13
+
14
+ fun id[Elem](x: Elem) -> Elem = x
15
+
16
+ fun runMain() -> Int =
17
+ p := P(x: id(3))
18
+ pick(A) + pick(B) + p.x + Cfg.E
19
+
20
+ # moved from e2e_types::type_param_static::single_letter_types_and_variants
21
+ # Single uppercase letters are ordinary names: a struct, enum variants, a constant, and a
22
+ # declared multi-letter fn generic all work side by side.
23
+ test "single letter types and variants"
24
+ assert runMain() == 29
compiler/tests/wasm_codegen/e2e_types/specialised_arm_keeps_result_type.plum ADDED
@@ -0,0 +1,26 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun pick(flag: Bool, n: Int) -> Result[Pt, Int] = if flag then Ok(Pt(first: n, second: 1)) else Err(100 + n)
8
+
9
+ fun viaMapErr() -> Int =
10
+ match pick(false, 2).mapErr(|e| e + 1)
11
+ Ok(pt) => pt.first
12
+ Err(e) => e
13
+
14
+ fun viaOk() -> Int =
15
+ r := pick(false, 4)
16
+ match r.ok()
17
+ Some(pt) => pt.first
18
+ None => 8
19
+
20
+ fun runMain() -> Int = viaMapErr() * 10 + viaOk()
21
+
22
+ # moved from e2e_types::question::specialised_arm_keeps_result_type
23
+ # Drive specialises `mapErr` / `ok` on the inlined `Err(..)`: the picked arm's own `Err` /
24
+ # `None` leaves `mapErr` / `ok` and must keep its `Result[Pt, Int]` / `Option[Pt]`.
25
+ test "specialised arm keeps result type"
26
+ assert runMain() == 1038
compiler/tests/wasm_codegen/e2e_types/static_method_of_generic_type_not_pinning_t.plum ADDED
@@ -0,0 +1,12 @@
1
+ import std/Int
2
+
3
+ struct Gen[T](v: T) =
4
+ fun ints(n: Int) -> Gen[Int] = Gen(v: n)
5
+ fun get(self) -> T = self.v
6
+
7
+ fun runMain() -> Int =
8
+ Gen.ints(4).get()
9
+
10
+ # moved from e2e_types::closure_optional::static_method_of_generic_type_not_pinning_t
11
+ test "static method of generic type not pinning t"
12
+ assert runMain() == 4
compiler/tests/wasm_codegen/e2e_types/static_on_builtin.plum ADDED
@@ -0,0 +1,11 @@
1
+ import std/Int
2
+
3
+ fun maxOf[T](n: Int) -> T = T.Max
4
+
5
+ fun runMain() -> Int =
6
+ m: Int := maxOf(1)
7
+ return if m == Int.Max then 1 else 0
8
+
9
+ # moved from e2e_types::type_param_static::static_on_builtin
10
+ test "static on builtin"
11
+ assert runMain() == 1
compiler/tests/wasm_codegen/e2e_types/static_on_builtin_method_and_json_cycle.plum ADDED
@@ -0,0 +1,17 @@
1
+ import std/Int
2
+ import std/Str
3
+ import std/Json
4
+ import std/Result
5
+
6
+ fun parseIt[T](s: Str) -> Result[T, NumError] =
7
+ T.parse(s)
8
+
9
+ fun runMain() -> Int =
10
+ r: Result[Int, NumError] := parseIt("41")
11
+ n := r.unwrapOr(0)
12
+ j := n.toJson()
13
+ return n + 1
14
+
15
+ # moved from e2e_types::type_param_static::static_on_builtin_method_and_json_cycle
16
+ test "static on builtin method and json cycle"
17
+ assert runMain() == 42
compiler/tests/wasm_codegen/e2e_types/static_on_type_param.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Int
2
+
3
+ struct Pt(x: Int) =
4
+ fun make(n: Int) -> Pt = Pt(x: n * 10)
5
+
6
+ fun build[T](n: Int) -> T = T.make(n)
7
+
8
+ fun runMain() -> Int =
9
+ p: Pt := build(3)
10
+ return p.x
11
+
12
+ # moved from e2e_types::type_param_static::static_on_type_param
13
+ test "static on type param"
14
+ assert runMain() == 30
compiler/tests/wasm_codegen/e2e_types/struct_payload_in_some.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Int
2
+ import std/Option
3
+ import std/Result
4
+
5
+ struct Pt(first: Int, second: Int)
6
+
7
+ fun g(n: Int) -> Option[Pt] = if n > 0 then Some(Pt(first: n, second: 2)) else None
8
+
9
+ fun f(n: Int) -> Option[Int] =
10
+ p := g(n)?
11
+ Some(p.first * 10 + p.second)
12
+
13
+ fun runMain() -> Int =
14
+ f(3).unwrapOr(0)
15
+
16
+ # moved from e2e_types::question::struct_payload_in_some
17
+ test "struct payload in some"
18
+ assert runMain() == 32
compiler/tests/wasm_codegen/e2e_types/unannotated_local_of_list_pinned_by_a_later_element.plum ADDED
@@ -0,0 +1,39 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/List
4
+
5
+ enum Node[M] =
6
+ | Box(w: Int)
7
+ | Tag(msg: M, w: Int)
8
+ | Group(kids: List[Node[M]])
9
+ | Wrap(child: Node[M], k: Int)
10
+
11
+ fun total(self) -> Int = match self
12
+ Box(w) => w
13
+ Tag(_, w) => w
14
+ Group(kids) => Node.sumKids(kids)
15
+ Wrap(child, k) => Node.twice(child, k)
16
+
17
+ fun twice(child: Node[M], k: Int) -> Int = child.total() + child.total() + k
18
+
19
+ fun sumKids(kids: List[Node[M]]) -> Int = match kids
20
+ Nil => 0
21
+ Cons(head, tail) => head.total() + Node.sumKids(tail)
22
+
23
+ # A second instantiation, so a wrongly pinned element picks a different type.
24
+ fun other(n: Node[Int]) -> Int = n.total()
25
+
26
+ fun tagged(s: Str) -> Node[Str] = Tag(msg: s, w: 2)
27
+
28
+ fun view(s: Str) -> Int =
29
+ t := tagged(s)
30
+ g := Group(kids: [Box(w: 1), t])
31
+ q := g
32
+ g.total() + q.total()
33
+
34
+ fun runMain() -> Int =
35
+ view("x") + other(Box(w: 10))
36
+
37
+ # moved from e2e_types::list_and_shadow::unannotated_local_of_list_pinned_by_a_later_element
38
+ test "unannotated local of list pinned by a later element"
39
+ assert runMain() == 16
compiler/tests/wasm_codegen/e2e_types/unit_closure_local_called_as_a_statement.plum ADDED
@@ -0,0 +1,15 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ fun runMain() -> Int =
7
+ f := || Unit
8
+ f()
9
+ f()
10
+ 7
11
+
12
+ # moved from e2e_types::synthetic_sites::unit_closure_local_called_as_a_statement
13
+ # A call through a local closure whose type is only known from the local's own initialiser.
14
+ test "unit closure local called as a statement"
15
+ assert runMain() == 7
compiler/tests/wasm_codegen/e2e_types/untyped_closure_passes_none_to_an_option_param.plum ADDED
@@ -0,0 +1,18 @@
1
+ import std/Str
2
+ import std/Int
3
+ import std/Float
4
+ import std/Slice
5
+
6
+ import std/Option
7
+
8
+ fun run(n: Int, o: Option[Int]) -> Int =
9
+ rec := |o, acc, n|
10
+ if n <= 0 then 0 else rec(None, acc + 1, n - 1)
11
+ rec(o, 0, n)
12
+
13
+ fun runMain() -> Int =
14
+ run(3, None)
15
+
16
+ # moved from e2e_types::synthetic_sites::untyped_closure_passes_none_to_an_option_param
17
+ test "untyped closure passes none to an option param"
18
+ assert runMain() == 0
compiler/tests/wasm_codegen/e2e_types/user_type_shadows_std_pair_imported_by_std.plum ADDED
@@ -0,0 +1,14 @@
1
+ import std/Int
2
+
3
+ struct Pair(a: Int, b: Int) =
4
+ fun sum(self) -> Int = self.a + self.b
5
+
6
+ fun runMain() -> Int =
7
+ p := Pair(a: 3, b: 4)
8
+ for i := range 3
9
+ p = Pair(a: p.a + i, b: p.b)
10
+ p.a * 10 + p.sum()
11
+
12
+ # moved from e2e_types::list_and_shadow::user_type_shadows_std_pair_imported_by_std
13
+ test "user type shadows std pair imported by std"
14
+ assert runMain() == 70
compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs CHANGED
@@ -38,43 +38,6 @@ fn unique_spread_reuses_object() {
38
38
  assert_eq!(runInt("reuse_run", &unique), 207);
39
39
  }
40
40
 
41
- #[test]
42
- fn plain_spread_still_copies() {
43
- let src = prog("struct Cell(x: Int, y: Int)
44
-
45
- fun main() -> Int =
46
- s := Cell(x: 1, y: 7)
47
- t := Cell(..s, x: 5)
48
- s.x * 1000 + t.x * 100 + t.y
49
- ");
50
- assert_eq!(runInt("plain", &src), 1000 + 500 + 7);
51
- }
52
-
53
- #[test]
54
- fn unique_spread_sets_fields() {
55
- let src = prog("struct Cell(x: Int, y: Int, z: Int)
56
-
57
- fun main() -> Int =
58
- s := Cell(x: 1, y: 2, z: 3)
59
- t := Cell(..s, z: 30, x: 10)
60
- t.x * 10000 + t.y * 100 + t.z
61
- ");
62
- assert_eq!(runInt("sets", &src), 100000 + 200 + 30);
63
- }
64
-
65
- /// A value reading an already-overwritten field must still see the OLD value.
66
- #[test]
67
- fn unique_spread_swap_reads_old_values() {
68
- let src = prog("struct Cell(x: Int, y: Int)
69
-
70
- fun main() -> Int =
71
- s := Cell(x: 1, y: 2)
72
- t := Cell(..s, x: s.y, y: s.x)
73
- t.x * 10 + t.y
74
- ");
75
- assert_eq!(runInt("swap", &src), 21);
76
- }
77
-
78
41
  #[test]
79
42
  fn unique_base_record_spread_in_place() {
80
43
  let unique = prog("struct Actor() =\n fun go(self) -> Int = 7\n\nstruct Ship(Actor, x: Int, hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
@@ -90,63 +53,6 @@ fn unique_base_field_record_spread_in_place() {
90
53
  assert!(spreadIsUnique("unique_base_field_flag", "hit", &unique));
91
54
  }
92
55
 
93
- #[test]
94
- fn unique_self_field_write() {
95
- let src = prog("struct Cell(mut x: Int) =
96
- fun bump(self) -> Cell =
97
- self.x = self.x + 1
98
- self
99
-
100
- fun main() -> Int =
101
- s := Cell(x: 41)
102
- t := s.bump()
103
- t.x
104
- ");
105
- assert_eq!(runInt("selfwrite", &src), 42);
106
- }
107
-
108
- /// A later value that reads a field an earlier one overwrote, or uses the source
109
- /// any other way (here a method call), must still run (copy fallback).
110
- #[test]
111
- fn unique_spread_hazard_falls_back_to_copy() {
112
- let src = prog("struct Cell(x: Int, y: Int) =
113
- fun sum(self) -> Int = self.x + self.y
114
-
115
- fun main() -> Int =
116
- s := Cell(x: 1, y: 2)
117
- t := Cell(..s, x: 10, y: s.sum())
118
- t.x * 100 + t.y
119
- ");
120
- assert_eq!(runInt("hazard_call", &src), 1000 + 3);
121
- let src = prog("struct Cell(x: Int, y: Int, z: Int)
122
-
123
- fun main() -> Int =
124
- s := Cell(x: 1, y: 2, z: 3)
125
- t := Cell(..s, x: 10, z: s.y + s.z)
126
- t.x * 100 + t.y * 10 + t.z
127
- ");
128
- assert_eq!(runInt("hazard_ok_read", &src), 1000 + 20 + 5);
129
- }
130
-
131
- /// R5: arms binding differently named locals of different record types.
132
- #[test]
133
- fn match_arm_rename_still_valid_wasm() {
134
- let src = prog("struct Pa(x: Int)
135
- struct Pb(y: Int, z: Int)
136
-
137
- enum Pick = Wa(v: Pa) | Wb(v: Pb)
138
-
139
- fun go(p: Pick) -> Int =
140
- match p
141
- Wa(a) => a.x
142
- Wb(b) => b.y * 10 + b.z
143
-
144
- fun main() -> Int =
145
- go(Wa(v: Pa(x: 4))) * 100 + go(Wb(v: Pb(y: 5, z: 6)))
146
- ");
147
- assert_eq!(runInt("match_rename", &src), 400 + 56);
148
- }
149
-
150
56
  /// F-T6-3: `s = Cell(..s, ...)` on a param must reassign the param's own slot, not
151
57
  /// a fresh shadow local that every read of `s` (the spread source included) sees unset.
152
58
  #[test]
@@ -169,28 +75,3 @@ fun main() -> Int =
169
75
  assert_eq!(runInt("reassign_param", &unique), 41000 + 207);
170
76
  assert!(spreadIsUnique("reassign_param_flag", "bump", &unique));
171
77
  }
172
-
173
- #[test]
174
- fn unique_spread_reassign_local() {
175
- let src = prog("struct Cell(x: Int, y: Int)
176
-
177
- fun main() -> Int =
178
- s := Cell(x: 1, y: 7)
179
- s = Cell(..s, x: s.x + 1)
180
- s = Cell(..s, y: s.y + s.x)
181
- s.x * 100 + s.y
182
- ");
183
- assert_eq!(runInt("reassign_local", &src), 200 + 9);
184
- }
185
-
186
- #[test]
187
- fn unique_self_spread_return() {
188
- let src = prog("struct Cell(x: Int, y: Int) =
189
- fun step(self) -> Cell = Cell(..self, x: self.x + 1)
190
-
191
- fun main() -> Int =
192
- t := Cell(x: 1, y: 7).step().step()
193
- t.x * 100 + t.y
194
- ");
195
- assert_eq!(runInt("self_spread", &src), 300 + 7);
196
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/enum_init.rs CHANGED
@@ -1,56 +1,8 @@
1
- use plum_test_support::{load, runInt};
2
- use plum_test_support::compileSource;
1
+ use plum_test_support::{compileSource, load};
3
- const LINK: &str = "enum Link[T] =
4
- | Nil
5
- | Cons(head: T, tail: Link[T])
6
-
7
- fun init(a: T, b: T) -> Link[T] = Cons(head: a, tail: Cons(head: b, tail: Nil))
8
-
9
- fun length(self) -> Int = match self
10
- Nil => 0
11
- Cons(_, t) => 1 + t.length()
12
-
13
- fun sum(self) -> Int = match self
14
- Nil => 0
15
- Cons(h, t) => h + t.sum()
16
-
17
- ";
18
-
19
2
  fn prog(body: &str) -> String {
20
3
  format!("import std/Str\nimport std/Int\n\n{body}")
21
4
  }
22
5
 
23
- #[test]
24
- fn empty_init_with_annotation() {
25
- let link0 = LINK.replace(
26
- "fun init(a: T, b: T) -> Link[T] = Cons(head: a, tail: Cons(head: b, tail: Nil))",
27
- "fun init() -> Link[T] = Nil",
28
- );
29
- let src = prog(&format!("{link0}fun main() -> Int =
30
- xs: Link[Int] := Link()
31
- xs.length() + 7
32
- "));
33
- assert_eq!(runInt("empty", &src), 7);
34
- }
35
-
36
- #[test]
37
- fn fixed_param_init() {
38
- let src = prog("enum Pair2 =
39
- | Zero
40
- | Two(a: Int, b: Int)
41
-
42
- fun init(a: Int, b: Int) -> Pair2 = Two(a: a, b: b)
43
-
44
- fun total(self) -> Int = match self
45
- Zero => 0
46
- Two(a, b) => a + b
47
-
48
- fun main() -> Int =
49
- Pair2(3, 4).total()
50
- ");
51
- assert_eq!(runInt("fixed", &src), 7);
52
- }
53
-
54
6
  #[test]
55
7
  fn no_init_error_message() {
56
8
  let src = prog("enum Shape =
@@ -73,22 +25,3 @@ fun main() -> Int =
73
25
  "{err}"
74
26
  );
75
27
  }
76
-
77
- #[test]
78
- fn variant_name_wins() {
79
- let src = prog(&format!("{LINK}fun main() -> Int =
80
- xs := Cons(head: 5, tail: Link(1, 2))
81
- xs.sum() * 10 + xs.length()
82
- "));
83
- assert_eq!(runInt("variantwins", &src), 83);
84
- }
85
-
86
- #[test]
87
- fn generic_inferred_from_args() {
88
- let src = prog(&format!("{LINK}fun total(xs: Link[Int]) -> Int = xs.sum()
89
-
90
- fun main() -> Int =
91
- total(Link(5, 6)) + Link(\"a\", \"b\").length()
92
- "));
93
- assert_eq!(runInt("infer", &src), 13);
94
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/inline_variants.rs CHANGED
@@ -1,78 +1,6 @@
1
- use plum_test_support::{load, runInt};
1
+ use plum_test_support::{load};
2
2
 
3
3
 
4
- const HEAD: &str = "\
5
- import std/Str
6
- import std/Int
7
- import std/Slice
8
-
9
- enum Link[T] = Nil | Cons(head: T, tail: Link[T])
10
-
11
- fun length[T](xs: Link[T]) -> Int = match xs
12
- Nil => 0
13
- Cons(_, t) => 1 + length(t)
14
-
15
- fun sum(xs: Link[Int]) -> Int = match xs
16
- Nil => 0
17
- Cons(h, t) => h + sum(t)
18
-
19
- fun cons[T](h: T, t: Link[T]) -> Link[T] =
20
- Cons(head: h, tail: t)
21
-
22
- fun map(xs: Link[Int], f: fn(Int) -> Int) -> Link[Int] = match xs
23
- Nil => Nil
24
- Cons(h, t) => Cons(head: f(h), tail: map(t, f))
25
-
26
- ";
27
-
28
- #[test]
29
- fn cons_list_length_map_sum() {
30
- let src = format!(
31
- "{HEAD}\
32
- fun main() -> Int =
33
- xs := cons(1, cons(2, cons(3, Nil)))
34
- ys := map(xs, |n| n * 2)
35
- sum(xs) * 100 + sum(ys) + length(ys) * 10000
36
- "
37
- );
38
- assert_eq!(runInt("cons", &src), 30000 + 600 + 12);
39
- }
40
-
41
- #[test]
42
- fn nested_literal_construction() {
43
- let src = format!(
44
- "{HEAD}\
45
- fun main() -> Int =
46
- xs := Cons(head: 1, tail: Cons(head: 2, tail: Cons(head: 3, tail: Nil)))
47
- sum(xs)
48
- "
49
- );
50
- assert_eq!(runInt("nested", &src), 6);
51
- }
52
-
53
- #[test]
54
- fn tree_insert_and_size() {
55
- let src = "\
56
- import std/Str
57
- import std/Int
58
-
59
- enum Tree[T] = Empty | Leaf(v: Int) | Node(l: Tree[T], r: Tree[T])
60
-
61
- fun size[T](t: Tree[T]) -> Int = match t
62
- Empty => 0
63
- Leaf(_) => 1
64
- Node(l, r) => size(l) + size(r)
65
-
66
- fun lf() -> Tree[Str] =
67
- Leaf(v: 3)
68
-
69
- fun main() -> Int =
70
- t := Node(l: lf(), r: Node(l: lf(), r: Empty))
71
- size(t)
72
- ";
73
- assert_eq!(runInt("tree", src), 2);
74
- }
75
-
76
4
  #[test]
77
5
  fn union_enum_is_an_error() {
78
6
  let src = "\
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/mod.rs CHANGED
@@ -1,7 +1,5 @@
1
1
  #![allow(non_snake_case)]
2
- mod empty_record_embed;
3
2
  mod enum_init;
4
3
  mod inline_variants;
5
- mod record_embed;
6
4
  mod record_init;
7
5
  mod unboxed_variants;
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_embed.rs DELETED
@@ -1,79 +0,0 @@
1
- use plum_test_support::{runInt, PRE};
2
-
3
- fn prog(body: &str) -> String {
4
- format!("{PRE}{body}")
5
- }
6
-
7
- const STAGE: &str = "struct Stage(camScale: Int) =
8
- fun init(self) =
9
- self.root: Int = 10
10
- self.camX: Int = self.camScale * 2
11
-
12
- fun act(self, dt: Int) -> Int = self.root + dt
13
-
14
- fun draw(self) -> Int = 100
15
-
16
- ";
17
-
18
- #[test]
19
- fn inherited_fields_methods_and_init_order() {
20
- let src = prog(&format!("{STAGE}struct World(Stage(camScale), score: Int) =
21
- fun init(self) =
22
- self.best: Int = self.root + self.camX + 1
23
-
24
- fun main() -> Int =
25
- w := World(camScale: 3, score: 4)
26
- w.camScale + w.root * 10 + w.camX * 100 + w.score * 1000 + w.best * 10000 + w.act(5) * 100000
27
- "));
28
- // camScale 3, root 10, camX 6, score 4, best 17, act(5) = 15
29
- assert_eq!(runInt("fields", &src), 3 + 100 + 600 + 4000 + 170000 + 1500000);
30
- }
31
-
32
- #[test]
33
- fn override_calls_original() {
34
- let src = prog(&format!("{STAGE}struct World(Stage(camScale), score: Int) =
35
- override fun draw(self) -> Int = self.Stage.draw() + self.score
36
-
37
- fun main() -> Int =
38
- w := World(camScale: 1, score: 7)
39
- w.draw() * 10 + w.act(1)
40
- "));
41
- assert_eq!(runInt("override", &src), 1070 + 11);
42
- }
43
-
44
- #[test]
45
- fn embedded_record_standalone_and_unselected_set_by_init() {
46
- let src = prog("struct Stage(camScale: Int, tag: Int) =
47
- fun init(self) =
48
- self.camX: Int = 6
49
-
50
- struct World(Stage(tag), score: Int) =
51
- fun init(self) =
52
- self.camScale = 5
53
-
54
- fun main() -> Int =
55
- s := Stage(camScale: 2, tag: 1)
56
- w := World(tag: 3, score: 1)
57
- s.camScale * 100 + w.camScale * 10 + w.camX + w.tag + s.camX
58
- ");
59
- assert_eq!(runInt("standalone", &src), 200 + 50 + 6 + 3 + 6);
60
- }
61
-
62
- #[test]
63
- fn base_through_embedded_struct() {
64
- let src = prog("struct Actor(x: Int) =
65
- fun go(self) -> Int = 1
66
-
67
- struct Mid(Actor(x), m: Int) =
68
- override fun go(self) -> Int = 2 + self.Actor.go()
69
-
70
- struct Top(Mid(x, m), t: Int)
71
-
72
- fun run(a: Actor) -> Int = a.go()
73
-
74
- fun main() -> Int =
75
- t := Top(x: 1, m: 2, t: 3)
76
- run(t) * 10 + t.x
77
- ");
78
- assert_eq!(runInt("deep", &src), 31);
79
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs CHANGED
@@ -1,4 +1,4 @@
1
- use plum_test_support::{runInt, PRE};
1
+ use plum_test_support::{PRE};
2
2
  use plum_test_support::load;
3
3
 
4
4
  fn prog(body: &str) -> String {
@@ -14,79 +14,6 @@ const ACTOR: &str = "struct Actor(px: Int, py: Int) =
14
14
 
15
15
  ";
16
16
 
17
- #[test]
18
- fn ship_example_values() {
19
- let src = prog(&format!("{ACTOR}struct Ship(Actor(px, py), tex: Int) =
20
- fun init(self) =
21
- self.height = self.width + 1
22
- self.vx = 7
23
- override fun area(self) -> Int = self.width * self.height + self.vx
24
-
25
- fun main() -> Int =
26
- s := Ship(px: 1, py: 2, tex: 3)
27
- s.px + s.py * 10 + s.tex * 100 + s.area() * 1000
28
- "));
29
- // px 1 + py 20 + tex 300 + (10*11+7)*1000
30
- assert_eq!(runInt("ship", &src), 1 + 20 + 300 + 117 * 1000);
31
- }
32
-
33
- #[test]
34
- fn through_a_base_value() {
35
- let src = prog("struct Actor(px: Int, width: Int) =
36
- fun init(self) =
37
- self.width = 10
38
- fun area(self) -> Int = self.width * 2
39
-
40
- struct Ship(Actor(px), tex: Int) =
41
- override fun area(self) -> Int = self.width * 20
42
-
43
- struct Rock(Actor(px))
44
-
45
- fun total(a: Actor) -> Int = a.area() + a.px
46
-
47
- fun main() -> Int =
48
- total(Ship(px: 1, tex: 0)) * 1000 + total(Rock(px: 2))
49
- ");
50
- // Ship: 10*20 + 1 = 201 ; Rock: 20 + 2 = 22
51
- assert_eq!(runInt("basevalue", &src), 201 * 1000 + 22);
52
- }
53
-
54
- #[test]
55
- fn fresh_list_per_construction() {
56
- let src = prog(&format!("{ACTOR}struct Ship(Actor(px, py)) =
57
- fun init(self) =
58
- self.vx = 0
59
-
60
- fun main() -> Int =
61
- a := Ship(px: 0, py: 0)
62
- b := Ship(px: 0, py: 0)
63
- a.kids.push(1)
64
- a.kids.push(2)
65
- b.kids.push(9)
66
- a.kids.length() * 10 + b.kids.length()
67
- "));
68
- assert_eq!(runInt("fresh", &src), 21);
69
- }
70
-
71
- #[test]
72
- fn rest_runs_after_build_and_nested_construction() {
73
- let src = prog("struct Inner(mut n: Int)
74
-
75
- struct Outer(a: Int) =
76
- fun init(self) =
77
- self.inner = Inner(n: self.a + 1)
78
- self.total = 0
79
- self.bump()
80
- fun bump(self) =
81
- self.inner.n = self.inner.n * 10
82
-
83
- fun main() -> Int =
84
- o := Outer(a: 4)
85
- o.inner.n + o.a
86
- ");
87
- assert_eq!(runInt("rest", &src), 54);
88
- }
89
-
90
17
  #[test]
91
18
  fn errors_surface_through_the_loader() {
92
19
  let src = prog(&format!("{ACTOR}struct Ship(Actor(px, py)) =
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/unboxed_variants.rs CHANGED
@@ -1,6 +1,6 @@
1
1
  //! Unboxed layout: union-enum members and inline variants are wasm-gc subtypes
2
2
  //! of the enum, so constructing one allocates exactly one struct.
3
- use plum_test_support::{compile, runInt};
3
+ use plum_test_support::{compile};
4
4
  use wasmparser::{Operator, Parser, Payload};
5
5
 
6
6
  /// Number of `struct.new` instructions in the exported `main` function.
@@ -91,169 +91,3 @@ fun main() -> Int =
91
91
  );
92
92
  assert_eq!(structNewsInMain(&compile("generic", &src)), 3);
93
93
  }
94
-
95
- #[test]
96
- fn match_variant_fields() {
97
- let src = format!(
98
- "{HEAD}\
99
- enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
100
-
101
- fun area(s: Shape) -> Int =
102
- match s
103
- Circle(r) => r * r
104
- Rect(w, h) => w * h
105
-
106
- fun main() -> Int =
107
- area(Circle(r: 3)) * 100 + area(Rect(w: 2, h: 5))
108
- "
109
- );
110
- assert_eq!(runInt("match_fields", &src), 910);
111
- }
112
-
113
- #[test]
114
- fn nested_enum_match() {
115
- let src = format!(
116
- "{HEAD}\
117
- enum Link[T] = End | Duo(head: T, tail: Link[T])
118
-
119
- fun sum(l: Link[Int]) -> Int =
120
- match l
121
- End => 0
122
- Duo(head, tail) => head + sum(tail)
123
-
124
- fun second(l: Link[Int]) -> Int =
125
- match l
126
- Duo(_, Duo(h, _)) => h
127
- _ => -1
128
-
129
- fun main() -> Int =
130
- l: Link[Int] := Duo(head: 1, tail: Duo(head: 20, tail: Duo(head: 300, tail: End)))
131
- sum(l) * 1000 + second(l)
132
- "
133
- );
134
- assert_eq!(runInt("nested", &src), 321020);
135
- }
136
-
137
- #[test]
138
- fn equality_is_identity_of_the_variant_value() {
139
- let src = format!(
140
- "{HEAD}\
141
- enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
142
-
143
- fun main() -> Int =
144
- a: Shape := Circle(r: 2)
145
- b: Shape := a
146
- c: Shape := Rect(w: 1, h: 2)
147
- x := if a == b then 1 else 0
148
- y := if a != c then 10 else 0
149
- x + y
150
- "
151
- );
152
- assert_eq!(runInt("eq", &src), 11);
153
- }
154
-
155
- #[test]
156
- fn reassign_enum_local_across_variants() {
157
- let src = format!(
158
- "{HEAD}\
159
- enum Shape = Circle(r: Int) | Rect(w: Int, h: Int)
160
-
161
- fun area(s: Shape) -> Int =
162
- match s
163
- Circle(r) => r * 3
164
- Rect(w, h) => w * h
165
-
166
- fun kind(s: Shape) -> Int =
167
- match s
168
- Circle(_) => 1
169
- Rect(_, _) => 10
170
-
171
- fun main() -> Int =
172
- s: Shape := Circle(r: 1)
173
- n := area(s)
174
- s = Rect(w: 2, h: 5)
175
- n = n * 100 + area(s)
176
- k := 0
177
- for i := range 4
178
- if i % 2 == 0
179
- s = Rect(w: 1, h: 1)
180
- else
181
- s = Circle(r: 1)
182
- k = k + kind(s)
183
- n * 100 + k
184
- "
185
- );
186
- assert_eq!(runInt("reassign", &src), (3 * 100 + 10) * 100 + 22);
187
- }
188
-
189
- #[test]
190
- fn deep_nested_variant_match() {
191
- let src = format!(
192
- "{HEAD}\
193
- enum Tree = Leaf(v: Int) | Node(l: Tree, r: Tree)
194
-
195
- fun depthSum(t: Tree, d: Int) -> Int =
196
- match t
197
- Leaf(v) => v * d
198
- Node(l, r) => depthSum(l, d + 1) + depthSum(r, d + 1)
199
-
200
- fun build(n: Int) -> Tree =
201
- if n == 0 then Leaf(v: 1) else Node(l: build(n - 1), r: Leaf(v: n))
202
-
203
- fun main() -> Int = depthSum(build(5), 1)
204
- "
205
- );
206
- // Leaves: 1 at depth 6, then Leaf(n) at depth 7 - n for n = 1..5.
207
- assert_eq!(runInt("deep", &src), 6 + (1..=5).map(|n| n * (7 - n)).sum::<i64>());
208
- }
209
-
210
- #[test]
211
- fn million_cells_built_and_mapped_in_constant_stack() {
212
- let src = format!(
213
- "{HEAD}\
214
- enum Link = End | Duo(head: Int, tail: Link)
215
-
216
- fun upTo(n: Int) -> Link =
217
- if n == 0 then End else Duo(head: n, tail: upTo(n - 1))
218
-
219
- fun inc(xs: Link) -> Link =
220
- match xs
221
- End => End
222
- Duo(h, t) => Duo(head: h + 1, tail: inc(t))
223
-
224
- fun sum(xs: Link, acc: Int) -> Int =
225
- match xs
226
- End => acc
227
- Duo(h, t) => sum(t, acc + h)
228
-
229
- fun main() -> Int = sum(inc(upTo(1000000)), 0)
230
- "
231
- );
232
- assert_eq!(runInt("million", &src), 500000500000 + 1000000);
233
- }
234
-
235
- #[test]
236
- fn enum_init_versus_variant_name() {
237
- let src = format!(
238
- "{HEAD}\
239
- enum Cell =
240
- | Empty
241
- | Full(count: Int, label: Str)
242
-
243
- fun init(n: Int) -> Cell = if n <= 0 then Empty else Full(count: n, label: \"n{{n}}\")
244
-
245
- fun size(self) -> Int =
246
- match self
247
- Empty => 0
248
- Full(c, _) => c
249
-
250
- fun main() -> Int =
251
- a := Cell(5)
252
- b := Cell(0)
253
- c := Full(count: 9, label: \"nine\")
254
- d := Full(label: \"swapped\", count: 2)
255
- a.size() * 1000 + b.size() * 100 + c.size() * 10 + d.size()
256
- "
257
- );
258
- assert_eq!(runInt("init_names", &src), 5000 + 90 + 2);
259
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/closure_tail.rs CHANGED
@@ -1,31 +1,7 @@
1
- use plum_test_support::{compile, runInt};
1
+ use plum_test_support::{compile};
2
2
 
3
3
  const HEAD: &str = "import std/Int\n\n";
4
4
 
5
- #[test]
6
- fn closure_body_tail_call_million() {
7
- let src = format!("{HEAD}fun countDown(n: Int) -> Int = if n == 0 then 0 else countDown(n - 1)\n\nfun main() -> Int =\n dec := |n: Int| if n == 0 then 7 else countDown(n - 1) + 0\n fin := |n: Int| countDown(n)\n fin(1000000) + dec(0)\n");
8
- assert_eq!(runInt("cbody", &src), 7);
9
- }
10
-
11
- #[test]
12
- fn recursive_local_closure_million() {
13
- let src = format!("{HEAD}fun main() -> Int =\n go := |n, acc| if n == 0 then acc else go(n - 1, acc + n)\n go(1000000, 0)\n");
14
- assert_eq!(runInt("rec", &src), 500000500000);
15
- }
16
-
17
- #[test]
18
- fn recursive_closure_captures_outer() {
19
- let src = format!("{HEAD}fun main() -> Int =\n k := 3\n go := |n, acc| if n == 0 then acc else go(n - 1, acc + k)\n go(1000000, 0)\n");
20
- assert_eq!(runInt("reccap", &src), 3000000);
21
- }
22
-
23
- #[test]
24
- fn closure_not_recursive_unchanged() {
25
- let src = format!("{HEAD}fun main() -> Int =\n k := 10\n f := |x| x + k\n f(5)\n");
26
- assert_eq!(runInt("plain", &src), 15);
27
- }
28
-
29
5
  #[test]
30
6
  fn wasm_closure_has_return_call() {
31
7
  let src = format!("{HEAD}fun main() -> Int =\n go := |n, acc| if n == 0 then acc else go(n - 1, acc + n)\n go(10, 0)\n");
@@ -43,15 +19,3 @@ fn wasm_closure_has_return_call() {
43
19
  }
44
20
  assert!(found, "expected return_call_indirect in a closure body");
45
21
  }
46
-
47
- #[test]
48
- fn self_calling_closure_typed_params_runs() {
49
- let src = format!("{HEAD}fun main() -> Int =\n fact := |n: Int| if n <= 1 then 1 else n * fact(n - 1)\n fact(5)\n");
50
- assert_eq!(runInt("selftyped", &src), 120);
51
- }
52
-
53
- #[test]
54
- fn self_calling_closure_untyped_params_runs() {
55
- let src = format!("{HEAD}fun main() -> Int =\n fact := |n| if n <= 1 then 1 else n * fact(n - 1)\n fact(5)\n");
56
- assert_eq!(runInt("selfuntyped", &src), 120);
57
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/error_tail.rs DELETED
@@ -1,27 +0,0 @@
1
- use plum_test_support::runInt;
2
-
3
- const RHEAD: &str = "import std/Int\nimport std/Result\n\nenum NegError = Neg | Other\n\n";
4
-
5
- #[test]
6
- fn result_self_loop_million() {
7
- let src = format!("{RHEAD}fun down(n: Int) -> Result[Int, NegError] =\n if n < 0\n return Err(Neg)\n if n == 0\n return Ok(5)\n down(n - 1)\n\nfun main() -> Int = down(1000000).unwrapOr(99)\n");
8
- assert_eq!(runInt("result_self_loop_million", &src), 5);
9
- }
10
-
11
- #[test]
12
- fn result_self_loop_returns_err() {
13
- let src = format!("{RHEAD}fun down(n: Int) -> Result[Int, NegError] =\n if n == 0\n return Err(Neg)\n down(n - 1)\n\nfun main() -> Int = down(1000000).unwrapOr(42)\n");
14
- assert_eq!(runInt("result_self_loop_returns_err", &src), 42);
15
- }
16
-
17
- #[test]
18
- fn result_mutual_million() {
19
- let src = format!("{RHEAD}fun ping(n: Int) -> Result[Int, NegError] =\n if n == 0\n return Ok(1)\n pong(n - 1)\n\nfun pong(n: Int) -> Result[Int, NegError] =\n if n == 0\n return Ok(2)\n ping(n - 1)\n\nfun main() -> Int = ping(1000000).unwrapOr(99)\n");
20
- assert_eq!(runInt("result_mutual_million", &src), 1);
21
- }
22
-
23
- #[test]
24
- fn question_in_tail_not_tail_call() {
25
- let src = format!("{RHEAD}fun down(n: Int) -> Result[Int, NegError] =\n if n == 0\n return Ok(5)\n v := down(n - 1)?\n Ok(v)\n\nfun main() -> Int = down(100).unwrapOr(99)\n");
26
- assert_eq!(runInt("question_in_tail_not_tail_call", &src), 5);
27
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/loop_break.rs DELETED
@@ -1,148 +0,0 @@
1
- use plum_test_support::{runInt, PRE};
2
-
3
- fn prog(body: &str) -> String {
4
- format!("{PRE}{body}")
5
- }
6
-
7
- // Every loop is fuel-bounded (`i < 50`) so a wrong branch depth fails an
8
- // assertion instead of hanging the test.
9
- const LST: &str = "
10
- enum Lst = Nil | Cons(head: Int, tail: Lst)
11
-
12
- fun build(n: Int, acc: Lst) -> Lst =
13
- if n == 0 then acc else build(n - 1, Cons(head: n, tail: acc))
14
- ";
15
-
16
- #[test]
17
- fn break_in_if_in_match_arm_in_while() {
18
- let src = prog(&format!(
19
- "{LST}
20
- fun find(xs: Lst, target: Int) -> Int =
21
- i := 0
22
- cur := xs
23
- while i < 50
24
- match cur
25
- Cons(h, t) =>
26
- if h == target
27
- break
28
- cur = t
29
- Nil =>
30
- break
31
- i = i + 1
32
- return i
33
-
34
- fun main() -> Int = find(build(10, Nil), 4) * 100 + find(build(10, Nil), 99)
35
- "
36
- ));
37
- assert_eq!(runInt("break_if_match_while", &src), 3 * 100 + 10);
38
- }
39
-
40
- #[test]
41
- fn continue_in_if_in_match_arm_in_while() {
42
- let src = prog(&format!(
43
- "{LST}
44
- fun sumOdd(xs: Lst) -> Int =
45
- total := 0
46
- i := 0
47
- cur := xs
48
- while i < 50
49
- i = i + 1
50
- match cur
51
- Cons(h, t) =>
52
- cur = t
53
- if h % 2 == 0
54
- continue
55
- total = total + h
56
- Nil =>
57
- break
58
- return total * 100 + i
59
-
60
- fun main() -> Int = sumOdd(build(6, Nil))
61
- "
62
- ));
63
- // 1 + 3 + 5 = 9; seven iterations (six cons cells then Nil).
64
- assert_eq!(runInt("continue_if_match_while", &src), 900 + 7);
65
- }
66
-
67
- #[test]
68
- fn break_in_match_arm_direct() {
69
- let src = prog(&format!(
70
- "{LST}
71
- fun count(xs: Lst) -> Int =
72
- i := 0
73
- cur := xs
74
- while i < 50
75
- match cur
76
- Nil =>
77
- break
78
- Cons(h, t) =>
79
- cur = t
80
- i = i + 1
81
- return i
82
-
83
- fun wild(n: Int) -> Int =
84
- i := 0
85
- while i < 50
86
- match i
87
- _ =>
88
- break
89
- i = i + 1
90
- return i + n
91
-
92
- fun main() -> Int = count(build(5, Nil)) * 100 + wild(7)
93
- "
94
- ));
95
- assert_eq!(runInt("break_match_arm_direct", &src), 5 * 100 + 7);
96
- }
97
-
98
- #[test]
99
- fn break_in_else_if_chain_in_while() {
100
- let src = prog(
101
- "fun main() -> Int =
102
- i := 0
103
- while i < 50
104
- if i == 100
105
- i = i + 1000
106
- else if i == 100
107
- i = i + 2000
108
- else if i == 7
109
- break
110
- i = i + 1
111
- return i
112
- ",
113
- );
114
- assert_eq!(runInt("break_else_if_chain", &src), 7);
115
- }
116
-
117
- #[test]
118
- fn nested_loops_with_match_in_inner() {
119
- let src = prog(&format!(
120
- "{LST}
121
- fun main() -> Int =
122
- outer := 0
123
- hits := 0
124
- while outer < 50
125
- if outer == 3
126
- break
127
- cur := build(5, Nil)
128
- j := 0
129
- while j < 50
130
- match cur
131
- Cons(h, t) =>
132
- cur = t
133
- if h == 2
134
- j = j + 1
135
- continue
136
- if h == 4
137
- break
138
- hits = hits + 1
139
- Nil =>
140
- break
141
- j = j + 1
142
- outer = outer + 1
143
- return outer * 100 + hits
144
- "
145
- ));
146
- // inner: h=1 hit, h=2 skipped, h=3 hit, h=4 break => 2 hits per outer pass, 3 passes.
147
- assert_eq!(runInt("nested_loops_match", &src), 3 * 100 + 6);
148
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/mod.rs CHANGED
@@ -1,5 +1,3 @@
1
1
  #![allow(non_snake_case)]
2
2
  mod closure_tail;
3
- mod error_tail;
4
- mod loop_break;
5
3
  mod tail;
compiler/wasm-codegen/tests/wasm_codegen/e2e_tail/tail.rs CHANGED
@@ -1,4 +1,4 @@
1
- use plum_test_support::{compile, runInt, PRE};
1
+ use plum_test_support::{compile, PRE};
2
2
 
3
3
  fn prog(body: &str) -> String {
4
4
  format!("{PRE}{body}")
@@ -15,119 +15,6 @@ fun sumAcc(xs: Link[Int], acc: Int) -> Int = match xs
15
15
  Cons(h, t) => sumAcc(t, acc + h)
16
16
  ";
17
17
 
18
- #[test]
19
- fn self_tail_million() {
20
- let src = prog(&format!(
21
- "{LINK}
22
- fun main() -> Int = sumAcc(build(1000000, Nil), 0)
23
- "
24
- ));
25
- assert_eq!(runInt("self_tail_million", &src), 500000500000);
26
- }
27
-
28
- #[test]
29
- fn self_tail_args_swap() {
30
- let src = prog(
31
- "fun gcd(a: Int, b: Int) -> Int = if b == 0 then a else gcd(b, a % b)
32
-
33
- fun swapN(a: Int, b: Int, n: Int) -> Int =
34
- if n == 0 then a * 1000 + b else swapN(b, a, n - 1)
35
-
36
- fun main() -> Int = gcd(1071, 462) * 1000000 + swapN(1, 2, 999999)
37
- ",
38
- );
39
- // gcd = 21; odd swap count leaves (2, 1).
40
- assert_eq!(runInt("self_tail_args_swap", &src), 21 * 1000000 + 2001);
41
- }
42
-
43
- #[test]
44
- fn mutual_tail_million() {
45
- let src = prog(
46
- "fun isEven(n: Int) -> Int = if n == 0 then 1 else isOdd(n - 1)
47
-
48
- fun isOdd(n: Int) -> Int = if n == 0 then 0 else isEven(n - 1)
49
-
50
- fun main() -> Int = isEven(1000001) * 10 + isOdd(1000001)
51
- ",
52
- );
53
- assert_eq!(runInt("mutual_tail_million", &src), 1);
54
- }
55
-
56
- #[test]
57
- fn closure_tail_call_runs() {
58
- let src = prog(
59
- "fun run(n: Int, k: fn(Int) -> Int) -> Int = if n == 0 then 7 else k(n - 1)
60
-
61
- fun go(n: Int) -> Int = run(n, go)
62
-
63
- fun main() -> Int = go(1000000)
64
- ",
65
- );
66
- assert_eq!(runInt("closure_tail_call_runs", &src), 7);
67
- }
68
-
69
- #[test]
70
- fn method_self_tail() {
71
- let src = prog(
72
- "struct Counter(step: Int) =
73
- fun count(self, n: Int, acc: Int) -> Int =
74
- if n == 0 then acc else self.count(n - 1, acc + self.step)
75
-
76
- fun main() -> Int = Counter(step: 3).count(1000000, 0)
77
- ",
78
- );
79
- assert_eq!(runInt("method_self_tail", &src), 3000000);
80
- }
81
-
82
- #[test]
83
- fn param_self_tail() {
84
- let src = prog(
85
- "struct Cell(x: Int, y: Int)
86
-
87
- fun bump(c: Cell, n: Int) -> Cell =
88
- if n == 0 then c else bump(Cell(..c, x: c.x + 1), n - 1)
89
-
90
- fun main() -> Int =
91
- c := bump(Cell(x: 0, y: 5), 100000)
92
- c.x * 10 + c.y
93
- ",
94
- );
95
- assert_eq!(runInt("param_self_tail", &src), 1000005);
96
- }
97
-
98
- /// R3: a closure made in one iteration keeps that iteration's parameter value.
99
- #[test]
100
- fn closure_captures_iteration_value() {
101
- let src = prog(
102
- "fun go(i: Int, f: fn(Int) -> Int) -> Int =
103
- if i == 3 then f(100) else go(i + 1, |x| x + i)
104
-
105
- fun main() -> Int = go(0, |x| x)
106
- ",
107
- );
108
- assert_eq!(runInt("closure_captures_iteration_value", &src), 102);
109
- }
110
-
111
- /// Match arms and else-if chains nest wasm blocks between the call and the loop.
112
- #[test]
113
- fn self_tail_through_nested_branches() {
114
- let src = prog(
115
- "fun f(n: Int, acc: Int) -> Int =
116
- if n == 0 then acc
117
- else if n % 3 == 0 then f(n - 1, acc + 1)
118
- else if n % 3 == 1 then f(n - 1, acc + 10)
119
- else f(n - 1, acc + 100)
120
-
121
- fun g(n: Int, acc: Int) -> Int = match n
122
- 0 => acc
123
- k => if k % 2 == 0 then g(k - 1, acc + 2) else g(k - 1, acc + 1)
124
-
125
- fun main() -> Int = f(30, 0) * 1000 + g(10, 0)
126
- ",
127
- );
128
- assert_eq!(runInt("self_tail_nested", &src), 1110 * 1000 + 15);
129
- }
130
-
131
18
  #[derive(Default)]
132
19
  struct FnOps {
133
20
  calls: Vec<u32>,
@@ -237,39 +124,3 @@ fun main() -> Int = isEven(10)
237
124
  }
238
125
 
239
126
 
240
- const HEAD: &str = "import std/Int\n\n";
241
-
242
- #[test]
243
- fn tail_call_in_match_arm() {
244
- let src = format!("{HEAD}enum Step =\n | Go(n: Int)\n | Stop\n\nfun run(s: Step, acc: Int) -> Int = match s\n Go(n) => run(if n == 0 then Stop else Go(n - 1), acc + 1)\n Stop => acc\n\nfun main() -> Int = run(Go(1000000), 0)\n");
245
- assert_eq!(runInt("match", &src), 1000001);
246
- }
247
-
248
- #[test]
249
- fn non_tail_union_wrap_not_tail() {
250
- // The callee returns Int but the fn returns Option[Int]: the call is wrapped on the way
251
- // out, so it must stay a normal call and still produce the right value.
252
- let src = format!("{HEAD}import std/Option\n\nfun inner(n: Int) -> Int = n + 1\n\nfun outer(n: Int) -> Option[Int] = Some(inner(n))\n\nfun main() -> Int =\n v := outer(41)\n v.unwrapOr(0)\n");
253
- assert_eq!(runInt("wrap", &src), 42);
254
- }
255
-
256
- #[test]
257
- fn big_link_built_and_summed() {
258
- let src = format!("{HEAD}enum Link =\n | Nil\n | Cons(head: Int, tail: Link)\n\nfun build(n: Int, acc: Link) -> Link = if n == 0 then acc else build(n - 1, Cons(n, acc))\n\nfun sum(l: Link, acc: Int) -> Int = match l\n Nil => acc\n Cons(h, t) => sum(t, acc + h)\n\nfun main() -> Int = sum(build(200000, Nil), 0)\n");
259
- assert_eq!(runInt("link", &src), 200000i64 * 200001 / 2);
260
- }
261
-
262
- /// TRMC: a map over a native-variant list of 100k elements builds without stack growth.
263
- #[test]
264
- fn trmc_link_map_100k() {
265
- let src = prog(&format!(
266
- "{LINK}
267
- fun mapInc(xs: Link[Int]) -> Link[Int] = match xs
268
- Nil => Nil
269
- Cons(h, t) => Cons(head: h + 1, tail: mapInc(t))
270
-
271
- fun main() -> Int = sumAcc(mapInc(build(100000, Nil)), 0)
272
- "
273
- ));
274
- assert_eq!(runInt("trmc_link_map_100k", &src), 5000050000 + 100000);
275
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/closure_optional.rs DELETED
@@ -1,39 +0,0 @@
1
- //! F-T3-2: closures whose expected return is `Option[T]` / `Result[T, E]`, calls through an fn-typed
2
- //! field returning `T?`, and static methods of a generic type that never pin `T`.
3
- use plum_test_support::*;
4
-
5
- #[test]
6
- fn closure_coerced_into_optional_return() {
7
- let src = "import std/Int\nimport std/Option\n\nfun ap(f: fn() -> Option[Int]) -> Int =\n f().unwrapOr(0)\n\nfun main() -> Int =\n ap(|| Some(3))\n";
8
- assert_eq!(runInt("opt_present", src), 3);
9
- }
10
-
11
- #[test]
12
- fn closure_returning_none_into_optional() {
13
- let src = "import std/Int\nimport std/Option\n\nfun ap(f: fn() -> Option[Int]) -> Int =\n f().unwrapOr(7)\n\nfun main() -> Int =\n ap(|| None)\n";
14
- assert_eq!(runInt("opt_null", src), 7);
15
- }
16
-
17
- #[test]
18
- fn closure_into_ref_option_and_result() {
19
- let src = "import std/Int\nimport std/Str\nimport std/Option\nimport std/Result\n\nenum BadError = Bad | Worse\n\nfun s(f: fn(Int) -> Option[Str]) -> Str =\n f(1).unwrapOr(\"none\")\n\nfun e(f: fn(Int) -> Result[Int, BadError]) -> Int =\n f(1).unwrapOr(9)\n\nfun main() -> Int =\n b := s(|x| {if x > 0 then Some(\"yes\") else None}).length()\n b2 := s(|x| Some(\"ab\")).length()\n c := e(|x| Ok(x + 1))\n d := e(|x| Err(Bad))\n b * 1000 + b2 * 100 + c * 10 + d\n";
20
- assert_eq!(runInt("opt_mixed", src), 3229);
21
- }
22
-
23
- #[test]
24
- fn fn_field_returning_optional() {
25
- let src = "import std/Int\nimport std/Option\n\nstruct Gen(next: fn() -> Option[Int])\n\nfun count(g: Gen) -> Int =\n g.next().unwrapOr(0)\n\nfun main() -> Int =\n k := 5\n count(Gen(next: || Some(k)))\n";
26
- assert_eq!(runInt("field_opt", src), 5);
27
- }
28
-
29
- #[test]
30
- fn static_method_of_generic_type_not_pinning_t() {
31
- let src = "import std/Int\n\nstruct Gen[T](v: T) =\n fun ints(n: Int) -> Gen[Int] = Gen(v: n)\n fun get(self) -> T = self.v\n\nfun main() -> Int =\n Gen.ints(4).get()\n";
32
- assert_eq!(runInt("static_unpinned", src), 4);
33
- }
34
-
35
- #[test]
36
- fn seq_with_optional_next() {
37
- let src = "import std/Int\nimport std/Option\n\nstruct Seq[T](next: fn(Int) -> Option[T]) =\n fun upTo(n: Int) -> Seq[Int] = Seq(next: |i| {if i < n then Some(i * 2) else None})\n fun sum(self) -> Int =\n total := 0\n i := 0\n while Some(v) := self.next(i)\n total = total + v\n i = i + 1\n total\n\nfun main() -> Int =\n Seq.upTo(4).sum()\n";
38
- assert_eq!(runInt("seq_opt", src), 12);
39
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/closure_types.rs DELETED
@@ -1,50 +0,0 @@
1
- use plum_test_support::*;
2
-
3
- const BOX: &str = "import std/Int\n\nstruct Box(v: Int) =\n fun get(self) -> Int = self.v\n\n";
4
-
5
- #[test]
6
- fn g1_typed_param() {
7
- let src = format!("{BOX}fun ap(f: fn(Box) -> Int) -> Int = f(Box(v: 2))\n\nfun main() -> Int =\n ap(|b: Box| b.get())\n");
8
- assert_eq!(runInt("g1", &src), 2);
9
- }
10
-
11
- #[test]
12
- fn g2_local_closure_inferred_from_first_call() {
13
- let src = format!("{BOX}fun main() -> Int =\n get := |b| b.get()\n get(Box(v: 2))\n");
14
- assert_eq!(runInt("g2", &src), 2);
15
- }
16
-
17
- #[test]
18
- fn g5_returned_closure() {
19
- let src = "import std/Int\n\nfun adder(n: Int) -> fn(Int) -> Int =\n |x| x + n\n\nfun main() -> Int =\n add1 := adder(1)\n add1(2)\n";
20
- assert_eq!(runInt("g5", src), 3);
21
- }
22
-
23
- // `adder(1)(2)` (call on a call result) does not parse: the grammar has no call-on-call form.
24
-
25
- #[test]
26
- fn adder_two_levels() {
27
- let src = "import std/Int\n\nfun adder(n: Int) -> fn(Int) -> fn(Int) -> Int =\n |x| |y| x + y + n\n\nfun main() -> Int =\n f := adder(1)\n g := f(2)\n g(3)\n";
28
- assert_eq!(runInt("two", src), 6);
29
- }
30
-
31
- const LINK: &str = "import std/Int\n\nenum Link[T] = Nil | Cons(head: T, tail: Link[T])\n\nfun len[T](xs: Link[T]) -> Int = match xs\n Nil => 0\n Cons(_, t) => 1 + len(t)\n\n";
32
-
33
- /// A bare `Nil` whose `T` is fixed by a LATER argument gets the call's `Link[Int]`.
34
- #[test]
35
- fn bare_tag_fixed_by_later_arg() {
36
- let src = format!("{LINK}fun push[T](xs: Link[T], x: T) -> Link[T] = Cons(head: x, tail: xs)\n\nfun main() -> Int =\n len(push(push(Nil, 1), 2))\n");
37
- assert_eq!(runInt("bare_later", &src), 2);
38
- }
39
-
40
- #[test]
41
- fn bare_tag_fixed_by_other_link_arg() {
42
- let src = format!("{LINK}fun two[T](a: Link[T], b: Link[T]) -> Int = len(a) + len(b)\n\nfun mk(n: Int) -> Link[Int] = Cons(head: n, tail: Nil)\n\nfun main() -> Int =\n two(Nil, mk(2)) * 10 + two(mk(1), Nil)\n");
43
- assert_eq!(runInt("bare_other", &src), 11);
44
- }
45
-
46
- #[test]
47
- fn curried_closure_runs() {
48
- let src = "import std/Int\n\nfun curry() -> fn(Int) -> fn(Int) -> Int =\n |x| |y| x + y\n\nfun main() -> Int =\n add2 := curry()\n f := add2(2)\n f(3)\n";
49
- assert_eq!(runInt("curried", src), 5);
50
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/list_and_shadow.rs DELETED
@@ -1,81 +0,0 @@
1
- //! F-T7-1: an unannotated list literal whose first element leaves the enum's generic
2
- //! unpinned (a later element pins it), and a user type shadowing a std type that std
3
- //! imports on its own (`std/Int` imports `std/Pair` for `Range.next`).
4
- use plum_test_support::*;
5
-
6
- const NODE: &str = "\
7
- import std/Str
8
- import std/Int
9
- import std/List
10
-
11
- enum Node[M] =
12
- | Box(w: Int)
13
- | Tag(msg: M, w: Int)
14
- | Group(kids: List[Node[M]])
15
- | Wrap(child: Node[M], k: Int)
16
-
17
- fun total(self) -> Int = match self
18
- Box(w) => w
19
- Tag(_, w) => w
20
- Group(kids) => Node.sumKids(kids)
21
- Wrap(child, k) => Node.twice(child, k)
22
-
23
- fun twice(child: Node[M], k: Int) -> Int = child.total() + child.total() + k
24
-
25
- fun sumKids(kids: List[Node[M]]) -> Int = match kids
26
- Nil => 0
27
- Cons(head, tail) => head.total() + Node.sumKids(tail)
28
-
29
- # A second instantiation, so a wrongly pinned element picks a different type.
30
- fun other(n: Node[Int]) -> Int = n.total()
31
-
32
- fun tagged(s: Str) -> Node[Str] = Tag(msg: s, w: 2)
33
-
34
- ";
35
-
36
- #[test]
37
- fn list_literal_element_pinned_by_a_later_element() {
38
- let src = format!(
39
- "{NODE}fun view(s: Str) -> Node[Str] =
40
- t := tagged(s)
41
- Wrap(child: Group(kids: [Box(w: 1), t, Box(w: 3)]), k: 100)
42
-
43
- fun main() -> Int =
44
- view(\"x\").total() + other(Box(w: 10))
45
- "
46
- );
47
- assert_eq!(runInt("list_pinned_late_fused", &src), 122);
48
- }
49
-
50
- #[test]
51
- fn unannotated_local_of_list_pinned_by_a_later_element() {
52
- let src = format!(
53
- "{NODE}fun view(s: Str) -> Int =
54
- t := tagged(s)
55
- g := Group(kids: [Box(w: 1), t])
56
- q := g
57
- g.total() + q.total()
58
-
59
- fun main() -> Int =
60
- view(\"x\") + other(Box(w: 10))
61
- "
62
- );
63
- assert_eq!(runInt("list_pinned_late_local", &src), 16);
64
- }
65
-
66
- #[test]
67
- fn user_type_shadows_std_pair_imported_by_std() {
68
- let src = "\
69
- import std/Int
70
-
71
- struct Pair(a: Int, b: Int) =
72
- fun sum(self) -> Int = self.a + self.b
73
-
74
- fun main() -> Int =
75
- p := Pair(a: 3, b: 4)
76
- for i := range 3
77
- p = Pair(a: p.a + i, b: p.b)
78
- p.a * 10 + p.sum()
79
- ";
80
- assert_eq!(runInt("user_pair", src), 70);
81
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs CHANGED
@@ -1,9 +1,6 @@
1
1
  #![allow(non_snake_case)]
2
- mod closure_optional;
3
- mod closure_types;
4
2
  mod io_entry;
5
3
  mod io_primitive;
6
- mod list_and_shadow;
7
4
  mod question;
8
5
  mod subtyping;
9
6
  mod synthetic_sites;
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/question.rs CHANGED
@@ -7,54 +7,6 @@ fn prog(body: &str) -> String {
7
7
  format!("{HEAD}{body}")
8
8
  }
9
9
 
10
- #[test]
11
- fn chained_question_returns_first_err() {
12
- let src = prog(
13
- "fun pick(n: Int) -> Result[Int, Int] =\n if n > 0\n return Ok(n)\n Err(100 + n)\n\nfun f(x: Int, y: Int, z: Int) -> Result[Int, Int] =\n p := pick(x)?\n q := pick(y)?\n r := pick(z)?\n Ok(p + q + r)\n\nfun main() -> Int =\n s := f(1, 2, 3).unwrapOr(0)\n t := f(1, 0, 0)\n bad := t.isErr()\n if bad then s * 1000 + 1 else 0\n",
14
- );
15
- assert_eq!(runInt("q_chain", &src), 6001);
16
- }
17
-
18
- #[test]
19
- fn chained_err_value_is_first() {
20
- let src = prog(
21
- "fun pick(n: Int) -> Result[Int, Int] =\n if n > 0\n return Ok(n)\n Err(100 + n)\n\nfun f(x: Int, y: Int, z: Int) -> Result[Int, Int] =\n p := pick(x)?\n q := pick(y)?\n r := pick(z)?\n Ok(p + q + r)\n\nfun main() -> Int =\n match f(1, -5, -7)\n Ok(v) => v\n Err(e) => e\n",
22
- );
23
- assert_eq!(runInt("q_first_err", &src), 95);
24
- }
25
-
26
- #[test]
27
- fn option_question_returns_none() {
28
- let src = prog(
29
- "fun g(n: Int) -> Option[Int] = if n > 0 then Some(n) else None\n\nfun f(n: Int) -> Option[Int] =\n v := g(n)?\n Some(v * 2)\n\nfun main() -> Int =\n f(4).unwrapOr(-1) * 100 + f(0).unwrapOr(7)\n",
30
- );
31
- assert_eq!(runInt("q_none", &src), 807);
32
- }
33
-
34
- #[test]
35
- fn ok_unit_and_expression_position() {
36
- let src = prog(
37
- "fun g(n: Int) -> Option[Int] = if n > 0 then Some(n) else None\n\nfun u(n: Int) -> Result[Unit, Int] =\n if n > 0\n return Ok(Unit)\n Err(n)\n\nfun h(n: Int) -> Option[Int] = Some(1 + g(n)?)\n\nfun main() -> Int =\n a := h(5).unwrapOr(0)\n b := u(1).isOk()\n c := u(0).isErr()\n if b && c then a else -1\n",
38
- );
39
- assert_eq!(runInt("q_unit_expr", &src), 6);
40
- }
41
-
42
- #[test]
43
- fn struct_payload_in_some() {
44
- let src = prog(
45
- "fun g(n: Int) -> Option[Pt] = if n > 0 then Some(Pt(first: n, second: 2)) else None\n\nfun f(n: Int) -> Option[Int] =\n p := g(n)?\n Some(p.first * 10 + p.second)\n\nfun main() -> Int =\n f(3).unwrapOr(0)\n",
46
- );
47
- assert_eq!(runInt("q_struct", &src), 32);
48
- }
49
-
50
- #[test]
51
- fn bang_unwraps_and_chains() {
52
- let src = prog(
53
- "fun g(n: Int) -> Option[Pt] = Some(Pt(first: n, second: 2))\n\nfun main() -> Int =\n x := Some(3)!\n y := g(4)!.first\n r: Result[Int, Int] := Ok(5)\n x + y * 10 + r! * 100\n",
54
- );
55
- assert_eq!(runInt("q_bang", &src), 543);
56
- }
57
-
58
10
  #[test]
59
11
  fn bang_on_none_crashes() {
60
12
  let src = prog("fun main() -> Int =\n o: Option[Int] := None\n o!\n");
@@ -62,99 +14,3 @@ fn bang_on_none_crashes() {
62
14
  assert!(std::panic::catch_unwind(|| runInt("q_crash", &src)).is_err());
63
15
  }
64
16
 
65
- #[test]
66
- fn bare_variants_in_if_branches_take_fn_return_type() {
67
- let src = prog(
68
- "fun pickInline(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)\n\nfun pickBlock(flag: Bool, n: Int) -> Result[Int, Int] =\n if flag\n Ok(n)\n else\n Err(100 + n)\n\nfun maybe(flag: Bool, n: Int) -> Option[Int] = if flag then None else Some(n)\n\nfun main() -> Int =\n a := pickInline(false, 1).isErr()\n b := pickBlock(true, 2).unwrapOr(0)\n c := maybe(false, 3).unwrapOr(0)\n d := maybe(true, 3).isNone()\n if a && d then b * 10 + c else -1\n",
69
- );
70
- assert_eq!(runInt("q_if_variants", &src), 23);
71
- }
72
-
73
- #[test]
74
- fn bare_variants_in_match_arms_take_fn_return_type() {
75
- let src = prog(
76
- "fun pick(n: Int) -> Result[Int, Int] = match n\n 0 => Err(100)\n _ => Ok(n)\n\nfun main() -> Int =\n match pick(0)\n Ok(v) => v\n Err(e) => e + pick(4).unwrapOr(0)\n",
77
- );
78
- assert_eq!(runInt("q_match_variants", &src), 104);
79
- }
80
-
81
- #[test]
82
- fn map_err_on_call_result() {
83
- let src = prog(
84
- "fun fails() -> Result[Int, Int] =\n r: Result[Int, Int] := Err(7)\n r\n\nfun main() -> Int =\n match fails().mapErr(|e| e + 1000)\n Ok(v) => v\n Err(e) => e\n",
85
- );
86
- assert_eq!(runInt("q_map_err", &src), 1007);
87
- }
88
-
89
-
90
- /// mapErr's closure param `f` must not take the arity of a top-level `fun f`.
91
- #[test]
92
- fn map_err_closure_param_shadows_top_level_fn() {
93
- let src = prog(
94
- "fun f(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)\n\nfun main() -> Int =\n match f(false, 1).mapErr(|e| e + 1000)\n Ok(v) => v\n Err(e) => e\n",
95
- );
96
- assert_eq!(runInt("q_shadow", &src), 1101);
97
- }
98
-
99
- /// An unannotated inline if: each bare variant pins the generic the other leaves open.
100
- #[test]
101
- fn inline_if_joins_bare_variant_instantiations() {
102
- let src = prog(
103
- "fun main() -> Int =\n flag := 3 > 2\n o := if flag then Ok(4) else Err(5)\n low := if flag then Err(1) else Ok(Pt(first: 1, second: 2))\n a := o.unwrapOr(0)\n match low\n Ok(pt) => pt.first\n Err(e) => a * 10 + e\n",
104
- );
105
- assert_eq!(runInt("q_if_join", &src), 41);
106
- }
107
-
108
- /// Fusion inlines `pick(false, 1)` to a bare `Err(101)` and drops `Result[Pt, Int]`
109
- /// (plum-core simplify), so codegen builds `Ok`'s payload as Unit.
110
- #[test]
111
- fn inlined_constant_call_keeps_return_type() {
112
- let src = prog(
113
- "fun pick(flag: Bool, n: Int) -> Result[Pt, Int] = if flag then Ok(Pt(first: n, second: 1)) else Err(100 + n)\n\nfun main() -> Int =\n match pick(false, 1)\n Ok(pt) => pt.first\n Err(e) => e\n",
114
- );
115
- assert_eq!(runInt("q_inlined", &src), 101);
116
- }
117
-
118
- /// Simplify inlines `pick(true, 2)` to `Ok(2)`; drive then fuses `unwrapOr` over `mapErr`
119
- /// into a fn generic in `E`, which a bare `Ok(2)` never binds (no instance is emitted).
120
- #[test]
121
- fn map_err_then_unwrap_or_fuses() {
122
- let src = prog(
123
- "fun pick(flag: Bool, n: Int) -> Result[Int, Int] = if flag then Ok(n) else Err(100 + n)\n\nfun main() -> Int =\n pick(true, 2).mapErr(|e| e + 1).unwrapOr(0)\n",
124
- );
125
- assert_eq!(runInt("q_map_err_unwrap", &src), 2);
126
- }
127
-
128
- /// Drive specialises `mapErr` / `ok` on the inlined `Err(..)`: the picked arm's own `Err` /
129
- /// `None` leaves `mapErr` / `ok` and must keep its `Result[Pt, Int]` / `Option[Pt]`.
130
- #[test]
131
- fn specialised_arm_keeps_result_type() {
132
- let src = prog(
133
- "fun pick(flag: Bool, n: Int) -> Result[Pt, Int] = if flag then Ok(Pt(first: n, second: 1)) else Err(100 + n)\n\nfun viaMapErr() -> Int =\n match pick(false, 2).mapErr(|e| e + 1)\n Ok(pt) => pt.first\n Err(e) => e\n\nfun viaOk() -> Int =\n r := pick(false, 4)\n match r.ok()\n Some(pt) => pt.first\n None => 8\n\nfun main() -> Int = viaMapErr() * 10 + viaOk()\n",
134
- );
135
- assert_eq!(runInt("q_spec_arm", &src), 1038);
136
- }
137
-
138
- #[test]
139
- fn none_as_param_default() {
140
- let src = prog(
141
- "fun f(n: Option[Int] = None) -> Option[Int] = n\n\nfun main() -> Int =\n a := f().unwrapOr(7)\n b := f(Some(3)).unwrapOr(0)\n a * 10 + b\n",
142
- );
143
- assert_eq!(runInt("q_none_default", &src), 73);
144
- }
145
-
146
- #[test]
147
- fn none_as_generic_param_default() {
148
- let src = prog(
149
- "fun g[T](x: T, o: Option[T] = None) -> Option[T] = o\n\nfun main() -> Int =\n a := g(1).unwrapOr(7)\n b := g(1, Some(3)).unwrapOr(0)\n a * 10 + b\n",
150
- );
151
- assert_eq!(runInt("q_none_generic_default", &src), 73);
152
- }
153
-
154
- #[test]
155
- fn err_field_is_error() {
156
- let src = prog(
157
- "fun main() -> Int =\n r: Result[Int, Int] := Err(error: 5)\n match r\n Ok(v) => v\n Err(e) => e\n",
158
- );
159
- assert_eq!(runInt("q_err_field", &src), 5);
160
- }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/synthetic_sites.rs CHANGED
@@ -1,118 +1,11 @@
1
1
  //! The places codegen reads a type off a name or a call rather than off an expression node of
2
2
  //! its own: each runs the shape through the whole pipeline and checks the value.
3
- use plum_test_support::{compileSource, load, runInt, PRE};
3
+ use plum_test_support::{compileSource, load, PRE};
4
4
 
5
5
  fn prog(body: &str) -> String {
6
6
  format!("{PRE}{body}")
7
7
  }
8
8
 
9
- /// `Nothing()` (call syntax on a field-less variant) is the variant's
10
- /// singleton, typed by the call node.
11
- #[test]
12
- fn field_less_variant_called_like_a_constructor() {
13
- let src = prog("\
14
- enum Maybe =
15
- | Nothing
16
- | Just(value: Int)
17
-
18
- fun orZero(m: Maybe) -> Int = match m
19
- Nothing => 0
20
- Just(v) => v
21
-
22
- fun main() -> Int =
23
- a: Maybe := Nothing()
24
- b: Maybe := Just(value: 40)
25
- orZero(a) + orZero(b) + 2
26
- ");
27
- assert_eq!(runInt("nothing_call", &src), 42);
28
- }
29
-
30
- /// A call through a local closure whose type is only known from the local's own initialiser.
31
- #[test]
32
- fn unit_closure_local_called_as_a_statement() {
33
- let src = prog("\
34
- fun main() -> Int =
35
- f := || Unit
36
- f()
37
- f()
38
- 7
39
- ");
40
- assert_eq!(runInt("unit_closure_local", &src), 7);
41
- }
42
-
43
- /// A rebuild that reuses the matched cell reads the cell's type off the local it names.
44
- #[test]
45
- fn reused_cell_is_read_through_its_local() {
46
- let src = prog("\
47
- enum Link =
48
- | Nil
49
- | Cons(head: Int, tail: Link)
50
-
51
- fun bump(xs: Link) -> Link = match xs
52
- Nil => Nil
53
- Cons(h, t) => Cons(head: h + 1, tail: t)
54
-
55
- fun sum(xs: Link) -> Int = match xs
56
- Nil => 0
57
- Cons(h, t) => h + sum(t)
58
-
59
- fun main() -> Int = sum(bump(Cons(head: 1, tail: Cons(head: 2, tail: Nil))))
60
- ");
61
- assert_eq!(runInt("reuse_local", &src), 4);
62
- }
63
-
64
- /// The same call syntax on a variant of a generic enum: the call has the enum's instantiated type.
65
- #[test]
66
- fn field_less_variant_of_a_generic_enum_called_like_a_constructor() {
67
- let src = prog("\
68
- enum Maybe[T] =
69
- | Nothing
70
- | Just(value: T)
71
-
72
- fun orZero(m: Maybe[Int]) -> Int = match m
73
- Nothing => 0
74
- Just(v) => v
75
-
76
- fun main() -> Int =
77
- a: Maybe[Int] := Nothing()
78
- b: Maybe[Int] := Just(value: 40)
79
- orZero(a) + orZero(b) + 2
80
- ");
81
- assert_eq!(runInt("nothing_call_generic", &src), 42);
82
- }
83
-
84
- /// F-T9-2: a closure calling itself with `None` for an `Option[Int]` param. The checker
85
- /// types that `None` as `Option[Unit]`; the param's declared type picks the singleton, so the
86
- /// wasm is valid and the value right.
87
- #[test]
88
- fn closure_passes_none_to_an_option_param() {
89
- let src = prog("\
90
- import std/Option
91
-
92
- fun main() -> Int =
93
- rec := |o: Option[Int], acc: Int, n: Int|
94
- if n <= 0 then acc else rec(None, acc + 1, n - 1)
95
- rec(None, 0, 3)
96
- ");
97
- assert_eq!(runInt("closure_none_typed", &src), 3);
98
- }
99
-
100
- #[test]
101
- fn untyped_closure_passes_none_to_an_option_param() {
102
- let src = prog("\
103
- import std/Option
104
-
105
- fun run(n: Int, o: Option[Int]) -> Int =
106
- rec := |o, acc, n|
107
- if n <= 0 then 0 else rec(None, acc + 1, n - 1)
108
- rec(o, 0, n)
109
-
110
- fun main() -> Int =
111
- run(3, None)
112
- ");
113
- assert_eq!(runInt("closure_none_untyped", &src), 0);
114
- }
115
-
116
9
  /// F-T9-1: a call to an unknown function reports that, not the missing type of its
117
10
  /// (unchecked) arguments.
118
11
  #[test]
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/type_param_static.rs CHANGED
@@ -1,36 +1,5 @@
1
1
  use plum_test_support::*;
2
2
 
3
- #[test]
4
- fn static_on_type_param() {
5
- let src = "\
6
- import std/Int
7
-
8
- struct Pt(x: Int) =
9
- fun make(n: Int) -> Pt = Pt(x: n * 10)
10
-
11
- fun build[T](n: Int) -> T = T.make(n)
12
-
13
- fun main() -> Int =
14
- p: Pt := build(3)
15
- return p.x
16
- ";
17
- assert_eq!(runInt("rec", src), 30);
18
- }
19
-
20
- #[test]
21
- fn static_on_builtin() {
22
- let src = "\
23
- import std/Int
24
-
25
- fun maxOf[T](n: Int) -> T = T.Max
26
-
27
- fun main() -> Int =
28
- m: Int := maxOf(1)
29
- return if m == Int.Max then 1 else 0
30
- ";
31
- assert_eq!(runInt("builtin", src), 1);
32
- }
33
-
34
3
  #[test]
35
4
  fn static_missing_reports() {
36
5
  let src = "\
@@ -52,50 +21,6 @@ fun main() -> Int =
52
21
  );
53
22
  }
54
23
 
55
- #[test]
56
- fn nested_generic() {
57
- let src = "\
58
- import std/Int
59
- import std/Slice
60
- import std/List
61
-
62
- struct Pt(x: Int) =
63
- fun make(n: Int) -> Pt = Pt(x: n + 1)
64
-
65
- fun buildAll[T](ns: Slice[Int]) -> Slice[T] =
66
- out := Slice[T]()
67
- for n := range ns
68
- out.push(T.make(n))
69
- return out
70
-
71
- fun main() -> Int =
72
- ps: Slice[Pt] := buildAll([1, 2, 3].toSlice())
73
- p := ps.get(2).unwrapOr(Pt(x: 0))
74
- return p.x
75
- ";
76
- assert_eq!(runInt("nested", src), 4);
77
- }
78
-
79
- #[test]
80
- fn static_on_builtin_method_and_json_cycle() {
81
- let src = "\
82
- import std/Int
83
- import std/Str
84
- import std/Json
85
- import std/Result
86
-
87
- fun parseIt[T](s: Str) -> Result[T, NumError] =
88
- T.parse(s)
89
-
90
- fun main() -> Int =
91
- r: Result[Int, NumError] := parseIt(\"41\")
92
- n := r.unwrapOr(0)
93
- j := n.toJson()
94
- return n + 1
95
- ";
96
- assert_eq!(runInt("builtin_method", src), 42);
97
- }
98
-
99
24
  #[test]
100
25
  fn extension_outside_std_is_rejected() {
101
26
  let src = "\
@@ -130,11 +55,3 @@ fun main() -> Int =
130
55
  };
131
56
  assert!(!errs.is_empty(), "toJson must not exist without std/Json");
132
57
  }
133
-
134
- /// Single uppercase letters are ordinary names: a struct, enum variants, a constant, and a
135
- /// declared multi-letter fn generic all work side by side.
136
- #[test]
137
- fn single_letter_types_and_variants() {
138
- let src = "import std/Int\n\nstruct P(x: Int)\n\nenum Two = A | B\n\nstruct Cfg(n: Int) =\n E = 5\n\nfun pick(t: Two) -> Int = match t\n A => 1\n B => 20\n\nfun id[Elem](x: Elem) -> Elem = x\n\nfun main() -> Int =\n p := P(x: id(3))\n pick(A) + pick(B) + p.x + Cfg.E\n";
139
- assert_eq!(runInt("single_letter_types", src), 29);
140
- }
docs/backlog.md CHANGED
@@ -4,7 +4,7 @@ Open work, in the order the user queued it, plus smaller items found during buil
4
4
 
5
5
  ## Planned (in order)
6
6
 
7
- **Current order (user 2026-11-05):** explicit fn generics (done faa4d7e) → cleanup phase 3b (pass list, positioned errors; running) → Io primitive + extern methods → move Rust crates under `compiler/` → tooling + scripts moved under compiler/ (done) → cleanup phase 4 (done: node ids, typed codegen, `inferExpr` errors at spans; `docs/plans/2026-11-05-compiler-cleanup-phase4.results.md`) → phase 5 (file splits, enum kinds) → phase 6 (tests in Plum) → benchmark (incl. iterator unboxing / niche Option, run C game A/B) → FBIP (stop for user approval) → deterministic sim → kids' games. The items below keep their detail; this line wins on order.
7
+ **Current order (user 2026-11-05):** explicit fn generics (done faa4d7e) → cleanup phase 3b (pass list, positioned errors; running) → Io primitive + extern methods → move Rust crates under `compiler/` → tooling + scripts moved under compiler/ (done) → cleanup phase 4 (done: node ids, typed codegen, `inferExpr` errors at spans; `docs/plans/2026-11-05-compiler-cleanup-phase4.results.md`) → phases 5 and 6 (done: file splits, type kinds, tests in Plum; `docs/plans/2026-11-05-compiler-cleanup-phase5-6.results.md`) → benchmark (incl. iterator unboxing / niche Option, run C game A/B) → FBIP (stop for user approval) → deterministic sim → kids' games. The items below keep their detail; this line wins on order.
8
8
 
9
9
  1. **Compiler cleanup and architecture** — `docs/plans/2026-11-04-compiler-cleanup.review.md` (written in another session; meant to run after language-restrictions merged). Approved 2026-11-04; running on branch `superlite/compiler-cleanup`, phase 1 first.
10
10
  - **Explicit function generics** (user 2026-11-04: right after cleanup phase 2) — functions declare their generics (`fun map[B](...)`) instead of every single capital letter being a generic; allows longer generic names and one-letter concrete types/tags. Grammar change + about 140 signatures in std/examples. Phase 2's "generics by scope" makes this mostly a grammar + signature rewrite.
docs/plans/2026-11-05-compiler-cleanup-phase5-6.md CHANGED
@@ -112,7 +112,7 @@ e.g. `test "deep tail recursion"` running a 200000-step self-tail call, a method
112
112
  **Test code:** the new test passes; sweep with the new folder.
113
113
 
114
114
  ### T7 move wasm_codegen run tests · Complexity 4 · Est. context 50k
115
- **Owns:** compiler/wasm-codegen/tests/wasm_codegen/**/*.rs, compiler/tests/codegen/*.plum
115
+ **Owns:** compiler/wasm-codegen/tests/wasm_codegen/**/*.rs, compiler/tests/wasm_codegen/**/*.plum
116
116
  **Components:** 1
117
117
  **LOC owned:** 200
118
118
  **Tests:** fast
docs/plans/2026-11-05-compiler-cleanup-phase5-6.progress.md ADDED
@@ -0,0 +1,26 @@
1
+ - start 5ecf1ab
2
+ Ruling: lint --max-task-min 25 for this plan - the predictor's history took in the last run's inflated timings (a 20-line results doc predicted at 19 min), so every task failed the 15 min limit; sizes are unchanged - cost: a task may run long
3
+ Ruling: accept predicted 132 min over the 90 min target for the same reason; the predictor overstates it - cost: none
4
+ Ruling: skip Wave 0 - no shared stubs - cost: none
5
+ - unblocked: dispatched T1 T2 at 22:37:01; waiting on fixes: none
6
+ Ruling: T2 waits for T1 despite the plan graph - wasm-codegen depends on plum-checker, so T2 can't build while T1 has the checker half moved - cost: about 15 min
7
+ - checkpoint T1 7c6a0a8056 checker split
8
+ - checkpoint T2 ed3a336e1c codegen split
9
+ Ruling: T1 and T2 pure moves verified by a coordinator sorted-line check against HEAD instead of a reviewer - cost: none
10
+ - checkpoint T3 56805e9736 codegen driver stages
11
+ - unblocked: dispatched T4 prime at 22:59:17; waiting on fixes: T3 review
12
+ Ruling: T3 review clean - statement order unchanged, no early returns, taken fields read only before they are taken; closure-only and tests-only files not exercised by it, covered by examples and std - cost: none
13
+ - unblocked: dispatched T4 at 23:01:46; waiting on fixes: none
14
+ - checkpoint T4 22338aefb9 type kinds
15
+ Ruling: T5 run by the coordinator with a script - verification only, no judgement needed - cost: none
16
+ - checkpoint T5 68cfdf7f34 verify phase 5
17
+ - unblocked: dispatched T6 at 23:10:49; waiting on fixes: none
18
+ - checkpoint T6 a4c7b4a80c Plum test home and tail-call proof
19
+ - unblocked: dispatched T7 T8 at 23:13:34; waiting on fixes: none
20
+ - checkpoint T8 dc0ff3997b move other run tests
21
+ - checkpoint T7 47565d6c8e move wasm_codegen run tests
22
+ Ruling: T7 Owns moved to compiler/tests/wasm_codegen - .gitignore ignores any path named codegen - cost: none
23
+ - tests full passed=741 failed=0 skipped=0 seconds=49.3
24
+ Ruling: final review - three findings, all "the packet shows no evidence", no defect. Evidence exists: T1 and T2 sorted-line check, 0 of about 12,500 lines lost; T3 and T4 reviews, statement order unchanged and wasm identical; 108 of 108 moved tests mapped with no value or program change; a flipped assert fails in each folder; counts 1510 before, 1514 after. Recorded as verified - cost: none
25
+ Ruling: T7 and T8 checkpoint with a pathspec error on deleted files; the moves are committed directly and verified above - cost: none
26
+ Ruling: run_tests --full is not the 14-binary check; verify_all.sh is - cost: none
docs/plans/2026-11-05-compiler-cleanup-phase5-6.results.md CHANGED
@@ -27,3 +27,39 @@ The T1 and T2 splits are pure moves. A sorted-line comparison against the previo
27
27
  **One behaviour note:** a user function literally named `__test_x` used to be skipped by a few passes, because they matched on the name. It no longer is. That name was never a supported way to mark a test.
28
28
 
29
29
  **Also fixed:** `compileEntryTests` shared one temp directory and failed about half the time under parallel threads. It now passes 20 of 20 runs.
30
+
31
+ ## Phase 6: tests in Plum
32
+
33
+ **The gate passed.** The review plan only allows this phase if `plum test` runs tail calls the way a compiled program does. `compiler/tests/tail_calls.plum` runs a self, a method and a closure tail call each 1,000,000 deep. A non-tail version at that depth traps and is reported as a failure, so the test can tell the difference.
34
+
35
+ | Check | Before | After |
36
+ |---|---|---|
37
+ | Rust tests, all 14 binaries | 848 | 740 (core 395, wasm_codegen 119, the rest unchanged) |
38
+ | Plum tests in the sweep | 662 | 774, with `compiler/tests` added |
39
+ | Total | 1,510 | 1,514 |
40
+ | Example wasm | | byte-identical, 25 files |
41
+ | Browser (stress_scene, imperative, functional, pure) | | PASS |
42
+
43
+ The sweep is now `target/debug/plum test std examples compiler/tests`.
44
+
45
+ **What moved:** 108 Rust tests, 102 from wasm_codegen and 6 from core, more than the 90 planned. They became 109 Plum tests, because one test that made two runs became two files. T6 adds 3 tail-call tests.
46
+
47
+ **How the move was checked:**
48
+ - A script mapped every removed Rust test to its Plum twin: 108 of 108 found, 0 with a different expected value or program.
49
+ - Flipping an expected value made `plum test` fail, tried in each folder.
50
+ - Every original compared an exact `Int`, so no assertion became weaker.
51
+ - No behaviour differences: every moved test passed first time.
52
+ - The explanatory comments moved too, 39 lines.
53
+
54
+ **What stayed in Rust:** compile-error tests, wasm structure checks, traps and crashes, fusion decisions, CLI behaviour, and most of core and checker, because those assert a pass's output or an error message rather than a program's answer.
55
+
56
+ **Costs:**
57
+ - 103 of the new tests are one file each, because programs in one file share a namespace and these reuse names like `Link`. Each compiles separately.
58
+ - `plum test compiler/tests` takes about 20 seconds, and the Rust suite is correspondingly shorter.
59
+ - `.gitignore` ignores any path named `codegen`, so these live in `compiler/tests/wasm_codegen/` and the plan's path was updated.
60
+ - Test bodies skip the tail, TRMC, closure-spec and drive passes, so moved programs keep their logic in helper functions. New tests need to do the same.
61
+
62
+ ## Not verified
63
+
64
+ - Frame times against `BENCHMARK.md`. The browser runs are headless and capped, so the numbers don't compare. The benchmark item is still queued.
65
+ - WebKit, release builds, and the LSP in an editor.