Wintermute 0.3.4 — type-switch receive
Fourth step of the 0.3.x transpiler-language line: the type switch over a
received message. Go's other type-branching form now maps to Erlang's central
process construct — a multi-clause receive.
-
switch v := otp.Receive().(type)→ multi-clausereceive— a process
waits for a message and dispatches on its type:switch v := otp.Receive().(type) { case Ping: otp.Print(v.Data) case Pong: otp.Send(v.To, v.Payload) default: otp.Print("unknown") }
→
receive {ping, Data} -> io:format("~p~n", [Data]); {pong, To, Payload} -> To ! Payload; _ -> io:format("unknown~n", []) end
Only struct-typed cases are supported: each
casenames one declared struct,
lowered to the tagged tuple{tag, Field…}via the same pattern builder the
0.3.1 single-clause receive uses (tag = lowercased type name, each field bound
to its capitalized Erlang variable, in declaration order).case *Pingequals
case Ping— Erlang has no pointers — but two cases that would share a tag
(both forms of one type, or names differing only in case) are rejected, since
the second clause would be unreachable. Field accessv.Fieldlowers to the
bound variableField; a bare use of the aliasvis rejected. Each clause
runs in its own binding scope (boundsnapshotted and restored), so a field
collision with an outer binding is still rejected — the fourth
bound-set-integrationcontext. -
default:is optional, and the choice is semantic: with adefault:the
receive gets a trailing catch-all_ ->(matches any other message
immediately); without one it is a selective receive — non-matching messages
stay in the mailbox and the process blocks until a listed type arrives. Both
are idiomatic Erlang. Accordingly,terminates()counts a type-switch receive
as terminating once every clause terminates, with NO default required (a
receivecannot fall through), so it may be the then-branch of a bareif.
Correctness is proven end-to-end by a real-toolchain rung: a dispatcher fixture
transpiles, compiles with erlc, and runs — a {ping, …} message hits the
ping clause and a {pong, …} the pong clause, each returning its checked
payload.
Deliberately deferred to 0.3.5+ (all still error): a type switch over a plain
value (→ a case), non-struct cases (→ Erlang type guards), multi-type cases
(case Ping, Pong:), passing the whole aliased value, and after-timeout /
fallthrough / init / tagless forms.
Built subagent-driven and TDD: six tasks (fresh implementer + spec-and-quality
review each), a whole-branch opus review returning "ready to merge" after an
adversarial silent-mis-transpile hunt found none, and two post-review cleanups
(unified struct-type check, removed a dead statement case) folded before release.