diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6a40c74454..fb8b5985e7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -821,7 +821,7 @@ public function specifyTypesInCondition( ); } else { $varType = $scope->getType($var->var); - $narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType); + $narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType, $dimType); if ($narrowedKey !== null) { $types = $types->unionWith( $this->create( diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index eb7de446ad..2b15a4eb65 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -32,7 +32,8 @@ public static function getType(): Type ]); } - public static function narrowOffsetKeyType(Type $varType): ?Type { + public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type + { if (!$varType->isArray()->yes() || $varType->isIterableAtLeastOnce()->no()) { return null; } @@ -66,17 +67,20 @@ public static function narrowOffsetKeyType(Type $varType): ?Type { if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) { $narrowedKey = TypeCombinator::union($narrowedKey, new FloatType()); } - } else { - $narrowedKey = new MixedType( - false, - new UnionType([ - new ArrayType(new MixedType(), new MixedType()), - new ObjectWithoutClassType(), - new ResourceType(), - ]), - ); + + return $narrowedKey; + } elseif ($varIterableKeyType->isInteger()->yes() && $keyType->isString()->yes()) { + return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType); } - return $narrowedKey; + return new MixedType( + false, + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new ObjectWithoutClassType(), + new ResourceType(), + ]), + ); } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index 2394e8a175..e637669483 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -35,9 +35,10 @@ public function parse(string $glue): string } /** - * @param array $arr + * @param array $intKeyedArr + * @param array $stringKeyedArr */ -function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void { +function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyedArr, array $stringKeyedArr): void { if (isset($generalArr[$mixed])) { assertType('mixed~(array|object|resource)', $mixed); } else { @@ -59,21 +60,42 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): vo } assertType('string', $s); - if (isset($arr[$mixed])) { + if (isset($intKeyedArr[$mixed])) { assertType('mixed~(array|object|resource)', $mixed); } else { assertType('mixed', $mixed); } assertType('mixed', $mixed); - if (isset($arr[$i])) { + if (isset($intKeyedArr[$i])) { assertType('int', $i); } else { assertType('int', $i); } assertType('int', $i); - if (isset($arr[$s])) { + if (isset($intKeyedArr[$s])) { + assertType("numeric-string", $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (isset($stringKeyedArr[$mixed])) { + assertType('mixed~(array|object|resource)', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($stringKeyedArr[$i])) { + assertType('int', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + + if (isset($stringKeyedArr[$s])) { assertType('string', $s); } else { assertType('string', $s);