Fix #11687: Method call on unioned class type analysis is broken#4960
Open
phpstan-bot wants to merge 4 commits into2.1.xfrom
Open
Fix #11687: Method call on unioned class type analysis is broken#4960phpstan-bot wants to merge 4 commits into2.1.xfrom
phpstan-bot wants to merge 4 commits into2.1.xfrom
Conversation
- In UnionType::getUnresolvedMethodPrototype(), stopped overriding each member's calledOnType with the full union for non-template unions - Template union types (TemplateUnionType, TemplateBenevolentUnionType) still propagate the union as calledOnType to preserve template identity - Updated existing test assertion in static-late-binding.php - Added regression test tests/PHPStan/Analyser/nsrt/bug-11687.php Closes phpstan/phpstan#11687
Closes phpstan/phpstan#12562 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automated fix attempt 1 for CI failures.
Automated fix attempt 2 for CI failures.
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.
Summary
When calling a static method that returns
staticon a union of class types (e.g.,$clUnioned::retStatic()where$clUnionedisclass-string<A>|class-string<X>), PHPStan incorrectly resolved thestaticreturn type to the entire union instead of just the class that declares the method. This causedA::retStatic()(returningstatic) to resolve asA|Xinstead of justA.Changes
src/Type/UnionType.phpingetUnresolvedMethodPrototype(): stopped callingwithCalledOnType($this)for non-template union types. Each member type now keeps its own called-on type, sostaticresolves correctly per-class.TemplateUnionType,TemplateBenevolentUnionType) still pass the full union as the called-on type, preserving template type identity (e.g.,T of DateTime|DateTimeImmutablestays as the template type rather than being split).tests/PHPStan/Analyser/nsrt/static-late-binding.phpfrombool|StaticLateBinding\A|StaticLateBinding\Xtobool|StaticLateBinding\A.tests/PHPStan/Analyser/nsrt/bug-11687.php.Root cause
In
UnionType::getUnresolvedMethodPrototype(), each member type's method prototype had itscalledOnTypeoverridden with the full union via->withCalledOnType($this). Later, inCalledOnTypeUnresolvedMethodPrototypeReflection::transformStaticType(), when aStaticTypewas encountered in a method's return type, it was replaced with$this->calledOnType— the full union. This meantA::retStatic()returningstatic(A)would becomeA|Xinstead of justA.The fix conditionally applies
withCalledOnType($this)only when the union is aTemplateType(i.e.,TemplateUnionTypeorTemplateBenevolentUnionType), where preserving the full union/template identity is correct. For plain unions, each member keeps its own type as the called-on type.Test
Added
tests/PHPStan/Analyser/nsrt/bug-11687.phpwhich tests:A::retStaticConst()returnsintX::retStaticConst()returnsbool$clUnioned::retStaticConst()returnsbool|int(correct, nostaticinvolved)A::retStatic()returnsA(uses@return static)X::retStatic()returnsbool$clUnioned::retStatic()returnsbool|A(previously wasbool|A|X)Fixes phpstan/phpstan#11687
Closes phpstan/phpstan#12562