Repository navigation
Dispatch tiers: a specific expectation answers before a catch-all, whichever order was declared - #156
Merged
Merged
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Fixes #152
What
Between overlapping specifications, dispatch now runs in two tiers: one that pins at least one argument to something narrower than "anything" — a literal, any matcher except
Arg::any(), or a tail pinned empty byArg::none()— is tried before one that says nothing about any argument (Arg::any()in every position, an unspelled optional parameter,Arg::rest()/remaining()), regardless of registration order. Within a tier, most-recently-registered still wins; two specifics are never compared against each other for narrowness (double PR #19's rule, applied to our richer matcher set).The trap from #136 can no longer be written: a catch-all registered after a specific stub used to take every call, leaving the specific one unreachable and undiagnosed — not even
strictStubsreported it. Now the specific one answers its own calls and the catch-all takes the rest. Tiering makes the ordering mistake impossible rather than diagnosed.Minor, considered — the compatibility policy quoted
The policy (README.md → Compatibility policy) freezes "the names, signatures, and readonly fields of the public API stable" across
1.x; none of those change here. What changes is the documented layering semantics: a suite that relied on a catch-all overriding a narrower stub declared earlier now sees the narrow one answer. That reliance means the specific stub was unreachable dead configuration answering nothing — a bug in the test, not a contract. CHANGELOG carries it as Changed, prominently. Shipping as part of the declared1.1.0minor boundary.The classification
Precomputed once per expectation (
Expectation::isGeneric(), a boolean read per candidate at dispatch — the issue's "one extra boolean per candidate at most"):AnyArgument,Unspelled(an optional parameter the specification left unspelled), and theAnyTail/AnyRest/UnspelledTailfamily say nothing; a literal, any narrowing matcher, andEmptyTail(Arg::none()pins the tail empty) pin.Applied exactly where the issue points:
DoubleState::expectationsFor()merges the literal-indexed and walked lists through one(tier asc, declaration desc)sort.hasActionFor()— the by-reference slot walk — now passes$argsthrough, so it walks candidates in the same tier order dispatch does; the "same walk" invariant extends to tiers, and a test pins it.Perf — the A/B the issue asks for
Full harness, three runs per side, same pinning, one sitting (master
4715d3c-baseda05d04avs this branch):Understudy moved −2.8% to +3.1% while the competitors moved −3.9% to +0.8% in the same runs — inside the noise floor the recorded methodology documents (medians move 0.2–3.5% between runs). Nothing attributable; the recorded figures in
perf/README.mdand both READMEs stay. The measured candidate even carried the since-removedarray_reversecalls.Notes for reviewers
theAnsweringStubIsTheLastMatchingOne→theMostSpecificMatchingStubAnswers(tier-aware oracle + new examples),theIndexAnswersWhatALinearWalkWouldHave→theIndexAnswersWhatATieredWalkWouldHave, and the lifecycle state machine'sEngineStatemodel answers by tier.mutation-diff: 33 mutants, zero escaped after restructuring (the equivalentarray_reverse/guard mutants died by simplification — the tier sort subsumes the reverse).stubbing/index.md,migrating-from-phpunit.md, regeneratedMIGRATION.md); the "register the broad stub first" advice is gone from everywhere it lived.docs-claimsgreen (66 claims).