SHAPE

SHIFT

Back to homepage

Pattern Matching Is Control Flow

Pattern matching looks like syntax.

For semantic merge, it is control flow.

A match, switch, destructuring branch, sealed-class visitor, or discriminated-union check decides which path a value takes through the program. If a merge changes a variant, payload, guard, default branch, or branch order, it may change behavior even when the edited text is small.

That is why pattern matching belongs in the semantic model.

Pattern MatrixVariant Dispatch Needs Proof
SurfaceClaimRequired proofCurrent evidenceRoute
RequiredVariant identityThe same cases exist in the outputVariant or discriminator mapOk, Err, Some, None, case namesPreserve identity
RequiredPayload shapeEach case carries the same dataField or tuple payload evidenceNamed fields, tuple slots, nested payloadsGate shape
MissingExhaustivenessEvery possible value is handledCoverage proofAll variants, wildcard, default branchRequire proof
MissingGuardConditions run in the same orderGuard evaluation evidenceif guards, where clauses, predicate orderReview order
MissingFallthroughBranch boundaries are explicitNo-fallthrough or fallthrough proofbreak, return, throw, default, switch semanticsBlock ambiguity
A pattern proof is not only about case names. It has to bind identity, payloads, coverage, guards, and branch behavior.

Variants Are Identities

Consider a source type:

Result<T, E>
  Ok(T)
  Err(E)

The useful identity is not only the word Result.

The variants are identities too.

Ok carries success data. Err carries failure data. A change that adds a field to Err, renames a discriminator, or moves a handler from Ok to default is not a harmless local edit.

It changes the route a value takes.

A Pattern Is A Dispatch Table
1match result {2  Ok(user) => show(user),3  Err(error) => report(error),4}
Source dispatchThe match is exhaustive because Ok and Err are the full variant set.
A default branch can be valid code while still losing the source claim that every variant was handled by name.

Exhaustiveness Is Evidence

Some languages can prove exhaustiveness statically.

Rust enum match
Swift enum switch
Kotlin sealed when
Scala sealed trait match
TypeScript discriminated union narrowing

Other languages need conventions or runtime guards.

JavaScript switch over kind
Python match over dataclass shape
Java visitor over sealed hierarchy
C switch over enum tag

The merge system should not pretend these are the same.

It should ask: what is the variant universe, and did the output cover it?

  1. Case textweak
    Can proveA branch label appears in source.
    Stops atDoes not prove it belongs to the variant set.
    Attach source span
  2. Variant identitybound
    Can proveA case maps to a known variant.
    Stops atDoes not prove every variant is handled.
    Build variant map
  3. Payload shapestatic
    Can proveThe branch receives the expected data.
    Stops atDoes not prove guard order or fallthrough.
    Gate branch shape
  4. Exhaustivenesscoverage
    Can proveThe variant universe is covered.
    Stops atDoes not prove runtime side effects inside arms.
    Admit dispatch shape
  5. Runtime traceobserved
    Can proveRepresentative branch behavior occurred.
    Stops atOnly proves traced values and state.
    Admit bounded behavior
Pattern evidence gets stronger as it moves from labels to variant identity, payloads, coverage, and runtime behavior.

Guards And Order Matter

Patterns can contain conditions:

Some(user) if user.active
case { kind: "user", active: true }
case x where x.isReady

A guard is not only a filter. It is an ordered predicate.

If two branches can both match, order becomes meaning. If a target reorders guards, combines cases, or converts guards into nested if statements, the output needs proof that the same value reaches the same branch.

Fallthrough has the same problem.

A source match arm usually does not fall through. A target switch might, unless it returns, breaks, throws, or is otherwise proven to stop.

That is why fallthrough should be explicit evidence, not a style preference.

Defaults Are Not Exhaustiveness

A default branch is useful.

It can protect runtime behavior when a value is unknown. It can preserve compatibility with future variants. It can make generated code smaller.

But a default branch is not the same claim as exhaustive coverage.

all variants named
wildcard intentionally accepts every remaining variant
default catches unknown runtime values
default hides a missing named variant

Those are different claims.

Semantic merge should record which one is true.

Pattern Route TableHow Pattern Changes Should Route
RouteSignalRequiresProduces
AdmitAll variants and payloads are representedCoverage proof and no fallthrough ambiguityDispatch constraint attached
ReviewDefault branch replaces named variantIntentional catch-all decisionBounded non-equivalence record
RebaseVariant was renamed but identity survivedVariant lineage evidenceUpdated branch labels
RerunGuard order depends on runtime valuesFixture or trace over branch conditionsRuntime branch proof
BlockPayload shape or exhaustiveness is unknownRecorded missing pattern proofNo admission
Pattern changes should route by the exact claim that is missing: identity, payload, coverage, order, or runtime behavior.

The Mental Model

Pattern matching is control flow with names.

The names matter.

The payloads matter.

The branch order matters.

The proof that every value reaches an intended branch matters.

Semantic merge should treat a pattern as a dispatch surface, not as decorative syntax around an if.

If the output cannot prove variant identity, payload shape, exhaustiveness, guard order, and fallthrough behavior, it should say what is missing and route the work from there.