Skip to content

Fix #11705: Value of a variable is forgotten in case where it ought to be known with certainty#5075

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

Fix #11705: Value of a variable is forgotten in case where it ought to be known with certainty#5075
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-st8jdy8

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When accessing an array with a variable key like $theInput[$tag] after array_key_exists($tag, $theInput), and the variable $tag is later narrowed (e.g., in a switch/case), PHPStan would still use the original broad value type instead of the narrowed one. This caused false positives like "Parameter #1 (array|string) of echo cannot be converted to string" even though the narrowed key type resolves to a specific string value.

Changes

  • Modified src/Analyser/MutatingScope.php in resolveType(): when an ArrayDimFetch node has an expression type holder and its dimension is a Variable, recompute the offset value type using the current (possibly narrowed) variable type. If the recomputed type is more specific (the expression type is a supertype of it), return the recomputed type instead of the stale expression type holder.
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-11705.php

Root cause

The ArrayKeyExistsFunctionTypeSpecifyingExtension creates an expression type holder for $arr[$key] with the array's full iterable value type when the key is non-constant. This expression type holder persists in the scope and takes priority over fresh type computation in resolveType(). When $key is later narrowed to a more specific type (e.g., $tag narrowed from string to 'name' in a case 'name': branch), the stale expression type holder still returns the broad union type, preventing the type system from using the narrowed key to resolve a more specific value type.

The fix detects this situation by checking whether the freshly computed getOffsetValueType() (using the current narrowed dim type) is more specific than the stored expression type. If so, the fresh computation's result is used.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11705.php which reproduces the original issue: a typed array array{'name': string, 'owners': array<int, string>} accessed with a variable key $tag inside a switch($tag) { case 'name': } block. The test verifies that $theInput[$tag] correctly resolves to string (not array<int, string>|string) both before and after an always-true if ($tag === 'name') check.

Fixes phpstan/phpstan#11705

- When an expression type holder exists for $arr[$var] (e.g. from array_key_exists),
  and $var is later narrowed (e.g. in a switch/case), recompute the offset value type
  using the narrowed variable type and use it if it's more specific
- New regression test in tests/PHPStan/Analyser/nsrt/bug-11705.php
- The root cause was that array_key_exists creates an expression type holder for
  $arr[$tag] based on the broad value type, which overrides fresh computation even
  after $tag is narrowed to a specific constant in a switch case

Closes phpstan/phpstan#11705
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