plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
861d398
— Peter John
2026-11-03T16:08:41+05:30
tail calls: plan; default FnCall.tail at construction sites; run markTailCalls in typedSource
- docs/plans/2026-11-03-tail-calls.md +126 -0
- docs/plans/2026-11-03-tail-calls.review.md +72 -0
- plum-checker/src/lib.rs +9 -1
- plum-core/src/ctors.rs +2 -0
- plum-core/src/forms.rs +2 -0
- plum-core/src/parser.rs +5 -0
- plum-core/src/union_lower.rs +4 -0
- plum-wasm-codegen/src/lib.rs +3 -0
docs/plans/2026-11-03-tail-calls.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Tail calls
|
|
2
|
+
|
|
3
|
+
**Review plan:** docs/plans/2026-11-03-tail-calls.review.md
|
|
4
|
+
**Goal:** calls in tail position use no stack (self calls become loops, others `return_call`), tail recursion modulo cons for constructor-wrapped recursive calls, larger host stack, depth tests at 1,000,000.
|
|
5
|
+
**Architecture:** plum-core `tail` pass marks tail calls on the AST → plum-core `trmc` pass rewrites `C(.., f: self(..))` tails into a hole-filling loop → codegen emits `loop`+`br` for self tail calls and `return_call`/`return_call_ref` for other tail calls → hosts enable wasm tail calls and an 8 MiB stack.
|
|
6
|
+
**Tech stack:** Rust (plum-core, plum-wasm-codegen with wasm-encoder 0.220, wasmtime 47 in plum-cli/plum-runtime/plum-game), Plum.
|
|
7
|
+
**Spec:** docs/plans/2026-11-03-tail-calls.review.md
|
|
8
|
+
**Detail:** contracts
|
|
9
|
+
**Target-min:** 30
|
|
10
|
+
|
|
11
|
+
## Global constraints
|
|
12
|
+
- Work only in /Users/pyrossh/Code/plum-pure-scene-values (branch pure-scene-values). Never touch /Users/pyrossh/Code/plum.
|
|
13
|
+
- Plum file tests: `touch plum-cli/src/main.rs && cargo build --quiet -p plum-cli && ./target/debug/plum test <file>`. Rust: build `cargo build --tests -p <crate>` and run test binaries; `touch` edited .rs first. Whole Plum sweep: `./target/debug/plum test plum-std plum-examples` must stay green (594 tests).
|
|
14
|
+
- Tail position: the value of a fn body; the last expr of a block in tail position; each `match`/`if`/ternary arm in tail position; `return e`. NOT tail: inside `try`/`?`/`!`/`??`, inside a `test`, under a `for`, as an argument, before `defer`-like cleanup, or in an export fn's boundary wrapper.
|
|
15
|
+
- Defaults (review plan): automatic tail calls and TRMC, self calls as loops, wasm tail calls required, frames dropped from backtraces.
|
|
16
|
+
- Known bug R5: same local name with different types across match arms gives invalid wasm; loop-converted params keep one fixed type.
|
|
17
|
+
- Ownership (plum-checker/src/ownership.rs) must keep passing; do not edit it unless your task owns it.
|
|
18
|
+
- Inline variant syntax: `enum Link[T] = Nil | Cons(head: T, tail: Link[T])`, `fun f(x) -> R = match x` (see plum-examples/functional.plum).
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart LR
|
|
22
|
+
AST["checked AST"] --> TP["tail pass (marks)"] --> TR["trmc rewrite"] --> CG["codegen: loop+br / return_call"] --> W[wasm]
|
|
23
|
+
H["hosts: wasm_tail_call + 8 MiB stack"] --> W
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
File structure: each task's Owns. Spec coverage: What you will get (T1-T5), What will be built (T1-T6; benchmark by coordinator), How it fits together (diagram), Decisions you can change (Global constraints), Risks (R1 T2, R2 T2/T5, R3 T2, R4 T4), Out of scope (not built), How we will know it works (T5, T6), Rough time and cost (schedule).
|
|
27
|
+
|
|
28
|
+
## Task graph
|
|
29
|
+
```mermaid
|
|
30
|
+
flowchart LR
|
|
31
|
+
T1[tail pass]
|
|
32
|
+
T2[codegen tail calls]
|
|
33
|
+
T3[host config]
|
|
34
|
+
T4[trmc rewrite]
|
|
35
|
+
T5[depth tests]
|
|
36
|
+
T6[deep fixture]
|
|
37
|
+
%% taskgraph
|
|
38
|
+
T1 --> T2
|
|
39
|
+
T1 --> T4
|
|
40
|
+
T2 --> T5
|
|
41
|
+
T3 --> T5
|
|
42
|
+
T4 --> T5
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Analysis
|
|
46
|
+
|
|
47
|
+
### T1 tail-position pass · Complexity 4 · Est. context 45k
|
|
48
|
+
**Owns:** plum-core/src/tail.rs, plum-core/src/lib.rs, plum-core/src/ast.rs, plum-core/tests/tail_test.rs
|
|
49
|
+
**Components:** 1
|
|
50
|
+
**LOC owned:** 160
|
|
51
|
+
**Tests:** fast
|
|
52
|
+
**Model:** sonnet
|
|
53
|
+
**Consumes:** none
|
|
54
|
+
**Produces:** tail::markTailCalls(src: &mut ast::Source) · tail::tailCalls(f: &ast::Fn) -> Vec<TailSite> · ast call nodes gain `tail: TailKind` (`NotTail | SelfCall | Other | Closure`)
|
|
55
|
+
e.g. `tailCalls(sumAcc)` -> `[TailSite(callee: "sumAcc", kind: SelfCall, line: 3)]`
|
|
56
|
+
e.g. `tailCalls(length)` (`1 + length(t)`) -> `[]`
|
|
57
|
+
**Review Focus:** deep: every rule in Global constraints' tail-position list, nested blocks, `= match` bodies, methods (`self.f(..)` on the same type is SelfCall), closures (a call inside a closure body is tail relative to the closure only).
|
|
58
|
+
**Test code:** tail_test.rs: `self_tail_in_match_arm`, `self_tail_in_if_else`, `not_tail_under_binary_op`, `not_tail_as_argument`, `not_tail_inside_try`, `other_fn_tail`, `mutual_recursion_both_tail`, `method_self_tail`, `closure_value_call_tail` (kind Closure), `return_expr_tail`, `block_last_expr_tail`. Adding the AST field: default `NotTail` at every construction site (any crate; list them).
|
|
59
|
+
**Steps:** pass runs after lowering and before codegen; wire the call in the shared pipeline entry that both `plum test` and `plum compile` use (find it; report where).
|
|
60
|
+
|
|
61
|
+
### T4 tail recursion modulo cons · Complexity 6 · Est. context 55k
|
|
62
|
+
**Owns:** plum-core/src/trmc.rs, plum-core/tests/trmc_test.rs
|
|
63
|
+
**Components:** 1
|
|
64
|
+
**LOC owned:** 170
|
|
65
|
+
**Tests:** fast
|
|
66
|
+
**Model:** sonnet
|
|
67
|
+
**Consumes:** tail::tailCalls(f: &ast::Fn) -> Vec<TailSite>
|
|
68
|
+
**Produces:** trmc::rewrite(src: &mut ast::Source) -> Vec<String>
|
|
69
|
+
e.g. `rewrite` on functional.plum's `map` -> `["map"]` (names of rewritten fns)
|
|
70
|
+
**Review Focus:** deep/state R4: only a single recursive call as one field of a constructor (record or inline variant) in tail position, other fields evaluated first in source order; results equal the original for every input; fields must be writable (if inline-variant hidden records are immutable, restrict to what is writable and report).
|
|
71
|
+
**Test code:** trmc_test.rs (compile and run via plum-wasm-codegen's test helper or the CLI): `map_rewritten_same_result`, `filter_rewritten_same_result` (the `rest := filter(..)` then `if` form is NOT TRMC: assert not rewritten), `fromTo_rewritten`, `append_rewritten`, `two_recursive_fields_not_rewritten` (tree map), `side_effect_order_kept` (a field before the recursive one calls a counter closure: same order).
|
|
72
|
+
**Steps:** rewrite into a loop that allocates each cell with a placeholder for the recursive field and writes it on the next iteration; run before T1's marking so the loop has no self call. Register the pass after `tail::markTailCalls`'s caller (same pipeline entry).
|
|
73
|
+
|
|
74
|
+
## Codegen and hosts
|
|
75
|
+
|
|
76
|
+
### T2 codegen tail calls · Complexity 6 · Est. context 70k
|
|
77
|
+
**Owns:** plum-wasm-codegen/src/lib.rs, plum-wasm-codegen/tests/tail_calls_e2e_test.rs
|
|
78
|
+
**Components:** 1
|
|
79
|
+
**LOC owned:** 200
|
|
80
|
+
**Tests:** fast
|
|
81
|
+
**Model:** sonnet
|
|
82
|
+
**Consumes:** tail::markTailCalls(src: &mut ast::Source)
|
|
83
|
+
**Produces:** none
|
|
84
|
+
**Review Focus:** state R1/R3: a fn with a SelfCall tail gets its body wrapped in `loop`; the tail call evaluates all args into temps, then `local.set`s params, then `br` to the loop (no param read after its set); a closure created in the body that captures a param copies it into a fresh local per iteration. Other/Closure tails emit `return_call` / `return_call_ref`. Error-union results and export wrappers never tail-call. R2: own params reassigned by the loop.
|
|
85
|
+
**Test code:** tail_calls_e2e_test.rs (run wasm with tail calls enabled in the test engine): `self_tail_million` (sumAcc over build(1_000_000) = 500000500000), `self_tail_args_swap` (`gcd(b, a % b)`), `mutual_tail_million` (isEven/isOdd 1_000_001), `closure_tail_call_runs` (deep, through `fn` value), `method_self_tail` , `own_param_self_tail` (own record threaded through 100_000 iterations), `closure_captures_iteration_value`, `wasm_has_no_call_in_loop` (wasmparser: sumAcc contains `br`, no `call` to itself), `return_call_emitted` (isEven contains return_call). Existing codegen suite green.
|
|
86
|
+
|
|
87
|
+
### T3 host config · Complexity 1 · Est. context 22k
|
|
88
|
+
**Owns:** plum-cli/src/main.rs, plum-runtime/src/main.rs, plum-game/src/host.rs
|
|
89
|
+
**Components:** 1
|
|
90
|
+
**LOC owned:** 20
|
|
91
|
+
**Tests:** fast
|
|
92
|
+
**Kind:** mechanical
|
|
93
|
+
**Model:** haiku
|
|
94
|
+
**Consumes:** none
|
|
95
|
+
**Produces:** none
|
|
96
|
+
**Review Focus:** state: every `wasmtime::Config::new()` (plum-cli/src/main.rs:796 and :946, plum-runtime/src/main.rs:361, plum-game/src/host.rs:162) gets `.wasm_tail_call(true)` and `.max_wasm_stack(8 << 20)`.
|
|
97
|
+
**Test code:** `cargo build --workspace` clean; `./target/debug/plum test plum-examples/functional.plum` passes; the probe `length(fromTo(0, 50000)) == 50000` in a scratch file under /private/tmp/claude-501/ passes (was "call stack exhausted" at 10,000).
|
|
98
|
+
|
|
99
|
+
## Verification
|
|
100
|
+
|
|
101
|
+
### T5 depth tests · Complexity 3 · Est. context 35k
|
|
102
|
+
**Owns:** plum-examples/tail_calls.plum
|
|
103
|
+
**Components:** 1
|
|
104
|
+
**LOC owned:** 120
|
|
105
|
+
**Tests:** fast
|
|
106
|
+
**Model:** sonnet
|
|
107
|
+
**Consumes:** none
|
|
108
|
+
**Produces:** none
|
|
109
|
+
**Review Focus:** deep: each test at 1,000,000 unless stated.
|
|
110
|
+
**Test code:** module tail_calls with `enum Link[T] = Nil | Cons(head: T, tail: Link[T])`: `tail_sum_million`, `tail_build_million`, `fold_million`, `mutual_even_odd_million`, `trmc_map_million`, `trmc_fromTo_million`, `closure_tail_million`, `own_record_loop_100k`, `tree_recursion_depth_recorded` (a non-tail tree depth that passes at 8 MiB, documented in a comment with the measured limit). Update the "not tail call optimised" comment at the top of plum-examples/functional.plum? NO (not owned): report it.
|
|
111
|
+
|
|
112
|
+
### T6 deep state/view fixture · Complexity 2 · Est. context 30k
|
|
113
|
+
**Owns:** plum-game/web-test/fixtures/stress_state_view_deep.plum
|
|
114
|
+
**Components:** 1
|
|
115
|
+
**LOC owned:** 90
|
|
116
|
+
**Tests:** fast
|
|
117
|
+
**Model:** sonnet
|
|
118
|
+
**Consumes:** none
|
|
119
|
+
**Produces:** none
|
|
120
|
+
**Review Focus:** numeric: same 5,750 actors and formulas as stress_state_view.plum, but the 5,000 dancers in ONE `Link` (5,000-deep recursion in step/view/draw).
|
|
121
|
+
**Test code:** `plum test` on the fixture (2 tests mirroring stress_state_view.plum); `plum compile` succeeds. Expected to overflow before T2/T3/T4 and run after: report both.
|
|
122
|
+
|
|
123
|
+
## Review Focus
|
|
124
|
+
- compiler (library): deep, state. Risks: wrong tail-position classification; param clobbering in loops; TRMC changing evaluation order; ownership interplay.
|
|
125
|
+
- hosts: state. Risk: a missed Config.
|
|
126
|
+
**Probe entry:** run=./target/debug/plum test
|
docs/plans/2026-11-03-tail-calls.review.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Tail calls: review plan
|
|
2
|
+
|
|
3
|
+
**Builds on:** branch `pure-scene-values` (phase B, commit 64aab59). The recursive state/view and `std/game/Picture` are the motivating code. The supercompiler is a later, separate plan.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
Plum recursion currently runs out of stack between 5,000 and 10,000 frames, measured on 2026-11-03 under `plum test` (wasmtime 47). Tail-shaped functions overflow just the same:
|
|
7
|
+
|
|
8
|
+
| probe | depth | result |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `length(fromTo(0, n))` (not tail) | 5,000 | ok |
|
|
11
|
+
| same | 10,000 | `wasm trap: call stack exhausted` |
|
|
12
|
+
| `sumAcc(build(n, Nil), 0)` (tail) | 5,000 | ok |
|
|
13
|
+
| same | 20,000 / 1,000,000 | stack exhausted |
|
|
14
|
+
|
|
15
|
+
This plan makes calls in tail position free, so tail-recursive functions run at any depth. It also turns the most common non-tail shape, building a list as you go (`Cons(head: f(h), tail: map(t, f))`), into a tail call. After that, `map`, `filter`, `fromTo`, `view` and `step` over long `Link`s no longer overflow either.
|
|
16
|
+
|
|
17
|
+
## What you will get
|
|
18
|
+
- **Self tail calls become loops.** A function that calls itself in tail position (the last thing a `match` arm, `if` branch or block does) compiles to a jump back to its start with the parameters reassigned. No new stack frame is used, and it runs at loop speed. Examples: `sumAcc`, `build`, `fold`, `drawNodes(tail)`.
|
|
19
|
+
- **Other tail calls use wasm `return_call`.** A call to a different function in tail position, mutual recursion (`isEven` / `isOdd`), and calls through a closure value use `return_call` / `return_call_ref`. They also stop growing the stack.
|
|
20
|
+
- **Tail recursion modulo cons.** A function whose tail is a constructor around one recursive call, such as `Cons(head: x, tail: f(rest))`, is rewritten to fill a hole in the result as it goes. That makes it a loop. This covers `map`, `filter`, `fromTo`, `append` and Picture/state-view `view` builders. Opt-in rules are listed under Decisions.
|
|
21
|
+
- **`plum test` and the host stack.** Raise wasmtime's stack limit to a stated size for deep non-tail recursion that remains (tree recursion such as `bounds` over a deep Group). Report the new limit.
|
|
22
|
+
- **Diagnostics.** `plum build --explain-tail fn` (or a test helper) lists which calls were made tail calls. Tests can then assert on a function's tail calls without inspecting the wasm.
|
|
23
|
+
|
|
24
|
+
## What will be built
|
|
25
|
+
| Part | What it does | Size |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Tail-position analysis | Marks calls in tail position through `match`/`if`/blocks/`= match` bodies, early `return`, and error-union `try` (not tail) | S |
|
|
28
|
+
| Self-tail loop | Codegen wraps the body in a `loop`, and a self tail call becomes param `local.set` + `br` | M |
|
|
29
|
+
| `return_call` | Codegen emits `return_call` / `return_call_ref` for other tail calls; wasmtime `Config::wasm_tail_call(true)` in plum-cli, plum-runtime and plum-game | S |
|
|
30
|
+
| Tail modulo cons | Pass: `C(.., field: f(..), ..)` in tail position, with `f` the enclosing fn and one recursive field → destination-passing loop (allocate the node with a hole, write the hole next iteration) | M |
|
|
31
|
+
| Stack limit | `max_wasm_stack` set in all three hosts; depth probe recorded | S |
|
|
32
|
+
| Tests and probes | Depth tests at 1,000,000 for tail and TRMC functions, mutual recursion, closures in tail position, and every existing suite green | M |
|
|
33
|
+
| Benchmark | Recursive state/view stress fixture re-measured; a 5,000-deep single-list variant added to show the recursion depth limit is gone | S |
|
|
34
|
+
|
|
35
|
+
## How it fits together
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart LR
|
|
38
|
+
AST["checked AST"] --> TP["tail-position analysis"]
|
|
39
|
+
TP --> TRMC["tail modulo cons rewrite"]
|
|
40
|
+
TRMC --> CG["codegen"]
|
|
41
|
+
CG --> L["self tail call: loop + br"]
|
|
42
|
+
CG --> RC["other tail call: return_call / return_call_ref"]
|
|
43
|
+
L --> W[wasm-gc]
|
|
44
|
+
RC --> W
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Decisions you can change
|
|
48
|
+
- **Which calls.** Default: every call in tail position is a tail call, automatically. Alternative: an explicit marker (for example `return tail f(x)`, or an `@tailrec` annotation that errors when the call isn't in tail position).
|
|
49
|
+
- **Tail modulo cons.** Default: automatic for a single recursive field in a constructor at tail position. Alternative: opt-in per function (OCaml's `[@tail_mod_cons]`).
|
|
50
|
+
- **Self calls.** Default: a loop, not `return_call`, because it's faster and needs no engine support. Alternative: `return_call` everywhere (simpler, slower).
|
|
51
|
+
- **Browser support.** Default: require wasm tail calls (Chrome 112+, Firefox 121+, Safari 18.2+). Alternative: loops only, no `return_call`, for older engines; mutual recursion would then still grow the stack.
|
|
52
|
+
- **Stack traces.** Tail calls drop frames from wasm backtraces. Default: accept that. Alternative: a debug flag that turns tail calls off.
|
|
53
|
+
|
|
54
|
+
## Risks
|
|
55
|
+
- **R1: R5 match-arm local bug.** Reassigning params in a loop meets the known invalid-wasm bug where the same name gets different types. The analysis gives every loop param one fixed type, and a regression test covers it.
|
|
56
|
+
- **R2: owned values.** Tail-call reassignment counts as a move under the ownership checker. Self tail calls with `own` params must not trip "used after it was moved". Tests cover it.
|
|
57
|
+
- **R3: closures capturing params.** A closure created in an iteration that captures a param must see that iteration's value. Loop-converted params are reassigned, so captured params are copied into a fresh local per iteration.
|
|
58
|
+
- **R4: TRMC and wasm-gc.** Filling a hole needs a mutable field. Plum records are mutable today, so this holds; immutable struct/inline-variant layouts would need a builder type. If hidden inline-variant records turn out to be immutable, TRMC covers records only, or is deferred.
|
|
59
|
+
|
|
60
|
+
## Out of scope
|
|
61
|
+
- The supercompiler (driving/folding, deforestation, fusing `view` into `draw`): the next plan, once this lands.
|
|
62
|
+
- General CPS, or any non-tail recursion beyond tail modulo cons (tree recursion keeps using the stack).
|
|
63
|
+
- Changing the existing for-loop forms.
|
|
64
|
+
|
|
65
|
+
## How we will know it works
|
|
66
|
+
- **Depth tests:** the probes above all pass at 1,000,000 for tail-recursive functions (`sumAcc`, `build`, `fold`), for mutual recursion, for TRMC functions (`map`, `filter`, `fromTo`), and for a closure called in tail position. Non-tail tree recursion still fails at a stated depth, which is recorded.
|
|
67
|
+
- **Explain output:** a test asserts which calls are tail calls for a small set of functions (tail, not tail, inside `try`, inside a closure).
|
|
68
|
+
- **Existing suites:** the whole suite stays green (1,765 tests today), and so do the host parity test and both browser checks.
|
|
69
|
+
- **Benchmark:** the recursive state/view row is re-measured, plus a 5,000-deep single-list fixture that overflows before this change and runs after it.
|
|
70
|
+
|
|
71
|
+
## Rough time and cost
|
|
72
|
+
About 7 parts. The analysis comes first, then the self-loop and `return_call` in parallel, then TRMC, then tests and the benchmark. Rough agent time is 45–70 minutes.
|
plum-checker/src/lib.rs
CHANGED
|
@@ -1179,7 +1179,14 @@ pub fn checkSource(source: &ast::Source) -> CheckResult<()> {
|
|
|
1179
1179
|
/// instantiated type of every expression (keyed by expression address) and the
|
|
1180
1180
|
/// set of generic instantiations (see `TypedInfo`) for codegen.
|
|
1181
1181
|
pub fn typedSource(source: &ast::Source) -> CheckResult<TypedInfo> {
|
|
1182
|
-
let (result, info) = checkSourceFull(source);
|
|
1182
|
+
let (result, mut info) = checkSourceFull(source);
|
|
1183
|
+
// TODO(tail-calls): the tail-recursion-modulo-cons AST rewrite pass goes HERE,
|
|
1184
|
+
// on the fully lowered program, immediately before `markTailCalls`.
|
|
1185
|
+
// Mark tail calls on the final lowered program codegen compiles. Flags are set
|
|
1186
|
+
// in place, so the address-keyed type info stays valid.
|
|
1187
|
+
if let Some(lowered) = info.lowered.as_mut().and_then(std::rc::Rc::get_mut) {
|
|
1188
|
+
plum_core::tail::markTailCalls(lowered);
|
|
1189
|
+
}
|
|
1183
1190
|
result.map(|()| info)
|
|
1184
1191
|
}
|
|
1185
1192
|
|
|
@@ -4428,6 +4435,7 @@ fn inferOperatorCall(
|
|
|
4428
4435
|
.collect(),
|
|
4429
4436
|
line: 0,
|
|
4430
4437
|
column: 0,
|
|
4438
|
+
tail: ast::TailKind::NotTail,
|
|
4431
4439
|
})
|
|
4432
4440
|
})
|
|
4433
4441
|
.clone()
|
plum-core/src/ctors.rs
CHANGED
|
@@ -374,6 +374,7 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
374
374
|
args: vec![],
|
|
375
375
|
line: line,
|
|
376
376
|
column: column,
|
|
377
|
+
tail: TailKind::NotTail,
|
|
377
378
|
}),
|
|
378
379
|
})),
|
|
379
380
|
0,
|
|
@@ -502,5 +503,6 @@ fn rewriteCall(e: &mut Expr, plans: &HashMap<String, Plan>, errors: &mut Vec<Str
|
|
|
502
503
|
args,
|
|
503
504
|
line: 0,
|
|
504
505
|
column: 0,
|
|
506
|
+
tail: TailKind::NotTail,
|
|
505
507
|
});
|
|
506
508
|
}
|
plum-core/src/forms.rs
CHANGED
|
@@ -301,6 +301,7 @@ fn method(obj: &str, name: &str, args: Vec<Expr>) -> Expr {
|
|
|
301
301
|
args: args.into_iter().map(Arg::Positional).collect(),
|
|
302
302
|
line: 0,
|
|
303
303
|
column: 0,
|
|
304
|
+
tail: TailKind::NotTail,
|
|
304
305
|
}),
|
|
305
306
|
}))
|
|
306
307
|
}
|
|
@@ -1867,6 +1868,7 @@ fn yieldCall(args: Vec<Expr>) -> Expr {
|
|
|
1867
1868
|
args: args.into_iter().map(Arg::Positional).collect(),
|
|
1868
1869
|
line: 0,
|
|
1869
1870
|
column: 0,
|
|
1871
|
+
tail: TailKind::NotTail,
|
|
1870
1872
|
})
|
|
1871
1873
|
}
|
|
1872
1874
|
|
plum-core/src/parser.rs
CHANGED
|
@@ -1607,6 +1607,7 @@ impl<'a> AstParser<'a> {
|
|
|
1607
1607
|
args,
|
|
1608
1608
|
line: pos.row + 1,
|
|
1609
1609
|
column: pos.column + 1,
|
|
1610
|
+
tail: TailKind::NotTail,
|
|
1610
1611
|
})
|
|
1611
1612
|
}
|
|
1612
1613
|
None => AttrKind::Field(member),
|
|
@@ -1666,6 +1667,7 @@ impl<'a> AstParser<'a> {
|
|
|
1666
1667
|
args,
|
|
1667
1668
|
line: pos.row + 1,
|
|
1668
1669
|
column: pos.column + 1,
|
|
1670
|
+
tail: TailKind::NotTail,
|
|
1669
1671
|
})
|
|
1670
1672
|
}
|
|
1671
1673
|
None => AttrKind::Field(member),
|
|
@@ -1689,6 +1691,7 @@ impl<'a> AstParser<'a> {
|
|
|
1689
1691
|
args: vec![Arg::Positional(Expr::Closure(Box::new(closure)))],
|
|
1690
1692
|
line: pos.row + 1,
|
|
1691
1693
|
column: pos.column + 1,
|
|
1694
|
+
tail: TailKind::NotTail,
|
|
1692
1695
|
}),
|
|
1693
1696
|
}))
|
|
1694
1697
|
}
|
|
@@ -1716,6 +1719,7 @@ impl<'a> AstParser<'a> {
|
|
|
1716
1719
|
args,
|
|
1717
1720
|
line: pos.row + 1,
|
|
1718
1721
|
column: pos.column + 1,
|
|
1722
|
+
tail: TailKind::NotTail,
|
|
1719
1723
|
}
|
|
1720
1724
|
}
|
|
1721
1725
|
|
|
@@ -1752,6 +1756,7 @@ impl<'a> AstParser<'a> {
|
|
|
1752
1756
|
args: vec![part(0), part(1)],
|
|
1753
1757
|
line: pos.row + 1,
|
|
1754
1758
|
column: pos.column + 1,
|
|
1759
|
+
tail: TailKind::NotTail,
|
|
1755
1760
|
}))
|
|
1756
1761
|
}
|
|
1757
1762
|
_ => {
|
plum-core/src/union_lower.rs
CHANGED
|
@@ -168,6 +168,7 @@ impl<'a> Lowerer<'a> {
|
|
|
168
168
|
args: vec![Arg::Positional(value)],
|
|
169
169
|
line: 0,
|
|
170
170
|
column: 0,
|
|
171
|
+
tail: TailKind::NotTail,
|
|
171
172
|
}),
|
|
172
173
|
})),
|
|
173
174
|
0,
|
|
@@ -345,6 +346,7 @@ impl<'a> Lowerer<'a> {
|
|
|
345
346
|
args: vec![],
|
|
346
347
|
line: 0,
|
|
347
348
|
column: 0,
|
|
349
|
+
tail: TailKind::NotTail,
|
|
348
350
|
});
|
|
349
351
|
}
|
|
350
352
|
}
|
|
@@ -371,6 +373,7 @@ impl<'a> Lowerer<'a> {
|
|
|
371
373
|
args: vec![Arg::Positional(inner)],
|
|
372
374
|
line: 0,
|
|
373
375
|
column: 0,
|
|
376
|
+
tail: TailKind::NotTail,
|
|
374
377
|
});
|
|
375
378
|
}
|
|
376
379
|
}
|
|
@@ -696,6 +699,7 @@ isOwn: false,
|
|
|
696
699
|
args,
|
|
697
700
|
line: 0,
|
|
698
701
|
column: 0,
|
|
702
|
+
tail: TailKind::NotTail,
|
|
699
703
|
}),
|
|
700
704
|
}));
|
|
701
705
|
Case {
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -7586,6 +7586,7 @@ fn compileExprCore(
|
|
|
7586
7586
|
.collect(),
|
|
7587
7587
|
line: 0,
|
|
7588
7588
|
column: 0,
|
|
7589
|
+
tail: ast::TailKind::NotTail,
|
|
7589
7590
|
};
|
|
7590
7591
|
compileVariantConstruction(info, &synthetic_call, expr, body, ctx, state)?;
|
|
7591
7592
|
}
|
|
@@ -7645,6 +7646,7 @@ fn compileExprCore(
|
|
|
7645
7646
|
args: vec![],
|
|
7646
7647
|
line: 0,
|
|
7647
7648
|
column: 0,
|
|
7649
|
+
tail: ast::TailKind::NotTail,
|
|
7648
7650
|
};
|
|
7649
7651
|
compileMethodCall(
|
|
7650
7652
|
&object,
|
|
@@ -8414,6 +8416,7 @@ fn compileInterpolatedString(
|
|
|
8414
8416
|
args: vec![],
|
|
8415
8417
|
line: 0,
|
|
8416
8418
|
column: 0,
|
|
8419
|
+
tail: ast::TailKind::NotTail,
|
|
8417
8420
|
};
|
|
8418
8421
|
if plum_checker::nominalName(&other)
|
|
8419
8422
|
.is_some_and(|n| ctx.func_ids.contains_key(&format!("{}::toStr", n)))
|