From 4a4c739f9ff85b6c73659c21f8f3b8b7af8c82c9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Apr 2024 09:16:54 +0200 Subject: [PATCH] Fix string concatenation with benevolent union type --- src/Type/BenevolentUnionType.php | 9 ++++- src/Type/UnionType.php | 38 +++++++++++++++---- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-10863.php | 26 +++++++++++++ 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-10863.php diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index 27987a03b3..dc1245ad45 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -43,11 +43,18 @@ protected function unionTypes(callable $getType): Type return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$resultTypes)); } - protected function pickFromTypes(callable $getValues): array + protected function pickFromTypes( + callable $getValues, + callable $criteria, + ): array { $values = []; foreach ($this->getTypes() as $type) { $innerValues = $getValues($type); + if ($innerValues === [] && $criteria($type)) { + return []; + } + foreach ($innerValues as $innerType) { $values[] = $innerType; } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f7f37fadd8..7917a7ec20 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -124,27 +124,42 @@ public function getReferencedClasses(): array public function getObjectClassNames(): array { - return array_values(array_unique($this->pickFromTypes(static fn (Type $type) => $type->getObjectClassNames()))); + return array_values(array_unique($this->pickFromTypes( + static fn (Type $type) => $type->getObjectClassNames(), + static fn (Type $type) => $type->isObject()->yes(), + ))); } public function getObjectClassReflections(): array { - return $this->pickFromTypes(static fn (Type $type) => $type->getObjectClassReflections()); + return $this->pickFromTypes( + static fn (Type $type) => $type->getObjectClassReflections(), + static fn (Type $type) => $type->isObject()->yes(), + ); } public function getArrays(): array { - return $this->pickFromTypes(static fn (Type $type) => $type->getArrays()); + return $this->pickFromTypes( + static fn (Type $type) => $type->getArrays(), + static fn (Type $type) => $type->isArray()->yes(), + ); } public function getConstantArrays(): array { - return $this->pickFromTypes(static fn (Type $type) => $type->getConstantArrays()); + return $this->pickFromTypes( + static fn (Type $type) => $type->getConstantArrays(), + static fn (Type $type) => $type->isArray()->yes(), + ); } public function getConstantStrings(): array { - return $this->pickFromTypes(static fn (Type $type) => $type->getConstantStrings()); + return $this->pickFromTypes( + static fn (Type $type) => $type->getConstantStrings(), + static fn (Type $type) => $type->isString()->yes(), + ); } public function accepts(Type $type, bool $strictTypes): TrinaryLogic @@ -719,7 +734,10 @@ public function shuffleArray(): Type public function getEnumCases(): array { - return $this->pickFromTypes(static fn (Type $type) => $type->getEnumCases()); + return $this->pickFromTypes( + static fn (Type $type) => $type->getEnumCases(), + static fn (Type $type) => $type->isObject()->yes(), + ); } public function isCallable(): TrinaryLogic @@ -1073,15 +1091,19 @@ protected function unionTypes(callable $getType): Type */ protected function pickTypes(callable $getTypes): array { - return $this->pickFromTypes($getTypes); + return $this->pickFromTypes($getTypes, static fn () => false); } /** * @template T * @param callable(Type $type): list $getValues + * @param callable(Type $type): bool $criteria * @return list */ - protected function pickFromTypes(callable $getValues): array + protected function pickFromTypes( + callable $getValues, + callable $criteria, + ): array { $values = []; foreach ($this->types as $type) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a41cdd1ffe..074129c740 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -764,6 +764,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6404.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6399.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10863.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-chunk.php'); diff --git a/tests/PHPStan/Analyser/data/bug-10863.php b/tests/PHPStan/Analyser/data/bug-10863.php new file mode 100644 index 0000000000..ecad48736e --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-10863.php @@ -0,0 +1,26 @@ + $b + */ + public function doFoo($b): void + { + assertType('non-falsy-string', '@' . $b); + } + + /** + * @param int|false $b + */ + public function doFoo2($b): void + { + assertType('non-falsy-string', '@' . $b); + } + +}