Skip to content

Commit 55773e6

Browse files
phpstan-botclaude
andcommitted
Fix phpstan/phpstan#11705: move fix from MutatingScope to ArrayKeyExistsFunctionTypeSpecifyingExtension
The previous fix in MutatingScope::resolveType() was a workaround that recomputed ArrayDimFetch types when the dim was narrowed. The root cause is in ArrayKeyExistsFunctionTypeSpecifyingExtension which stores the full getIterableValueType() for $arr[$key] when the key is non-constant. This stored type becomes stale when the key is later narrowed (e.g., in a switch/case), blocking the correct type from being computed. The fix: for constant arrays (shaped arrays) without optional keys, skip storing the dim fetch value type. The type will instead be computed fresh from the array type and the narrowed key type, giving the correct specific type. For arrays with optional keys, the stored type is still needed to prevent false "offset might not exist" errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2716e22 commit 55773e6

2 files changed

Lines changed: 21 additions & 18 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,24 +1043,7 @@ private function resolveType(string $exprString, Expr $node): Type
10431043
&& !$node instanceof Expr\ArrowFunction
10441044
&& $this->hasExpressionType($node)->yes()
10451045
) {
1046-
$type = $this->expressionTypes[$exprString]->getType();
1047-
1048-
if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) {
1049-
$dimType = $this->getType($node->dim);
1050-
if ($dimType->isConstantScalarValue()->yes()) {
1051-
$arrayType = $this->getType($node->var);
1052-
$offsetValueType = $arrayType->getOffsetValueType($dimType);
1053-
if (
1054-
!$offsetValueType instanceof ErrorType
1055-
&& $type->isSuperTypeOf($offsetValueType)->yes()
1056-
&& !$offsetValueType->isSuperTypeOf($type)->yes()
1057-
) {
1058-
$type = $offsetValueType;
1059-
}
1060-
}
1061-
}
1062-
1063-
return $type;
1046+
return $this->expressionTypes[$exprString]->getType();
10641047
}
10651048

10661049
/** @var ExprHandler<Expr> $exprHandler */

src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\Type\FunctionTypeSpecifyingExtension;
2424
use PHPStan\Type\IntersectionType;
2525
use PHPStan\Type\MixedType;
26+
use PHPStan\Type\Type;
2627
use PHPStan\Type\TypeCombinator;
2728
use function count;
2829
use function in_array;
@@ -54,6 +55,21 @@ public function isFunctionSupported(
5455
&& !$context->null();
5556
}
5657

58+
private function shouldStoreArrayDimFetchType(Type $arrayType): bool
59+
{
60+
if (!$arrayType->isConstantArray()->yes()) {
61+
return true;
62+
}
63+
64+
foreach ($arrayType->getConstantArrays() as $constantArray) {
65+
if (count($constantArray->getOptionalKeys()) > 0) {
66+
return true;
67+
}
68+
}
69+
70+
return false;
71+
}
72+
5773
public function specifyTypes(
5874
FunctionReflection $functionReflection,
5975
FuncCall $node,
@@ -109,6 +125,10 @@ public function specifyTypes(
109125
$key,
110126
);
111127

128+
if (!$this->shouldStoreArrayDimFetchType($arrayType)) {
129+
return $specifiedTypes->setRootExpr(new Identical($arrayDimFetch, new ConstFetch(new Name('__PHPSTAN_FAUX_CONSTANT'))));
130+
}
131+
112132
return $specifiedTypes->unionWith($this->typeSpecifier->create(
113133
$arrayDimFetch,
114134
$arrayType->getIterableValueType(),

0 commit comments

Comments
 (0)