Skip to content

Fix phpstan/phpstan#11430: Two unbounded generics in conditional return are assumed to be always the same#5336

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-32r4c2y
Open

Fix phpstan/phpstan#11430: Two unbounded generics in conditional return are assumed to be always the same#5336
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-32r4c2y

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a method had two unbounded template types (e.g. T and S) used in a conditional return type like (T is S ? None : Some<T>), PHPStan incorrectly reported "Method never returns Some so it can be removed from the return type." This was a false positive — the method can clearly return either branch depending on the arguments.

Changes

  • Modified TemplateTypeTrait::tryRemove() in src/Type/Generic/TemplateTypeTrait.php to return null when asked to remove a TemplateType, preventing meaningless template bound narrowing
  • Added regression test in tests/PHPStan/Rules/TooWideTypehints/data/bug-11430.php
  • Added test method testBug11430 in tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php

Root cause

When resolving the conditional return type (T is S ? None : Some<T>), ConditionalType::getNormalizedElse() replaces the subject T in the else branch with TypeCombinator::remove(T, S). This called TemplateTypeTrait::tryRemove(S), which in turn called TypeCombinator::remove(mixed, S). Since plain MixedType::tryRemove considers mixed a supertype of any template type, it succeeded and returned mixed~S (mixed minus S). This created a new template type T of mixed~S.

The problem: when narrowType later compared the declared return type None|Some<T of mixed~S> against the actual return type None|Some<T>, the invariant generic variance check used equals() to compare T of mixed~S with T. Since these aren't equal (different bounds), isValidVariance returned No, causing Some<T of mixed~S> to be reported as never returned.

The fix prevents subtracting a TemplateType from another TemplateType's bound, since template types represent unknown types and their subtraction doesn't produce useful narrowing information.

Test

Added a regression test reproducing the exact scenario from the issue: a final class with a static method using two unbounded generics in a conditional return type. The test expects no errors (the false positive is eliminated).

Fixes phpstan/phpstan#11430

…rn assumed same

- Don't subtract TemplateType from another TemplateType's bound in tryRemove
- This prevented `T of mixed~S` from being created, which couldn't match
  the plain `T` in invariant generic argument checks
- New regression test in tests/PHPStan/Rules/TooWideTypehints/data/bug-11430.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant