Fix type narrowed too much after identical comparison with never-typed generic method#5491
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Open
Fix type narrowed too much after identical comparison with never-typed generic method#5491phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…d generic method When a generic class is constructed with an empty array default parameter, the template type resolves to `never`. Method return types using this template (e.g. `TElement|null`) then collapse to `null`, causing `===` comparisons against non-null values to be seen as always-false, which makes subsequent code unreachable and narrows variable types to `*NEVER*`. The fix replaces `never` with the template's declared bound when resolving class-level template types in method return types, but only when the return type does not contain conditional types (which need the actual `never` value to evaluate correctly, e.g. `T is never ? false : bool`). Fixes phpstan/phpstan#14281
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
Fixes phpstan/phpstan#14281
When a generic class like
Collection<TElement>is constructed with an empty array default (new TestCollection()), the templateTElementresolves tonever. This causes method return types likeTElement|nullto collapse tonull. Subsequentassert($data[0] === $collection->get(0))comparisons are then seen as always-false (e.g.0 === null), making code unreachable and narrowing$datato*NEVER*.Root cause
In
ResolvedFunctionVariantWithOriginal::resolveResolvableTemplateTypes(), class-level template types that resolve toneverwere used literally in method return types. For the object type itself (TestCollection<never>), this is correct —neveris the bottom type and passes covariance checks. But for method return types, it produces overly narrow types that break type narrowing in the caller.Fix
When resolving class-level template types in method return types, if the resolved type is
never, replace it with the template's declared bound (e.g.mixedfor unbounded,stdClassfor@template T of stdClass). This is skipped when the return type containsConditionalTypenodes, since conditional return types like(T is never ? false : bool)need the actualnevervalue to evaluate correctly.Changed files
src/Reflection/ResolvedFunctionVariantWithOriginal.php— the fixtests/PHPStan/Analyser/nsrt/bug-14281.php— regression testtests/PHPStan/Analyser/nsrt/generics.php— updated assertion: emptyStdClassCollectionnow returnsarray<stdClass>fromgetAll()instead ofarray{}, reflecting the template bound instead ofneverImpact
Collection<never>::get()now returnsmixed(bound) instead ofnull(never|null)StdClassCollection<never, never>::getAll()now returnsarray<stdClass>instead ofarray{}(T is never ? false : bool)are preserved unchanged