Skip to content

Commit a39ffdc

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
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 phpstan/phpstan#11705
1 parent ba4fe14 commit a39ffdc

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/Analyser/MutatingScope.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,20 @@ private function resolveType(string $exprString, Expr $node): Type
979979
&& !$node instanceof Expr\ArrowFunction
980980
&& $this->hasExpressionType($node)->yes()
981981
) {
982-
return $this->expressionTypes[$exprString]->getType();
982+
$expressionType = $this->expressionTypes[$exprString]->getType();
983+
if (
984+
$node instanceof Expr\ArrayDimFetch
985+
&& $node->dim instanceof Variable
986+
&& $this->getType($node->var)->isArray()->yes()
987+
) {
988+
$dimType = $this->getType($node->dim);
989+
$arrayType = $this->getType($node->var);
990+
$computedType = $arrayType->getOffsetValueType($dimType);
991+
if ($expressionType->isSuperTypeOf($computedType)->yes()) {
992+
return $computedType;
993+
}
994+
}
995+
return $expressionType;
983996
}
984997

985998
if ($node instanceof AlwaysRememberedExpr) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11705;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param array<string,string|array<int,string>> $theInput
9+
* @phpstan-param array{'name':string,'owners':array<int,string>} $theInput
10+
* @param array<int,string> $theTags
11+
*/
12+
function Example(array $theInput, array $theTags): void
13+
{
14+
foreach ($theTags as $tag) {
15+
if (!array_key_exists($tag, $theInput)) {
16+
continue;
17+
}
18+
switch ($tag) {
19+
case 'name':
20+
assertType("'name'", $tag);
21+
assertType('string', $theInput[$tag]);
22+
echo $theInput['name'];
23+
if ($tag === 'name') {
24+
echo "Of course it is...";
25+
}
26+
// After the always-true if, $tag should still be 'name'
27+
assertType("'name'", $tag);
28+
assertType('string', $theInput[$tag]);
29+
echo $theInput[$tag];
30+
break;
31+
default:
32+
// fall out
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)