spec-memory.md's generics section still said there were no constraints and wrote
its type variable as a bare lowercase name, which is the spelling the $t sigil
replaced -- the largest example in it would not have compiled. The where clause
and the five predicates are plan.org's account and this now agrees with it rather
than contradicting it. The empty-map example named no types and used defvar,
neither of which is how map-new is called.
spec-conditions.md named BoundsError once and nothing else. The four conditions
the runtime signals, and the split between the two that establish a retry and the
two that deliberately establish nothing, belong in \xc2\xa75 because that is the section
whose account of restarts the split could have falsified. It does not: the rule is
that the restart a bad index wants is the one the program already had.
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.
web/index.html's Flan blocks convert and its output blocks do not, which
is the same split render.ml makes: the printed form keeps the colon until
the Emacs inspector that reads it moves too. Same in BUILT.md.
plan.org, spec-conditions.md and spec-memory.md carried struct literals in
the old spelling and now do not.
NEXT.md decision 6 is struck, and batch item 2 with it, naming what to run
at merge. BUILT.md says why the colon belongs to keys -- mostly that a map
literal wants {:key value}, and two literals sharing one syntax would have
left the reader asking the checker which it was looking at.
The sweep was not idempotent and is now: {.k :hi} -- a field already
converted, holding an enum member -- read as a destructuring pair on a
second run and ate the member. A re-run over a lane's files would have
corrupted them silently, which is exactly what the tool exists to do
safely.
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.
plan.org now says every Flan function carries the transfer channel, that
uniformity is what keeps indirect calls and hot reload ABI-safe, and that a
later optimisation cannot change the ABI. spec-conditions.md §6 still read the
other way round - escape analysis deciding which functions are
transfer-transparent, with the rest paying nothing - which describes a
signature that depends on an analysis, and a cell cannot hold one of those.
So the analysis is demoted to what it can still honestly do: a function that
provably cannot transfer need not check the channel after a call and can pass
the pointer straight through. It may not drop the parameter. NEXT.md said the
same thing as a for-later note and now says it is settled.
The stated reason was that native and wasm32 must behave identically, which is
misleading: we do not develop on wasm32 and only ever export to it. The reasons
that hold are all release-side. wasm32 cannot unwind without the exceptions
proposal, so the export would not work at all. Native unwinding is not cheaper
and is much less legible - every call becomes an invoke with a landing pad,
where a cmp/jne after a call reads like ordinary code, which will matter once
there is a disassembler. And one mechanism is one thing to get right, since the
acceptance table runs the same programs on both targets and compares a hash.
The channel is an out-parameter rather than a discriminated return value, which
§6 had left open. The return type then stays what the source says; a
discriminated return would repack every ret, turn an aggregate return into an
sret call, and nest inside the discriminated return (Option T) already is. One
pointer threads down the chain, so a callee writes the target into its caller's
own slot and each frame only checks and returns early - reusing the existing
return path and therefore §5's defers.
A single global would be more legible still, with no signature change at all,
but it is not re-entrant: §5 runs defers during a transfer, so a defer that
signals and invokes a restart would start a second transfer over the first.