v0.7.0
A minor rather than a patch: Invocation loses two public properties,
verify() refuses argument combinations it used to resolve by precedence, and
notADouble() gains a required argument. Composer's caret treats that as
breaking on 0.x, and it is — though every one of them was the engine knowing
something and not saying it.
-
Invocation::$fileand$lineare gone. They were promoted readonly
properties on an@apiclass that the dispatcher never filled in and
nothing ever read — public API whose only value wasnull. Filling them
would mean adebug_backtrace()on every dispatched call, which this
package will not pay for a field nobody asked for. -
verify($call, never: true, times: 3)is refused rather than resolved.
neverused to win and the count beside it was discarded without a word, so
a test could say two opposite things and pass. Both analyser packages report
this pairing and both promise a runtime counterpart for every complaint they
make; this is it, in their wording, so a user does not meet a second
phrasing of a mistake they have already seen. -
verify($call, times: -1)is refused as the nonsense argument it is.
The bounds now go throughCardinality, which is what the fluenttimes()
has always used, so a negative count and a maximum below the minimum are
caught where they are written.times: -1used to become the range
[-1, -1]and fail as an unmet count. -
A facade method handed something that is not a double names itself.
Understudy::strict(new stdClass())used to answer "the specification
closure did not call a method on an understudy" — andstrict()has no
closure, so the reader was sent looking for a mistake they had not made.
strict,lean,forwarding,label,unused,nothingElse,
allVerifiedandtranscripteach say their own name now, the way
forget()already did. Knowing which facade was called also separates two
mistakes that used to share one message: an object that never was a double,
and a double whose context has been reset — the latter now gets the same
ForgottenDoubleanswer a CALL on that object already got. -
A loose double answers
: staticwith itself. The receiver IS the
double and the generated method really does declarestatic, so a fluent
contract chains without a stub for each link. It used to refuse with
NoDefaultValue— the one return type whose answer needs no invention at
all. Both READMEs say so, and say why: selfis a different claim. -
A return-type conflict no longer prints an unresolved
parent. The message
paths rendered the type without the declaring class, so a reader on PHP 8.3
or 8.4 was told a target declares: parentand left to work out which
class that is; 8.5 resolves it in Reflection, which is why it went
unnoticed. Pinned by a test that fails on 8.3 without the fix. -
Runtime\Modeis@internal, like the rest ofRuntime\. It never
appears in a public signature —strict(),lean()andforwarding()are
what a user writes — so the@apion it promised a contract nobody could
reach and no document described. -
Two orphaned docblocks removed, and
WhenBuildersays why it is the one
class here that is notfinal.FileWrapper::install()says that it only
ever widens, and that a target list added to the global mode changes nothing
because global already reaches every class it names. -
A targeted
bypassFinals()stops tokenising files it has no interest
in. The guard was "does the source contain the wordfinal", and
finallycontains it — 58% of a real vendor tree passes, and in targeted
mode almost none of it declares anything asked for. A file that never names
a target's class cannot declare that class, so it never reaches the
tokenizer: 16 µs against 256 µs on a 41 KB file, or roughly a second of
tokenising per process. The filter is a NECESSARY condition, which is why it
is safe — its absence is proof, its presence decides nothing and the
tokenizer still says. -
QueryEqualskeeps building itsReflectionMethodper call, and that
is the answer rather than an omission: caching the decision per class and
calling the getter directly measures 94 ns against 104 ns, which is 0.6% of
the 1.65 µs a dispatch through this matcher costs. The number is in the
code, so the next audit reads it instead of re-asking.
Fixes #95.