Skip to content

Fix phpstan/phpstan#11488: Wrongly assumed undefined variable since 1.11.10#5149

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

Fix phpstan/phpstan#11488: Wrongly assumed undefined variable since 1.11.10#5149
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vqdbiuj

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Fixes false positive "Variable $operator on left side of ?? is never defined" when using count($row) !== 1 to narrow a union of constant array types like array{mixed}|array{mixed, string|null, mixed}. After the count check, PHPStan incorrectly narrowed the type to *NEVER* instead of array{mixed, string|null, mixed}.

Changes

  • Modified specifyTypesForCountFuncCall in src/Analyser/TypeSpecifier.php to handle the falsey context differently: instead of collecting matching types and removing them via create(..., falsey), it now collects non-matching types directly and uses TypeSpecifierContext::createTrue() to set them
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-11488.php

Root cause

In the falsey context (e.g., count($arr) !== 1), specifyTypesForCountFuncCall built the matching types (those with the target count) and used create(..., falsey) to remove them from the variable's type. This ultimately called TypeCombinator::remove(), which checks $typeToRemove->isSuperTypeOf($fromType). Since array{mixed} is structurally a supertype of array{mixed, string|null, mixed} (it only checks that required keys exist with compatible values, not that there are no extra keys), removing array{mixed} from the union also removed array{mixed, string|null, mixed}, resulting in *NEVER*.

The fix changes the falsey context to build the complement directly: it keeps array types whose count definitely doesn't match the target and skips those that definitely match, then uses truthy context to set the result.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11488.php which verifies that after count($row) !== 1 where $row is array{mixed}|array{mixed, string|null, mixed}, the type is correctly narrowed to array{mixed, string|null, mixed} and destructured variables like $operator have the expected string|null type.

Fixes phpstan/phpstan#11488

- Fixed specifyTypesForCountFuncCall falsey context to build complement types directly
  instead of removing matching types, avoiding structural subtyping issues with
  TypeCombinator::remove
- Added regression test in tests/PHPStan/Analyser/nsrt/bug-11488.php
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-vqdbiuj branch March 8, 2026 15:04
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.

2 participants