diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 79c83c7b0f..70803e4379 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -229,7 +229,13 @@ public function toArray(): Type public function toArrayKey(): Type { - return new IntegerType(); + return new UnionType([ + new IntegerType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + ]); } public function isNull(): TrinaryLogic diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 1b770d6889..16e999bcb3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -609,11 +609,17 @@ public function findTypeAndMethodNames(): array public function hasOffsetValueType(Type $offsetType): TrinaryLogic { - $offsetType = $offsetType->toArrayKey(); + $offsetArrayKeyType = $offsetType->toArrayKey(); + + return $this->recursiveHasOffsetValueType($offsetArrayKeyType); + } + + private function recursiveHasOffsetValueType(Type $offsetType): TrinaryLogic + { if ($offsetType instanceof UnionType) { $results = []; foreach ($offsetType->getTypes() as $innerType) { - $results[] = $this->hasOffsetValueType($innerType); + $results[] = $this->recursiveHasOffsetValueType($innerType); } return TrinaryLogic::extremeIdentity(...$results); @@ -623,7 +629,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic if ($finiteTypes !== []) { $results = []; foreach ($finiteTypes as $innerType) { - $results[] = $this->hasOffsetValueType($innerType); + $results[] = $this->recursiveHasOffsetValueType($innerType); } return TrinaryLogic::extremeIdentity(...$results); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 519649a384..f683ad9cbd 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1029,7 +1029,10 @@ public function toArray(): Type public function toArrayKey(): Type { if ($this->isNumericString()->yes()) { - return new IntegerType(); + return TypeCombinator::union( + new IntegerType(), + $this, + ); } if ($this->isString()->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-3133.php b/tests/PHPStan/Analyser/nsrt/bug-3133.php index 98eb5841f0..c6516dfe70 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3133.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3133.php @@ -52,7 +52,7 @@ public function doLorem( { $a = []; $a[$numericString] = 'foo'; - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-8592.php b/tests/PHPStan/Analyser/nsrt/bug-8592.php new file mode 100644 index 0000000000..e876597853 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8592.php @@ -0,0 +1,15 @@ + $foo + */ +function foo(array $foo): void +{ + foreach ($foo as $key => $value) { + assertType('int|numeric-string', $key); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php index 8ffb1ddb89..a926f11293 100644 --- a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php +++ b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php @@ -296,7 +296,7 @@ class Bug4671 */ public function doFoo(int $intput, array $strings): void { - assertType('false', isset($strings[(string) $intput])); + assertType('bool', isset($strings[(string) $intput])); } } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index aa4d8cd83b..f079710e49 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -898,6 +898,11 @@ public function testBug2634(): void $this->analyse([__DIR__ . '/data/bug-2634.php'], []); } + public function testBug11390(): void + { + $this->analyse([__DIR__ . '/data/bug-11390.php'], []); + } + public function testInternalClassesWithOverloadedOffsetAccess(): void { $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access.php'], []); diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11390.php b/tests/PHPStan/Rules/Arrays/data/bug-11390.php new file mode 100644 index 0000000000..de601158ba --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11390.php @@ -0,0 +1,29 @@ + $tags + * @param numeric-string $tagId + */ +function printTagName(array $tags, string $tagId): void +{ + // Adding the second `*` to either of the following lines makes the error disappear + + $tagsById = array_combine(array_column($tags, 'id'), $tags); + if (false !== $tagsById) { + echo $tagsById[$tagId]['tagName'] . PHP_EOL; + } +} + +printTagName( + [ + ['id' => '123', 'tagName' => 'abc'], + ['id' => '4.5', 'tagName' => 'def'], + ['id' => '6e78', 'tagName' => 'ghi'] + ], + '4.5' +); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index a798142941..548b240e97 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1069,6 +1069,16 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug4163(): void + { + $this->analyse([__DIR__ . '/data/bug-4163.php'], [ + [ + 'Method Bug4163\HelloWorld::lall() should return array but returns array.', + 28, + ], + ]); + } + public function testBug11663(): void { $this->analyse([__DIR__ . '/data/bug-11663.php'], []); diff --git a/tests/PHPStan/Rules/Methods/data/bug-4163.php b/tests/PHPStan/Rules/Methods/data/bug-4163.php new file mode 100644 index 0000000000..7b8de0491c --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4163.php @@ -0,0 +1,30 @@ + + */ + function lall() { + $helloCollection = [new HelloWorld(), new HelloWorld()]; + $result = []; + + foreach ($helloCollection as $hello) { + $key = (string)$hello->lall; + + if (!isset($result[$key])) { + $lall = 'do_something_here'; + $result[$key] = $lall; + } + } + + return $result; + } +} diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 16d92ed767..2f15908cfa 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -233,10 +233,7 @@ public function testBug4671(): void { $this->treatPhpDocTypesAsCertain = true; $this->strictUnnecessaryNullsafePropertyFetch = false; - $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ - 'Offset numeric-string on array in isset() does not exist.', - 13, - ]]); + $this->analyse([__DIR__ . '/data/bug-4671.php'], []); } public function testVariableCertaintyInIsset(): void