Skip to content

Fix phpstan/phpstan#11565: False positive: Conditional return type takes the wrong branch#5362

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

Fix phpstan/phpstan#11565: False positive: Conditional return type takes the wrong branch#5362
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-b6qlhzl

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a variable is reassigned from a function with a conditional return type (e.g., $items = iteratorToList($items)), PHPStan incorrectly resolved the conditional return type to never instead of list<T>. This happened because the TypeSpecifier used the post-assignment scope to evaluate the conditional, causing the variable's new type (the function's return type) to be used as the input, creating a circular evaluation.

Changes

  • src/Analyser/TypeSpecifier.php: In the null-context handling of Assign expressions, remove any specifiedTypes that target the assigned variable. This prevents the post-assignment type from incorrectly influencing the conditional return type evaluation.
  • src/Analyser/SpecifiedTypes.php: Added removeExpr(string $exprString) method to allow filtering out entries from both sureTypes and sureNotTypes by expression key.
  • tests/PHPStan/Analyser/nsrt/bug-11565.php: Regression test covering both the buggy case (same variable) and the working case (different variable).

Root cause

After processing $items = iteratorToList($items), the scope has $items typed as list<string>. The statement processing in NodeScopeResolver then calls specifyTypesInCondition with this post-assignment scope to extract additional type narrowing. For an Assign in null context, the TypeSpecifier recurses into the RHS function call. ParametersAcceptorSelector::selectFromArgs evaluates the argument $items using the post-assignment scope, getting list<string> instead of the original iterable<string, string>. Since list<string> IS a list, the conditional ($iterable is list ? never : list<T>) resolves to never, and the TypeSpecifier creates a "not list" narrowing for $items. When applied to list<string>, this produces *NEVER*.

The fix removes specifiedTypes targeting the assigned variable in null context, since after reassignment the variable no longer represents the function's input.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11565.php which tests that $items = iteratorToList($items) correctly resolves to list<string> when $items starts as iterable<string, string>. Also verifies the existing working case with different variables.

Fixes phpstan/phpstan#11565

… when variable is reassigned

- When processing `$items = iteratorToList($items)` as a statement, the TypeSpecifier
  evaluated the conditional return type using the post-assignment scope where `$items`
  was already `list<string>`, causing the condition `$iterable is list` to be true
  and the return type to resolve to `never`
- Added `removeExpr` method to SpecifiedTypes to filter out entries by expression key
- In TypeSpecifier, when processing an Assign in null context, remove specifiedTypes
  that target the assigned variable to prevent the post-assignment type from being
  incorrectly narrowed by the conditional return type evaluation
- New regression test in tests/PHPStan/Analyser/nsrt/bug-11565.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