From e7ef43dd83ab3fbec59699ebbeb908dd0a739b94 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 21 Dec 2022 09:57:54 +0100 Subject: [PATCH 01/11] Fix constant-string handling in union-types --- src/Type/UnionType.php | 21 ++++++++++++++++++- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-8568.php | 18 ++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-8568.php diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 414de9b311..2e3fe45e29 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -17,6 +17,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; @@ -77,6 +78,24 @@ public function getTypes(): array return $this->types; } + /** + * @param class-string $typeClass + * @return list + */ + private function getTypesOfClass(string $typeClass): array + { + $matchingTypes = []; + foreach ($this->getTypes() as $innerType) { + if (!$innerType instanceof $typeClass) { + return []; + } + + $matchingTypes[] = $innerType; + } + + return $matchingTypes; + } + public function isNormalized(): bool { return $this->normalized; @@ -117,7 +136,7 @@ public function getConstantArrays(): array public function getConstantStrings(): array { - return UnionTypeHelper::getConstantStrings($this->getTypes()); + return UnionTypeHelper::getConstantStrings($this->getTypesOfClass(ConstantStringType::class)); } public function accepts(Type $type, bool $strictTypes): TrinaryLogic diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9c7243a005..eb53bed1ef 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1154,6 +1154,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/pathinfo-php8.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/pathinfo.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8568.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-8568.php b/tests/PHPStan/Analyser/data/bug-8568.php new file mode 100644 index 0000000000..10a3acca43 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-8568.php @@ -0,0 +1,18 @@ +get()); + } + + public function get(): ?int + { + return rand() ? 5 : null; + } +} From 4c8ddf9b4cf937aa011d910d8f02c5b81a7a21f5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 21 Dec 2022 10:09:11 +0100 Subject: [PATCH 02/11] fix php <=7.3 --- src/Type/UnionTypeHelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index 7f8936899e..e0fed341f1 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -70,6 +70,10 @@ public static function getConstantStrings(array $types): array $strings[] = $type->getConstantStrings(); } + if ($strings === []) { + return []; + } + return array_merge(...$strings); } From fa19cace170e59c90dbe7efdaa957147b1c9565c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 21 Dec 2022 11:05:04 +0100 Subject: [PATCH 03/11] added regression test --- .../UnreachableIfBranchesRuleTest.php | 6 ++++++ .../PHPStan/Rules/Comparison/data/bug-8562.php | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8562.php diff --git a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php index 431a37cbe0..986af00a85 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php @@ -122,4 +122,10 @@ public function testBug8076(): void $this->analyse([__DIR__ . '/data/bug-8076.php'], []); } + public function testBug8562(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8562.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8562.php b/tests/PHPStan/Rules/Comparison/data/bug-8562.php new file mode 100644 index 0000000000..9deeaa6a0d --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8562.php @@ -0,0 +1,18 @@ + $a + */ +function a(array $a): void { + $l = (string) array_key_last($a); + $s = substr($l, 0, 2); + if ($s === '') { + ; + } else { + var_dump($s); + } +} + + From fdfde4811d7d6da0b43068b9686ce2c414f3598c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 21 Dec 2022 14:43:19 +0100 Subject: [PATCH 04/11] remove UnionTypeHelper usages --- src/Type/IntersectionType.php | 29 +++++++++-- src/Type/UnionType.php | 42 ++++++++++++++-- src/Type/UnionTypeHelper.php | 62 ------------------------ tests/PHPStan/Analyser/data/bug-8568.php | 8 +++ 4 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 913e5a455e..f2fb125687 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -27,6 +27,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use function array_map; +use function array_merge; use function count; use function implode; use function in_array; @@ -98,22 +99,42 @@ public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap */ public function getReferencedClasses(): array { - return UnionTypeHelper::getReferencedClasses($this->types); + $classes = []; + foreach ($this->types as $type) { + $classes[] = $type->getReferencedClasses(); + } + + return array_merge(...$classes); } public function getArrays(): array { - return UnionTypeHelper::getArrays($this->getTypes()); + $arrays = []; + foreach ($this->types as $type) { + $arrays[] = $type->getArrays(); + } + + return array_merge(...$arrays); } public function getConstantArrays(): array { - return UnionTypeHelper::getConstantArrays($this->getTypes()); + $constantArrays = []; + foreach ($this->types as $type) { + $constantArrays[] = $type->getConstantArrays(); + } + + return array_merge(...$constantArrays); } public function getConstantStrings(): array { - return UnionTypeHelper::getConstantStrings($this->getTypes()); + $strings = []; + foreach ($this->types as $type) { + $strings[] = $type->getConstantStrings(); + } + + return array_merge(...$strings); } public function accepts(Type $otherType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 2e3fe45e29..a1410282bd 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -16,6 +16,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; @@ -26,6 +27,7 @@ use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use function array_map; +use function array_merge; use function count; use function implode; use function sprintf; @@ -121,22 +123,54 @@ private function getSortedTypes(): array */ public function getReferencedClasses(): array { - return UnionTypeHelper::getReferencedClasses($this->getTypes()); + $classes = []; + foreach ($this->types as $type) { + $classes[] = $type->getReferencedClasses(); + } + + return array_merge(...$classes); } public function getArrays(): array { - return UnionTypeHelper::getArrays($this->getTypes()); + $arrays = []; + foreach ($this->types as $type) { + $arrays[] = $type->getArrays(); + } + + if ($arrays === []) { + return []; + } + + return array_merge(...$arrays); } public function getConstantArrays(): array { - return UnionTypeHelper::getConstantArrays($this->getTypes()); + $constantArrays = []; + foreach ($this->getTypesOfClass(ConstantArrayType::class) as $type) { + $constantArrays[] = $type->getConstantArrays(); + } + + if ($constantArrays === []) { + return []; + } + + return array_merge(...$constantArrays); } public function getConstantStrings(): array { - return UnionTypeHelper::getConstantStrings($this->getTypesOfClass(ConstantStringType::class)); + $strings = []; + foreach ($this->getTypesOfClass(ConstantStringType::class) as $type) { + $strings[] = $type->getConstantStrings(); + } + + if ($strings === []) { + return []; + } + + return array_merge(...$strings); } public function accepts(Type $type, bool $strictTypes): TrinaryLogic diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index e0fed341f1..db153a6a01 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -3,12 +3,10 @@ namespace PHPStan\Type; use PHPStan\Type\Accessory\AccessoryType; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use function array_merge; use function count; use function strcasecmp; use function usort; @@ -17,66 +15,6 @@ class UnionTypeHelper { - /** - * @param Type[] $types - * @return string[] - */ - public static function getReferencedClasses(array $types): array - { - $referencedClasses = []; - foreach ($types as $type) { - $referencedClasses[] = $type->getReferencedClasses(); - } - - return array_merge(...$referencedClasses); - } - - /** - * @param Type[] $types - * @return list - */ - public static function getArrays(array $types): array - { - $arrays = []; - foreach ($types as $type) { - $arrays[] = $type->getArrays(); - } - - return array_merge(...$arrays); - } - - /** - * @param Type[] $types - * @return list - */ - public static function getConstantArrays(array $types): array - { - $constantArrays = []; - foreach ($types as $type) { - $constantArrays[] = $type->getConstantArrays(); - } - - return array_merge(...$constantArrays); - } - - /** - * @param Type[] $types - * @return list - */ - public static function getConstantStrings(array $types): array - { - $strings = []; - foreach ($types as $type) { - $strings[] = $type->getConstantStrings(); - } - - if ($strings === []) { - return []; - } - - return array_merge(...$strings); - } - /** * @param Type[] $types * @return Type[] diff --git a/tests/PHPStan/Analyser/data/bug-8568.php b/tests/PHPStan/Analyser/data/bug-8568.php index 10a3acca43..71db7a6c98 100644 --- a/tests/PHPStan/Analyser/data/bug-8568.php +++ b/tests/PHPStan/Analyser/data/bug-8568.php @@ -15,4 +15,12 @@ public function get(): ?int { return rand() ? 5 : null; } + + /** + * @param numeric-string $numericS + */ + public function intersections($numericS): void { + assertType('non-falsy-string', 'a'. $numericS); + assertType('numeric-string', (string) $numericS); + } } From d1d9b44c88c9f92f839a3be107281271d952bfa8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 22 Dec 2022 11:01:35 +0100 Subject: [PATCH 05/11] simplify --- src/Type/IntersectionType.php | 28 +++--- src/Type/UnionType.php | 69 ++++++-------- test.php | 23 +++++ tests/PHPStan/Type/UnionTypeTest.php | 130 +++++++++++++++++++++++++++ 4 files changed, 197 insertions(+), 53 deletions(-) create mode 100644 test.php diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index f2fb125687..1aeaf6eb51 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -27,7 +27,6 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use function array_map; -use function array_merge; use function count; use function implode; use function in_array; @@ -94,47 +93,52 @@ public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap return $types; } - /** - * @return string[] - */ public function getReferencedClasses(): array { $classes = []; foreach ($this->types as $type) { - $classes[] = $type->getReferencedClasses(); + foreach ($type->getReferencedClasses() as $className) { + $classes[] = $className; + } } - return array_merge(...$classes); + return $classes; } public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { - $arrays[] = $type->getArrays(); + foreach ($type->getArrays() as $array) { + $arrays[] = $array; + } } - return array_merge(...$arrays); + return $arrays; } public function getConstantArrays(): array { $constantArrays = []; foreach ($this->types as $type) { - $constantArrays[] = $type->getConstantArrays(); + foreach ($type->getConstantArrays() as $constantArray) { + $constantArrays[] = $constantArray; + } } - return array_merge(...$constantArrays); + return $constantArrays; } public function getConstantStrings(): array { $strings = []; foreach ($this->types as $type) { - $strings[] = $type->getConstantStrings(); + foreach ($type->getConstantStrings() as $string) { + $strings[] = $string; + } } - return array_merge(...$strings); + return $strings; } public function accepts(Type $otherType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index a1410282bd..9a0477237c 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -16,9 +16,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; @@ -27,7 +25,6 @@ use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use function array_map; -use function array_merge; use function count; use function implode; use function sprintf; @@ -80,24 +77,6 @@ public function getTypes(): array return $this->types; } - /** - * @param class-string $typeClass - * @return list - */ - private function getTypesOfClass(string $typeClass): array - { - $matchingTypes = []; - foreach ($this->getTypes() as $innerType) { - if (!$innerType instanceof $typeClass) { - return []; - } - - $matchingTypes[] = $innerType; - } - - return $matchingTypes; - } - public function isNormalized(): bool { return $this->normalized; @@ -125,52 +104,60 @@ public function getReferencedClasses(): array { $classes = []; foreach ($this->types as $type) { - $classes[] = $type->getReferencedClasses(); + foreach ($type->getReferencedClasses() as $className) { + $classes[] = $className; + } } - return array_merge(...$classes); + return $classes; } public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { - $arrays[] = $type->getArrays(); - } - - if ($arrays === []) { - return []; + foreach ($type->getArrays() as $array) { + $arrays[] = $array; + } } - return array_merge(...$arrays); + return $arrays; } public function getConstantArrays(): array { $constantArrays = []; - foreach ($this->getTypesOfClass(ConstantArrayType::class) as $type) { - $constantArrays[] = $type->getConstantArrays(); - } + foreach ($this->types as $type) { + $typeAsConstantArrays = $type->getConstantArrays(); - if ($constantArrays === []) { - return []; + if ($typeAsConstantArrays === []) { + return []; + } + + foreach ($typeAsConstantArrays as $constantArray) { + $constantArrays[] = $constantArray; + } } - return array_merge(...$constantArrays); + return $constantArrays; } public function getConstantStrings(): array { $strings = []; - foreach ($this->getTypesOfClass(ConstantStringType::class) as $type) { - $strings[] = $type->getConstantStrings(); - } + foreach ($this->types as $type) { + $constantStrings = $type->getConstantStrings(); + + if ($constantStrings === []) { + return []; + } - if ($strings === []) { - return []; + foreach ($constantStrings as $string) { + $strings[] = $string; + } } - return array_merge(...$strings); + return $strings; } public function accepts(Type $type, bool $strictTypes): TrinaryLogic diff --git a/test.php b/test.php new file mode 100644 index 0000000000..3e51f3be13 --- /dev/null +++ b/test.php @@ -0,0 +1,23 @@ + $expectedDescriptions + */ + public function testGetConstantArrays( + array $types, + array $expectedDescriptions, + ): void + { + $unionType = TypeCombinator::union(...$types); + $constantArrays = $unionType->getConstantArrays(); + + $actualDescriptions = []; + foreach ($constantArrays as $constantArray) { + $actualDescriptions[] = $constantArray->describe(VerbosityLevel::precise()); + } + + $this->assertSame($expectedDescriptions, $actualDescriptions); + } + + public function dataGetConstantArrays(): iterable + { + yield from [ + [ + [ + TypeCombinator::intersect( + new ConstantArrayType( + [new ConstantIntegerType(1), new ConstantIntegerType(2)], + [new IntegerType(), new StringType()], + 2, + [0, 1], + ), + new NonEmptyArrayType(), + ), + new ConstantArrayType( + [new ConstantIntegerType(0), new ConstantIntegerType(1)], + [new ObjectType(Foo::class), new ObjectType(stdClass::class)], + 2, + ), + ], + [ + 'array{1?: int, 2?: string}', + 'array{RecursionCallable\Foo, stdClass}', + ], + ], + [ + [ + TypeCombinator::intersect( + new ConstantArrayType( + [new ConstantIntegerType(1), new ConstantIntegerType(2)], + [new IntegerType(), new StringType()], + 2, + [0, 1], + ), + ), + new IntegerType(), + ], + [], + ], + ]; + } + + /** + * @dataProvider dataGetConstantStrings + * @param list $expectedDescriptions + */ + public function testGetConstantStrings( + Type $unionType, + array $expectedDescriptions, + ): void + { + $constantStrings = $unionType->getConstantStrings(); + + $actualDescriptions = []; + foreach ($constantStrings as $constantString) { + $actualDescriptions[] = $constantString->describe(VerbosityLevel::precise()); + } + + $this->assertSame($expectedDescriptions, $actualDescriptions); + } + + public function dataGetConstantStrings(): iterable + { + yield from [ + [ + TypeCombinator::union( + new ConstantStringType('hello'), + new ConstantStringType('world'), + ), + [ + "'hello'", + "'world'", + ], + ], + [ + TypeCombinator::union( + new ConstantStringType(''), + TypeCombinator::intersect( + new StringType(), + new AccessoryNumericStringType(), + ), + ), + [], + ], + [ + new UnionType([ + new IntersectionType( + [ + new ConstantStringType('foo'), + new AccessoryLiteralStringType(), + ], + ), + new IntersectionType( + [ + new ConstantStringType('bar'), + new AccessoryLiteralStringType(), + ], + ), + ]), + [ + "'foo'", + "'bar'", + ], + ], + ]; + } + } From 3948597b166ce16bc8c5b005a7a95311ed31ab7b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 25 Dec 2022 18:43:44 +0100 Subject: [PATCH 06/11] Delete test.php --- test.php | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 test.php diff --git a/test.php b/test.php deleted file mode 100644 index 3e51f3be13..0000000000 --- a/test.php +++ /dev/null @@ -1,23 +0,0 @@ - Date: Mon, 26 Dec 2022 19:42:13 +0100 Subject: [PATCH 07/11] fix getArrays --- src/Type/Constant/ConstantArrayType.php | 5 ++ src/Type/IntersectionType.php | 4 ++ src/Type/UnionType.php | 4 ++ tests/PHPStan/Type/UnionTypeTest.php | 73 +++++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index ac70af5ae5..aebf87f426 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -145,6 +145,11 @@ public function getOptionalKeys(): array return $this->optionalKeys; } + public function getArrays(): array + { + return $this->getAllArrays(); + } + /** * @return self[] */ diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 1aeaf6eb51..d089303fb2 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -109,6 +109,10 @@ public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { + if (!$type instanceof ArrayType) { + continue; + } + foreach ($type->getArrays() as $array) { $arrays[] = $array; } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 9a0477237c..c9853c7943 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -116,6 +116,10 @@ public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { + if (!$type instanceof ArrayType) { + return []; + } + foreach ($type->getArrays() as $array) { $arrays[] = $array; } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index c18374b125..61d4768534 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -1410,4 +1410,77 @@ public function dataGetConstantStrings(): iterable ]; } + /** + * @dataProvider dataGetArrays + * @param list $expectedDescriptions + */ + public function testGetArrays( + Type $unionType, + array $expectedDescriptions, + ): void + { + $arrays = $unionType->getArrays(); + + $actualDescriptions = []; + foreach ($arrays as $arrayType) { + $actualDescriptions[] = $arrayType->describe(VerbosityLevel::precise()); + } + + $this->assertSame($expectedDescriptions, $actualDescriptions); + } + + public function dataGetArrays(): iterable + { + yield from [ + [ + TypeCombinator::union( + new ConstantStringType('hello'), + new ConstantStringType('world'), + ), + [], + ], + [ + TypeCombinator::union( + TypeCombinator::intersect( + new ConstantArrayType( + [new ConstantIntegerType(1), new ConstantIntegerType(2)], + [new IntegerType(), new StringType()], + 2, + [0, 1], + ), + new NonEmptyArrayType(), + ), + new ConstantArrayType( + [new ConstantIntegerType(0), new ConstantIntegerType(1)], + [new ObjectType(Foo::class), new ObjectType(stdClass::class)], + 2, + ), + ), + [ + 'array{1?: int, 2?: string}', + 'array{RecursionCallable\Foo, stdClass}', + ], + ], + [ + TypeCombinator::union( + new ArrayType(new IntegerType(), new StringType()), + new ConstantArrayType( + [new ConstantIntegerType(1), new ConstantIntegerType(2)], + [new IntegerType(), new StringType()], + 2, + [0, 1], + ), + new ConstantArrayType( + [new ConstantIntegerType(0), new ConstantIntegerType(1)], + [new ObjectType(Foo::class), new ObjectType(stdClass::class)], + 2, + ), + ), + [ + 'array', + ], + ], + ]; + } + } From c07a033e24f166a57b1deed4af42a33bbb0c5434 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 Dec 2022 13:34:03 +0100 Subject: [PATCH 08/11] nope --- src/Type/Constant/ConstantArrayType.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index aebf87f426..ac70af5ae5 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -145,11 +145,6 @@ public function getOptionalKeys(): array return $this->optionalKeys; } - public function getArrays(): array - { - return $this->getAllArrays(); - } - /** * @return self[] */ From 7d59e3766f62aaeb79db33aef07174a4566034bb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 Dec 2022 13:34:33 +0100 Subject: [PATCH 09/11] Another nope --- src/Type/IntersectionType.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index d089303fb2..1aeaf6eb51 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -109,10 +109,6 @@ public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { - if (!$type instanceof ArrayType) { - continue; - } - foreach ($type->getArrays() as $array) { $arrays[] = $array; } From 7d779e9748923969b1c0385976b111b074661ba0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 Dec 2022 13:39:16 +0100 Subject: [PATCH 10/11] fix --- phpstan-baseline.neon | 8 +++++++ src/Analyser/MutatingScope.php | 42 +++++++++++++++------------------- src/Type/UnionType.php | 5 ++-- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a6c7090e34..d33fabf595 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,6 +15,14 @@ parameters: count: 1 path: src/Analyser/LazyInternalScopeFactory.php + - + message: """ + #^Call to deprecated method getAnyArrays\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: + Use PHPStan\\\\Type\\\\Type\\:\\:getArrays\\(\\) instead\\.$# + """ + count: 2 + path: src/Analyser/MutatingScope.php + - message: """ #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 79178a0cb2..8dcdda47f8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4078,7 +4078,7 @@ public function processClosureScope( $prevVariableType = $prevScope->getVariableType($variableName); if (!$variableType->equals($prevVariableType)) { $variableType = TypeCombinator::union($variableType, $prevVariableType); - $variableType = self::generalizeType($variableType, $prevVariableType); + $variableType = self::generalizeType($variableType, $prevVariableType, 0); } } @@ -4200,7 +4200,7 @@ private function generalizeVariableTypeHolders( $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(), - self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType()), + self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0), $variableTypeHolder->getCertainty(), ); } @@ -4208,7 +4208,7 @@ private function generalizeVariableTypeHolders( return $variableTypeHolders; } - private static function generalizeType(Type $a, Type $b): Type + private static function generalizeType(Type $a, Type $b, int $depth): Type { if ($a->equals($b)) { return $a; @@ -4301,6 +4301,7 @@ private static function generalizeType(Type $a, Type $b): Type self::generalizeType( $constantArraysA->getOffsetValueType($keyType), $constantArraysB->getOffsetValueType($keyType), + $depth + 1, ), !$constantArraysA->hasOffsetValueType($keyType)->and($constantArraysB->hasOffsetValueType($keyType))->negate()->no(), ); @@ -4309,8 +4310,8 @@ private static function generalizeType(Type $a, Type $b): Type $resultTypes[] = $resultArrayBuilder->getArray(); } else { $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType())), - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType())), + TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)), + TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)), ); if ($constantArraysA->isIterableAtLeastOnce()->yes() && $constantArraysB->isIterableAtLeastOnce()->yes()) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); @@ -4334,16 +4335,14 @@ private static function generalizeType(Type $a, Type $b): Type $aValueType = $generalArraysA->getIterableValueType(); $bValueType = $generalArraysB->getIterableValueType(); - $aArrays = $aValueType->getArrays(); - $bArrays = $bValueType->getArrays(); if ( - count($aArrays) === 1 - && $aArrays[0]->isConstantArray()->no() - && count($bArrays) === 1 - && $bArrays[0]->isConstantArray()->no() + $aValueType->isArray()->yes() + && $aValueType->isConstantArray()->no() + && $bValueType->isArray()->yes() + && $bValueType->isConstantArray()->no() ) { - $aDepth = self::getArrayDepth($aArrays[0]); - $bDepth = self::getArrayDepth($bArrays[0]); + $aDepth = self::getArrayDepth($aValueType) + $depth; + $bDepth = self::getArrayDepth($bValueType) + $depth; if ( ($aDepth > 2 || $bDepth > 2) && abs($aDepth - $bDepth) > 0 @@ -4354,8 +4353,8 @@ private static function generalizeType(Type $a, Type $b): Type } $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType())), - TypeCombinator::union(self::generalizeType($aValueType, $bValueType)), + TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)), + TypeCombinator::union(self::generalizeType($aValueType, $bValueType, $depth + 1)), ); if ($generalArraysA->isIterableAtLeastOnce()->yes() && $generalArraysB->isIterableAtLeastOnce()->yes()) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); @@ -4514,17 +4513,14 @@ private static function generalizeType(Type $a, Type $b): Type ); } - private static function getArrayDepth(ArrayType $type): int + private static function getArrayDepth(Type $type): int { $depth = 0; - while ($type instanceof ArrayType) { + $arrays = TypeUtils::getAnyArrays($type); + while (count($arrays) > 0) { $temp = $type->getIterableValueType(); - $arrays = $temp->getArrays(); - if (count($arrays) === 1) { - $type = $arrays[0]; - } else { - $type = $temp; - } + $type = $temp; + $arrays = TypeUtils::getAnyArrays($type); $depth++; } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index c9853c7943..9630c6bbc3 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -116,11 +116,12 @@ public function getArrays(): array { $arrays = []; foreach ($this->types as $type) { - if (!$type instanceof ArrayType) { + $innerTypeArrays = $type->getArrays(); + if ($innerTypeArrays === []) { return []; } - foreach ($type->getArrays() as $array) { + foreach ($innerTypeArrays as $array) { $arrays[] = $array; } } From 58d04727519b3f29ec4e19695f18571c1c5052b6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 31 Dec 2022 14:57:09 +0100 Subject: [PATCH 11/11] recurse into union-inner Intersections --- src/Type/TypeUtils.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index a51e2fb51b..7c83f34d48 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -218,7 +218,9 @@ private static function map( if ($type instanceof UnionType) { $matchingTypes = []; foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof $typeClass) { + $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched); + + if ($matchingInner === []) { if ($stopOnUnmatched) { return []; } @@ -226,7 +228,9 @@ private static function map( continue; } - $matchingTypes[] = $innerType; + foreach ($matchingInner as $innerMapped) { + $matchingTypes[] = $innerMapped; + } } return $matchingTypes;