If a frame mutates a global and then signals, taking a retry re-runs the
mutation. Control resumes at the restart-case and runs forward; nothing is
undone. Common Lisp has the same property and offers no help either, so this
is written down rather than fixed.
The discipline is that the author chooses where the retry boundary is: a
restart-case above the mutations re-runs them, one below re-runs only what
follows. Put the restart before anything mutates, make the retried section
idempotent, or snapshot what will be re-applied.
It matters more here than in most Lisps because the intended use is a game
loop, and a bad index signalling BoundsError rather than ending the process
made abandoning and retrying a frame an ordinary thing to do.
conditions.org and web/index.html already carried the mechanical half as a
one-line gotcha; those are rewritten in place rather than gaining a second
bullet beside them. spec-conditions.md takes it in section 5, which already
enumerates what a transfer does and does not do. No numbered case changed
meaning.
The slot after a defn's parameters is unconditionally a type. Parse.decl no
longer takes a set of type names, and is_type_form, qualified_type, types_in,
declared_types and prelude_types are gone with the pre-pass that fed them.
What they were for: (Option f64) and (Some 1) are the same s-expression, so the
parser decided which it had by looking the head up in a set of the file's own
type names. Sound -- one top-level namespace means a name cannot be both a type
and a value -- and brittle, because the set had to be complete. It was wrong
twice in one day, the second time parsing (defn f [] (Rune {.code 65}) (bar))
as a function returning a Rune with a one-form body, silently, in every file in
the language.
Two things fall out. A type the parser could not have known -- a struct
declared further down the file, rl/Vector2 behind an unresolved alias, a
prelude type -- never needed recognising, only placing. And a mistyped type is
a mistyped type: (defn f [] f65 0.0) reaches the resolver's near-miss check and
says did you mean f64, where it used to be read as the first form of the body
and reported as an unknown name.
Unit is written (). The old spelling is refused with a message naming the new
one, the rule the colon-to-dot change followed. Internally it is still
Tname "Unit" and Types.Unit, so the resolver, the shim and the emitter did not
change; Cimport still builds Tname "Unit" for C's void without going through
the parser. Types.to_string prints () though -- that printer prints what a
person would write for every other type it knows, [i32], {K V}, (Ptr T), and
Unit was the odd one out once the source spelling moved.
Dropping prelude_types removes one of the two reasons Macro.reduce may only
drop defns: the memoised set a bootstrap build could have poisoned is gone, so
the remaining reason is the plain one.
§4 meets §3, and the answer a reader will assume is the other one. An
inner (use-value [s string] ...) shadows an outer (use-value [v i32] ...),
so an i32 is refused there and the outer clause that would have taken it
is never consulted. Searching outward for a frame whose signature fits
would make which restart runs depend on the arguments, which is overload
resolution on a dynamic stack.
Also: neither of the new guards is a bounds check, so --no-bounds-checks
does not remove them. A wrong index is a wrong answer; a transfer into a
clause whose parameters were written to a different layout is not.
The language half of §3's parameters is in; the half that makes it worth
having is not. A break loop chooses by position and has nothing to fill a
clause's parameters in with, and that is now the top item in NEXT.md,
spelled out end to end — the accessors the frame can already answer, the
signature on the wire, and the one store that has to happen before the
channel is aimed.
§3 asks for a clause's report string to be settled before parameters and
it was not. The field is cheap and so is the accessor; the only thing
that would read either is the break loop's listing, which lives in the
agent and the daemon, so it would have shipped as a field nothing read.
It belongs with the editor half, which is changing that listing anyway.
spec-conditions.md §4 gains the rule the shadowing bug was hiding: a handler
matches by name, a debugger identifies by position, and the two are not the
same question. With it, the snapshot — a position means nothing against a
stack that moves — and the fact that a visible restart may still be
unreachable, which §6's explicit lowering makes possible.
§3 records the open one: a clause should carry a report string.
is what invoke-restart needs and not what a person reading a list needs. It
wants settling before restarts with parameters, which is where a bare name is
least sufficient.
conditions.org had the break loop under "Not yet", which it has not been for
some time, and now says why find-restart and compute-restarts still are: they
are blocked on a Restart type and a list to return one in, not on effort.
The cheatsheet was a document. It should fit on a screen: the syntax, what is
missing, the traps that are not in any error message, and the three ways it
stops. What was cut is either in spec-conditions.md or in the compiler's own
refusal, and both say it better.
conditions.org is how to drive what is built, as against spec-conditions.md
which is what it should mean and NEXT.md which is why it is shaped that way.
Every refusal message in it is verbatim rather than paraphrased, because the
reason is the thing worth knowing and a remembered approximation of it is how a
cheatsheet starts lying.
conditions-play.flan is the program to poke at. It loops rather than exiting so
flan dev can attach to it, and it is deliberately two frames deep with a defer
in the middle, so that redefining probe or fetch from Emacs and watching the
next pass through run-once shows the transfer crossing something.
It also carries the two gotchas that are not in any spec or message: a handler
closes over nothing, and invoking a restart re-runs whatever sits between it
and the target - which is what "restarts go at the resync point" is actually
about.