Issue #986: async (ADR-async-4, async-2): checker - wait points, groups, touch sets, handles, par_iter lambdas

Open and answer this issue on GitHub.

blocked · opened 2026-10-30 · updated 2026-10-30 · 0 comments

Part of #983. Records: ADR-async-4 D2-D5, D7, D10-D14; ADR-async-2 D1, D3, D5, D8, D9; ADR-async-3 D2, D7; ADR-async-1 D5, D7-D9, D11; ADR-346 D3 (changed by async-4 D14). Spec: Part I 8.1.2, 8.2; Part II 12.6; Part III NK2104, NK2107, NK2109, NK2110-NK2112, NK2115, NK2118-NK2122, NK1132, NK2202, NK2203, NK2209. Needs #984 (parser).

To build

  1. Wait points: let x = async e before the first statement naming any of its names anywhere nested (except a mention inside another task: that task waits); an argument before its call; an operand before its operation; an unnamed binding or statement form at its group’s end.
  2. Groups: the innermost async { } in the same function, else the innermost block. NK2110 for a task with nothing beside it (an argument with no sibling computed beside it, a binding named in the next statement, a statement ending its group; help removes the word); NK2111 for an async { } with no task; NK2120 for async in a loop body.
  3. Captures (async-3 D2): a child task borrows names bound outside its group and takes those bound in a block that ends earlier (NK2105); async(detached) takes everything (NK2101, the existing spawn rules).
  4. Touch sets (check_walks.nika, touch.nika): NK2104 between a bound task and each window statement, between the tasks of one call’s arguments / one operation’s operands and what is computed beside them, and a par_iter lambda of map/filter/for_each against itself (the stdout case: xs.par_iter().for_each fn(x) { println(…) } is refused); limit: 1 exempts; C.4. NK2107 as before.
  5. Pausing par_iter lambdas: NK2209 no longer asked of map/filter/for_each lambdas; still asked, finally, of fold, par_pieces, par_fold (async-2 D9). The help of NK2209 on a fold lambda names the chain map (pausing) followed by fold, e.g. xs.par_iter().map fn(u) { http::get(u) }.fold(…). Record per site pieces / one task per element in the ledger; NK2119 (warning) where the committed ledger had pieces and the lambda can now pause, naming the call (the NK2211 mechanism).
  6. Handles: async(handle) is a task::ChildHandle[T]; one that leaves its group is NK2122; an unjoined one is waited at the group’s end (not NK2109). async(deferred) not started or cancelled on some path, or started twice: NK2121 (the path rule NK2109 uses). async(detached) handles keep NK2109.
  7. join(within:) and NK2109 (async-4 D14, Part I 8.2): a join(within: d) without on_timeout: task::OnTimeout::Cancel takes a TaskHandle only on its success path (and on a failure of the task’s body). On the path where it throws task::TimedOut the handle is still live and must be taken on every path out of the handler: in a match error arm for task::TimedOut, in an else or binding arm that can match it, in the whole handler where it does not match on the error; otherwise NK2109. join(within: d, on_timeout: task::OnTimeout::Cancel) takes it on both paths. A live ChildHandle after a timed-out join is waited at its group’s end, not NK2109.
  8. Chaining ownership: read/read allowed; window change refused (NK2104); a task moving a pending binding hands it over at its start (NK2105 later); diamonds.
  9. Types: async e has e’s type; the callee sees T. async fn { … } the lambda’s result type. Handles their types.
  10. Jumps: leaving a task is NK1132; return inside async fn { … } ends the task.
  11. Options: limit: on a one-task form NK2112; types of limit, within, admission; on_timeout: task::OnTimeout on join (Keep by default, or Cancel); limit:/within:/admission: on par_iter, limit: on fold/par_fold (workers: refused as an unknown option).
  12. Pausing and locks: a body holding an async form is not sync (NK2202); an async form in an open door NK2203.
  13. NK2118: a mention after a catch where the failure arrived took it, without a fallback.
  14. Tests (tests/compiler/tests/async_*.nika with .stderr), among them: two tasks printing refused; a par_iter lambda printing refused; limit: 1 accepted; f(async g()) alone refused; async in a loop refused; a child handle returned refused; a deferred handle never started refused; chained read/read accepted; NK2119 after a ledger change; a join(within:) whose task::TimedOut handler drops the handle refused (NK2109), the same with on_timeout: task::OnTimeout::Cancel accepted; a pausing fold step refused with the map-then-fold help.

The names are final (async-4 D13).


Generated by Claude Code

Back to Open Issues