Skip to content

Wintermute 0.3.6 — non-struct type-switch cases

Choose a tag to compare

@bsmr bsmr released this 17 Jul 13:47

Wintermute 0.3.6 widens the type switch with non-struct (primitive) cases and
whole-alias V — the sixth feature step of the 0.3.x transpiler line.

  • Non-struct cases → Erlang type guards. case int:/string:/bool:/
    float64: lower to V when is_integer(V) / is_binary(V) / is_boolean(V)
    / is_float(V). They land in the shared clause builder, so both the value
    form (case X of) and the receive form (receive) get them. A guarded
    non-struct clause in a receive is a selective-receive clause (it blocks on a
    non-match, never falls through).
  • Whole-alias V. The switch alias binds the whole matched value: always for
    a primitive case (the guard needs the named variable), for a struct case when
    the body uses bare V (Erlang alias pattern V = {tag, Fields}), and for the
    default: when its body uses bare V (catch-all binding V -> Body, since
    Go's default binds the alias to the whole original value). A field-only struct
    case is unchanged from 0.3.5 ({tag, Fields} ->, no alias binding).
  • default: stays REQUIRED for the value form. Unchanged from 0.3.5: a value
    matching no case falls through in Go, which a total Erlang case cannot
    express, so a default-less value switch is rejected.

Safety: the four guards and atom-tagged struct tuples are pairwise disjoint, so
Erlang's runtime first-match order coincides with Go's static per-case
exclusivity — no fall-through-vs-crash divergence. The default catch-all is
always emitted last regardless of source order. seenTag dedup extends to
primitives (a duplicate case int: is rejected).

Still rejected loudly (0.3.7+): multi-type cases (case Ping, Pong:), the
tagless form (switch M.(type)), wider primitive types (int32, float32,
byte, []byte, named types), and nil cases.

Built subagent-driven/TDD: three tasks (implementer + spec/quality review each) +
a whole-branch opus review. The Task-1 review caught a real silent mis-transpile
(a bare alias in a default: clause would have emitted an unbound Erlang
variable); it was folded into the correct catch-all binding before merge. The
whole-branch silent-mis-transpile hunt found nothing further.