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.
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.
1match result {2 Ok(user) => show(user),3 Err(error) => report(error),4}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 narrowingOther languages need conventions or runtime guards.
JavaScript switch over kind
Python match over dataclass shape
Java visitor over sealed hierarchy
C switch over enum tagThe merge system should not pretend these are the same.
It should ask: what is the variant universe, and did the output cover it?
- Case textweakCan proveA branch label appears in source.Stops atDoes not prove it belongs to the variant set.Attach source span
- Variant identityboundCan proveA case maps to a known variant.Stops atDoes not prove every variant is handled.Build variant map
- Payload shapestaticCan proveThe branch receives the expected data.Stops atDoes not prove guard order or fallthrough.Gate branch shape
- ExhaustivenesscoverageCan proveThe variant universe is covered.Stops atDoes not prove runtime side effects inside arms.Admit dispatch shape
- Runtime traceobservedCan proveRepresentative branch behavior occurred.Stops atOnly proves traced values and state.Admit bounded behavior
Guards And Order Matter
Patterns can contain conditions:
Some(user) if user.active
case { kind: "user", active: true }
case x where x.isReadyA 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 variantThose are different claims.
Semantic merge should record which one is true.
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.