Repository navigation
Let a callable member of a union parameter take an argument it accepts - #6690
Open
SanderMuller wants to merge 3 commits into
Open
SanderMuller wants to merge 3 commits into
SanderMuller wants to merge 3 commits into
Conversation
A parameter such as (callable(TValue): bool)|TValue records no lower bound for its callable member. The union loop then treated an argument that the callable member accepts as not taken, so the argument became a lower bound of the TValue marker next to it. After merge(), reject(isExcluded(...)) inferred Coll<Field|Closure>. The callable member now takes an argument it accepts, like a marker-free member does and like UnionType::inferTemplateTypes() does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A raw Closure is only maybe accepted by callable(TValue): bool, so it still flows into TValue. A Box<X> sent to Box<TValue>|TValue binds TValue to X through the Box member, which is not callable. Both cases kill the mutants that the first commit left: !isCallable()->no() and !isSuperTypeOf()->no(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Closes phpstan/phpstan#15414
With
unresolvedTemplateArguments(bleedingEdge),$a->merge($b)->reject(isExcluded(...))inferredColl<Field|(Closure(Field): bool)>instead ofColl<Field>. Laravel'sCollection::merge()andreject()have this shape.observeLowerBound()splits a union parameter. When a member takes an argument, the argument does not also flow into a naked marker next to that member. For(callable(TValue): bool)|TValue, the callable member never records a lower bound, because its template is in a contravariant position. The loop therefore saw no change and treated the Closure as not taken, so the Closure became a lower bound of theTValuemarker.Now a callable member takes an argument that it accepts. This is the same rule as for a marker-free member, and as in
UnionType::inferTemplateTypes(). A callable that the callable member does not accept, for examplereject(isInt(...))onColl<Field>, still flows intoTValueas on 2.3.0.TemplateArgumentObserver.cppgets the same change, and the second commit bumps the expected turbo version.Verification:
tests/PHPStan/Analyser/nsrt/bug-15414.phpfails without the change, withColl<Field|(Closure(Field): bool)>on line 63, and passes with it.\Closure, andBox<X>sent toBox<TValue>|TValue. Both behave as on the base. They fail under the two mutants that Infection reported on the first run,!isCallable()->no()and!isSuperTypeOf()->no().make phpstanreports no errors.smoke.phpreportsALL OK, andsignature-parity.phpreports OK. The raw analysis output ofsrc/Analyser/Genericsand the new test file is identical with and without the extension. Without the.cppchange, the turbo build still infers the Closure, so the port is needed. I could not runmake lint-turbo, because clang-tidy is not installed here.bin/phpstan analyse src/Type src/Analyserwith bleedingEdge and without turbo, 3 interleaved runs. The base took 7.09-8.05 s and 57.1-58.1 s user CPU, and this PR took 7.11-7.12 s and 57.6-58.4 s. The output is identical.🤖 Generated with Claude Code