Fix phpstan/phpstan#14188: template error: It breaks the contract for some argument types, typically subtypes.#5301
Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Closed
Conversation
…s-string parameter - Modified NewHandler::resolveType() to intersect the template type from the parameter's declared class-string<T> type with the concrete object type when evaluating `new $class()` after the class-string has been narrowed - Added rule regression test in tests/PHPStan/Rules/Methods/data/bug-14188.php - Added NSRT test in tests/PHPStan/Analyser/nsrt/bug-14188.php - Root cause: when class-string<T> is narrowed via === to a constant class string, `new $class()` lost the template type T and returned a plain ObjectType, causing the return type check to report a false positive "breaks the contract" error
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 a method has a
class-string<T>parameter andTtemplate return type, narrowing the parameter via$class === A::classand then doingreturn new $class()inside that branch produced a false positive: "Method should return T but returns A. Type A is not always the same as T. It breaks the contract for some argument types, typically subtypes."The fix preserves the template type relationship when resolving
new $class()after the class-string parameter has been narrowed.Changes
src/Analyser/ExprHandler/NewHandler.php: InresolveType(), when evaluatingnew $variable()where the variable is a function parameter with a declaredclass-string<T>type, intersect the template typeTwith the concrete object type. This producesT & Ainstead of justA, which is correctly accepted as a subtype ofT.tests/PHPStan/Rules/Methods/data/bug-14188.phpand test method inReturnTypeRuleTest.phptests/PHPStan/Analyser/nsrt/bug-14188.phpRoot cause
When
$classhas typeclass-string<T of MyInterface>and is narrowed by$class === A::class, the scope narrows$classtoConstantStringType('A'), losing the template type information. Thennew $class()resolves toObjectType('A')viagetObjectTypeOrClassStringObjectType(). The return type check comparesObjectType('A')againstTemplateType T, andTemplateTypeArgumentStrategy::accepts()returnsmaybewith the "breaks the contract" message becauseAis not always the same asTin general.The fix addresses this by looking up the function parameter's declared type when resolving
new $variable(). If the parameter was declared asclass-string<T>and the concrete object type satisfies T's bound, the result isT & A(an intersection), which is a proper subtype ofTand passes the return type check.Test
@template T of MyInterface,@param class-string<T> $class,@return T, where$class === A::classbranch returnsnew $class(). Expects no errors.new $class()inside the narrowed branch includes both the template type and the concrete type.Fixes phpstan/phpstan#14188