Rule priorities, computed from the patterns rather than typed - #1106
Merged
Conversation
#746 tier 2 asks for "rule priorities and conflict resolution, with confluence and termination checked by tooling rather than asserted by authors". A rule set is first-match-wins, so where two rules fire at one node and disagree, whichever is tried first decides the answer -- and until now that was where somebody typed it. The one place it is written down is a comment on MatchedRules.CollapseMultipleFractions: the set "is order-dependent, since `Mulf(Divf, Divf)` has to be tried before `Mulf(a, Divf)` or the more general rule would swallow the special one". That is subsumption, observed by hand and then maintained by hand. `MatchPattern.Subsumes` computes it. It is a matching problem rather than a size comparison: a hole repeated across a pattern is an equality constraint, so `Mulf(a, a)` matches strictly less than `Mulf(a, b)` while having the same node count. So this matches one pattern against the other as a term, carrying an assignment from the wider pattern's holes to the narrower one's subpatterns and requiring a repeated hole to be assigned consistently. Sound in one direction only: true is a structural claim about every expression, false means not proved -- a hole carrying a predicate is arbitrary code, an Exact literal can equal a value of another runtime type, and a Gathered pattern is not reasoned about. Each of those refuses and leaves the pair ordered as written. `MatchedRuleSet.RulesByPriority` applies it, as a stable topological sort: no rule is tried before a rule its pattern subsumes, and declaration order everywhere else. **Four things measured.** Subsumption does not lie. Over all 322 rules, **961** ordered pairs claim it; **513** are put to the test by the corpus containing something the narrower pattern matches; **none** is contradicted across 56,892 nodes. The count of witnessed claims is asserted too, so a corpus that stopped reaching these shapes cannot turn that into a test which passes by asking nothing. It changes no answer today. Of the 5,480 within-set rule pairs, subsumption has an opinion about **28** -- across eight sets, Boolean and InequalityEquality carrying half -- and in every one the specific rule is already declared first. None is mutual. So this orders what was already ordered; what it adds is that inserting a general rule above a specific one no longer silently reverses an answer. `RulePriorityTest` holds the two orders equal, so the file stays readable as well as correct. The existing confluence corpus cannot see any of it. RuleConfluenceTest grows its third level with unary shapes only, so it never builds a quotient of quotients or a product of quotients -- which is exactly where a special rule and the general rule that would swallow it meet. On that corpus **none** of the 28 pairs overlaps at all. Growing the third level with binary shapes too takes the conflicts from **3 to 45**: six settled by priority, and **39** where neither pattern subsumes the other and the answer is decided by which was written first and by nothing else. Those 39 are recorded by name -- the data rules have names, which is what the note on `AConflictIsReportableAsThePatternsItIsBetween` asks for and what a `switch` arm cannot supply. None of them changes what Simplify returns. And one test asserted the opposite of this and had to change. TheOrderOfTheRulesIsLoadBearing built the set with its rules reversed and required the general rule to swallow the special one. It does not any more, which is the whole point -- the test's own comment gave the reason, that a `switch` gets its ordering "by accident of being written top to bottom". Replaced by two: the specific rule wins however the set is written, and where neither rule subsumes the other the order still decides everything. The classification also surfaced `quotient-of-two-quotients` answering 0 where the general rule answers NaN, on `x / (1/2) / (1 / 0)`. Left alone: Simplify returns NaN for that input, priority picks the same arm the declared order already picked, and the missing hypothesis is what Soundness.SoundUnderAssumptions on that rule is declaring. No public API change. Full suite: 8968 passed, 14 skipped, 0 failed. Part of #746 tier 2.
This was referenced Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#746 tier 2 asks for "rule priorities and conflict resolution, with confluence and termination
checked by tooling rather than asserted by authors". This is the priorities half;
RuleConfluenceTestwas already the confluence half.A rule set is first-match-wins, so where two rules fire at one node and disagree, whichever is
tried first decides the answer — and until now that was where somebody typed it.
The one place it is written down is a comment on
MatchedRules.CollapseMultipleFractions: the set"is order-dependent, since
Mulf(Divf, Divf)has to be tried beforeMulf(a, Divf)or the moregeneral rule would swallow the special one". That is subsumption, observed by hand and then
maintained by hand.
MatchPattern.SubsumesA matching problem, not a size comparison. A hole repeated across a pattern is an equality
constraint, so
Mulf(a, a)matches strictly less thanMulf(a, b)while having the same nodecount — comparing
NodeCountwould call those two equally general and get the ordering wrong inthe one case the ordering exists for. So this matches one pattern against the other as a term,
carrying an assignment from the wider pattern's holes to the narrower one's subpatterns and requiring
a repeated hole to be assigned consistently.
Sound in one direction only.
trueis a structural claim about every expression;falsemeans notproved, never disproved — a hole carrying a predicate is arbitrary code, an
Exactliteral can beequal to a value of another runtime type (a rational that reduced to an integer), and an n-ary
Gatheredpattern matches a family this does not attempt to reason about. Each refuses, and leavesthe pair ordered as written, which is what the code did before.
MatchedRuleSet.RulesByPriorityapplies it as a stable topological sort: no rule is tried before arule its pattern subsumes, declaration order everywhere else.
Four things measured
Subsumption does not lie. Over all 322 rules — the relation is about patterns, and nothing about
it stops at a set boundary — 961 ordered pairs claim it, 513 are put to the test by the
corpus containing something the narrower pattern matches, and none is contradicted across 56,892
nodes. The count of witnessed claims is asserted as well, so a corpus that stopped reaching these
shapes cannot quietly turn that into a test which passes by asking nothing.
It changes no answer today. Of the 5,480 within-set rule pairs, subsumption has an opinion about
28, and in every one the specific rule is already declared first. None is mutual.
InequalityEqualityBooleanCollapseMultipleFractionsCollapseTrigonometricFunctions,Common,ExpandFactorialDivisions,FactorizeFactorialMultiplications,PowerSo this orders what was already ordered. What it adds is that inserting a general rule above a
specific one no longer silently reverses an answer — and one of those eight sets says anything about
it in its own documentation, which is the argument for computing it rather than writing it down.
RulePriorityTestholds the two orders equal, so the file stays readable as well as correct.The existing confluence corpus cannot see any of it.
RuleConfluenceTestgrows its third levelwith unary shapes only, so it never builds a quotient of quotients or a product of quotients — which
is exactly where a special rule and the general rule that would swallow it meet. On that corpus
none of the 28 pairs overlaps at all. Growing the third level with binary shapes too takes the
conflicts from 3 to 45:
quotient, four of them the pair
CollapseMultipleFractionsdescribes in prose;first and by nothing else.
Those 39 are recorded by name. The data rules have names, which is what the note on
AConflictIsReportableAsThePatternsItIsBetweenasks for — "the failure message has to name thearms, not their indices — an index moves whenever somebody edits the
switch, which is exactly whenthis test fires" — and what a
switcharm cannot supply. None of the 39 changes whatSimplifyreturns; the normalisation and the passes after it converge.
And one test asserted the opposite of this.
TheOrderOfTheRulesIsLoadBearingbuilt the set withits rules reversed and required the general rule to swallow the special one. It does not any more,
which is the whole point, and the test's own comment gave the reason: a
switchgets its ordering"by accident of being written top to bottom", and an accident is what an ordered list of values
does not have to inherit. Replaced by two — the specific rule wins however the set is written,
and where neither rule subsumes the other the order still decides everything.
One thing the classification surfaced and this leaves alone
quotient-of-two-quotientsanswers0where the general rule answersNaN, onx / (1/2) / (1 / 0). Left alone deliberately:SimplifyreturnsNaNfor that input, prioritypicks the same arm the declared order already picked so nothing here changes it, and the missing
hypothesis is what
Soundness.SoundUnderAssumptionson that rule is declaring. Recorded because thetooling found it, not because this PR fixes it.
State
No public API change. Full suite on this branch: 8925 passed, 14 skipped, 0 failed.
Independent of #1105 — the two touch no file in common and can merge in either order.
Part of #746 tier 2.