diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6d5d8216a8..79e6594b46 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -7025,6 +7025,23 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ return $phpDocReturnType; } + if ($phpDocReturnType instanceof UnionType) { + $types = []; + foreach ($phpDocReturnType->getTypes() as $innerType) { + if (!$nativeReturnType->isSuperTypeOf($innerType)->yes()) { + continue; + } + + $types[] = $innerType; + } + + if (count($types) === 0) { + return null; + } + + return TypeCombinator::union(...$types); + } + return null; } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index f61326b202..a8a6e5a948 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -53,6 +53,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; +use PHPStan\Type\UnionType; use function array_key_exists; use function array_keys; use function array_map; @@ -1241,10 +1242,31 @@ private function getPhpDocReturnType(ClassReflection $phpDocBlockClassReflection TemplateTypeVariance::createCovariant(), ); - if ($returnTag->isExplicit() || $nativeReturnType->isSuperTypeOf($phpDocReturnType)->yes()) { + if ($returnTag->isExplicit()) { return $phpDocReturnType; } + if ($nativeReturnType->isSuperTypeOf($phpDocReturnType)->yes()) { + return $phpDocReturnType; + } + + if ($phpDocReturnType instanceof UnionType) { + $types = []; + foreach ($phpDocReturnType->getTypes() as $innerType) { + if (!$nativeReturnType->isSuperTypeOf($innerType)->yes()) { + continue; + } + + $types[] = $innerType; + } + + if (count($types) === 0) { + return null; + } + + return TypeCombinator::union(...$types); + } + return null; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 80984358d1..9f7165c275 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -172,6 +172,8 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Arrays/data/slevomat-foreach-unset-bug.php'; yield __DIR__ . '/../Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php'; + yield __DIR__ . '/../Rules/Methods/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php'; + if (PHP_VERSION_ID >= 80000) { yield __DIR__ . '/../Rules/Comparison/data/bug-7898.php'; } diff --git a/tests/PHPStan/Analyser/nsrt/inherit-phpdoc-return-type-with-narrower-native-return-type-php8.php b/tests/PHPStan/Analyser/nsrt/inherit-phpdoc-return-type-with-narrower-native-return-type-php8.php new file mode 100644 index 0000000000..6e1ff61c3a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/inherit-phpdoc-return-type-with-narrower-native-return-type-php8.php @@ -0,0 +1,32 @@ += 8.0 + +namespace InheritPhpDocReturnTypeWithNarrowerNativeReturnTypePhp8; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @return array|positive-int|null + */ + public function doFoo(): array|int|null + { + + } + +} + +class Bar extends Foo +{ + + public function doFoo(): array|int + { + + } + +} + +function (Bar $bar): void { + assertType('array|int<1, max>', $bar->doFoo()); +}; diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index dc0f1b7931..ffeb6ce61b 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -118,4 +118,9 @@ public function testBug9657(): void $this->analyse([__DIR__ . '/data/bug-9657.php'], []); } + public function testInheritPhpDocReturnTypeWithNarrowerNativeReturnType(): void + { + $this->analyse([__DIR__ . '/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php b/tests/PHPStan/Rules/Methods/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php new file mode 100644 index 0000000000..b938396cdf --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php @@ -0,0 +1,32 @@ +|null + */ + public function doFoo(): ?array + { + + } + +} + +class Bar extends Foo +{ + + public function doFoo(): array + { + + } + +} + +function (Bar $bar): void { + assertType('array', $bar->doFoo()); +};