From a39ffdc11208e0c06ddf52d6346e7077aa199b7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:37:54 +0000 Subject: [PATCH] Fix array dim fetch type not reflecting narrowed variable key - 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 https://github.com/phpstan/phpstan/issues/11705 --- src/Analyser/MutatingScope.php | 15 +++++++++- tests/PHPStan/Analyser/nsrt/bug-11705.php | 35 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11705.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4036f5ca72..103512448a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -979,7 +979,20 @@ private function resolveType(string $exprString, Expr $node): Type && !$node instanceof Expr\ArrowFunction && $this->hasExpressionType($node)->yes() ) { - return $this->expressionTypes[$exprString]->getType(); + $expressionType = $this->expressionTypes[$exprString]->getType(); + if ( + $node instanceof Expr\ArrayDimFetch + && $node->dim instanceof Variable + && $this->getType($node->var)->isArray()->yes() + ) { + $dimType = $this->getType($node->dim); + $arrayType = $this->getType($node->var); + $computedType = $arrayType->getOffsetValueType($dimType); + if ($expressionType->isSuperTypeOf($computedType)->yes()) { + return $computedType; + } + } + return $expressionType; } if ($node instanceof AlwaysRememberedExpr) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-11705.php b/tests/PHPStan/Analyser/nsrt/bug-11705.php new file mode 100644 index 0000000000..6f1dc29c44 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11705.php @@ -0,0 +1,35 @@ +> $theInput + * @phpstan-param array{'name':string,'owners':array} $theInput + * @param array $theTags + */ +function Example(array $theInput, array $theTags): void +{ + foreach ($theTags as $tag) { + if (!array_key_exists($tag, $theInput)) { + continue; + } + switch ($tag) { + case 'name': + assertType("'name'", $tag); + assertType('string', $theInput[$tag]); + echo $theInput['name']; + if ($tag === 'name') { + echo "Of course it is..."; + } + // After the always-true if, $tag should still be 'name' + assertType("'name'", $tag); + assertType('string', $theInput[$tag]); + echo $theInput[$tag]; + break; + default: + // fall out + } + } +}