diff --git a/NEXT.md b/NEXT.md index cc87e2b..927e371 100644 --- a/NEXT.md +++ b/NEXT.md @@ -25,7 +25,9 @@ already has the stack they would walk. Still open from §3, and each refused by name today: **restarts with parameters** (argument marshalling plus the runtime arity check), and `handler-case`, which §"What this does not settle" leaves open as possibly a -macro over `handler-bind` plus a transfer. +macro over `handler-bind` plus a transfer. `error`, `find-restart` and +`compute-restarts` now refuse by name too — they used to fall through to a call +and come back as *unknown name*, which is the house rule's own class of bug. Read SBCL for what restarts should *mean* and ignore how it moves control: it transfers with `block`/`return-from`, which §6 rules out. diff --git a/lib/parse.ml b/lib/parse.ml index 5524c5d..0f35d96 100644 --- a/lib/parse.ml +++ b/lib/parse.ml @@ -237,7 +237,9 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr = when cbody <> [] -> if ps <> [] then fail c - "a restart takes no parameters yet — spec-conditions.md §3 has them, and they need argument marshalling and a runtime arity check that this version does not do"; + "a restart takes no parameters yet — spec-conditions.md §3 has \ + them, and they need argument marshalling and a runtime arity \ + check that this version does not do"; { Ast.rname = n; rbody = List.map expr cbody; rloc = c.Form.loc } | _ -> fail c "a restart-case clause is (name [] body ...)" in @@ -252,14 +254,22 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr = mk (Ast.InvokeRestart n) | [ _ ] -> fail f - "invoke-restart takes a quoted restart name, as in (invoke-restart 'use-placeholder)" + "invoke-restart takes a quoted restart name, as in \ + (invoke-restart 'use-placeholder)" | _ -> fail f - "a restart takes no arguments yet — spec-conditions.md §3 has them, and they need argument marshalling and a runtime arity check that this version does not do") + "a restart takes no arguments yet — spec-conditions.md §3 has them, \ + and they need argument marshalling and a runtime arity check that \ + this version does not do") (* Recognised, deliberately unimplemented. Rejected rather than left to fall through to Call, where they would parse and mean nothing. *) | Sym ("handler-case" + (* Named in the spec and not written yet, so each says so rather than + falling through to Call and coming back as an unknown name: [error] + is §2's diverging signal, and [find-restart] and [compute-restarts] + are §4's two ways to look at the stack without committing. *) + | "error" | "find-restart" | "compute-restarts" | "errdefer" | "with-allocator" | "loop" | "recur" | "defmacro" | "await" as name) -> fail f "%s is not implemented yet (see the build sequence in plan.org)" name diff --git a/test/test_flan.ml b/test/test_flan.ml index c6fbed6..76277c4 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -691,9 +691,17 @@ let () = rejects_check "invoke-restart inside a defer" "(defn f [] i32 (defer (invoke-restart 'skip)) 0)" ~needle:"not allowed inside a defer"; - (* Still unimplemented, and still says so by name. *) - rejects_check "handler-case is still unimplemented" - "(defn f [] (handler-case 1))" ~needle:"not implemented yet"; + (* Still unimplemented, and still says so by name — which is the point: an + operator the spec names and the compiler lacks must not fall through to a + call and come back as an unknown name. *) + List.iter + (fun (name, src) -> + rejects_check (name ^ " is still unimplemented") src + ~needle:"not implemented yet") + [ "handler-case", "(defn f [] (handler-case 1))"; + "error", "(defn f [] (error 1))"; + "find-restart", "(defn f [] (find-restart 'skip))"; + "compute-restarts", "(defn f [] (compute-restarts))" ]; (* ── The acceptance program checks end to end ──────────────────── *) accepts "calc-me.flan type checks"