From c93e80c946a05178be21b2756fb896d6e4e5ded4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 15 Aug 2024 13:34:37 +0200 Subject: [PATCH 1/5] Fix NumericString to array key --- .../Accessory/AccessoryNumericStringType.php | 8 ++++- src/Type/Constant/ConstantArrayType.php | 12 ++++++-- src/Type/IntersectionType.php | 5 +++- tests/PHPStan/Analyser/nsrt/bug-3133.php | 2 +- .../nsrt/isset-coalesce-empty-type.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 12 +++++++- tests/PHPStan/Rules/Methods/data/bug-4163.php | 30 +++++++++++++++++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 5 +--- 8 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4163.php 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..00e402131a 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 new UnionType([ + 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/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/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index a798142941..40ca588c6a 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1066,7 +1066,17 @@ public function testBug11337(): void public function testBug10715(): void { - $this->analyse([__DIR__ . '/data/bug-10715.php'], []); + $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 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 From b0bfea14c3ccaf709b089fc2fa839f7eb35ef8a7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 15 Aug 2024 15:47:26 +0200 Subject: [PATCH 2/5] Add non-regression test --- tests/PHPStan/Analyser/nsrt/bug-8592.php | 15 +++++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 ++++ tests/PHPStan/Rules/Arrays/data/bug-11390.php | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8592.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11390.php 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/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..41277ef814 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11390.php @@ -0,0 +1,27 @@ + $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); + echo $tagsById[$tagId]['tagName'] . PHP_EOL; +} + +printTagName( + [ + ['id' => '123', 'tagName' => 'abc'], + ['id' => '4.5', 'tagName' => 'def'], + ['id' => '6e78', 'tagName' => 'ghi'] + ], + '4.5' +); From 1b58c578a6789cdac2dd1f3734a16e5533589ba4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 15 Aug 2024 16:03:18 +0200 Subject: [PATCH 3/5] Fix old php versions --- tests/PHPStan/Rules/Arrays/data/bug-11390.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11390.php b/tests/PHPStan/Rules/Arrays/data/bug-11390.php index 41277ef814..de601158ba 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-11390.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-11390.php @@ -14,7 +14,9 @@ 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); - echo $tagsById[$tagId]['tagName'] . PHP_EOL; + if (false !== $tagsById) { + echo $tagsById[$tagId]['tagName'] . PHP_EOL; + } } printTagName( From 27a9b384d6bdc70921586e65e9a9aceaccc4914e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 19 Sep 2024 17:40:45 +0200 Subject: [PATCH 4/5] Fix rebase --- tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 40ca588c6a..548b240e97 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1066,7 +1066,7 @@ public function testBug11337(): void public function testBug10715(): void { - $this->analyse([__DIR__.'/data/bug-10715.php'], []); + $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } public function testBug4163(): void From b24c50fbc55538dbaa855b1401de6b0ac454c1ef Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 5 May 2025 17:51:28 +0200 Subject: [PATCH 5/5] Review --- src/Type/IntersectionType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 00e402131a..f683ad9cbd 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1029,10 +1029,10 @@ public function toArray(): Type public function toArrayKey(): Type { if ($this->isNumericString()->yes()) { - return new UnionType([ + return TypeCombinator::union( new IntegerType(), $this, - ]); + ); } if ($this->isString()->yes()) {