diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon index 0485b59152..fec7e42cc8 100644 --- a/build/enum-adapter-errors.neon +++ b/build/enum-adapter-errors.neon @@ -257,7 +257,7 @@ parameters: - message: "#^Call to method getName\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 3 + count: 4 path: ../src/Reflection/Php/PhpClassReflectionExtension.php - @@ -277,7 +277,7 @@ parameters: - message: "#^Call to method isTrait\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 + count: 2 path: ../src/Reflection/Php/PhpClassReflectionExtension.php - @@ -335,6 +335,11 @@ parameters: count: 1 path: ../tests/PHPStan/Analyser/AnalyserIntegrationTest.php + - + message: "#^Call to method getProperty\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" + count: 1 + path: ../src/PhpDoc/PhpDocInheritanceResolver.php + - message: "#^Call to method getName\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" count: 1 diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 320d46780f..25b335f272 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -127,6 +127,16 @@ private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $t if ($propertyName !== null && $classReflection->getNativeReflection()->hasProperty($propertyName)) { $stub = $this->stubPhpDocProvider->findPropertyPhpDoc($classReflection->getName(), $propertyName); + + if ($stub === null) { + $propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName); + + $propertyDeclaringClass = $propertyReflection->getBetterReflection()->getDeclaringClass(); + + if ($propertyDeclaringClass->isTrait() && (! $propertyReflection->getDeclaringClass()->isTrait() || $propertyReflection->getDeclaringClass()->getName() !== $propertyDeclaringClass->getName())) { + $stub = $this->stubPhpDocProvider->findPropertyPhpDoc($propertyDeclaringClass->getName(), $propertyName); + } + } if ($stub !== null) { return $stub; } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 3935cb2e3d..da14eb6cee 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -596,6 +596,24 @@ private function createMethod( $resolvedPhpDoc = null; $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters())); $phpDocBlockClassReflection = $declaringClass; + + if ($methodReflection->getReflection() !== null) { + $methodDeclaringClass = $methodReflection->getReflection()->getBetterReflection()->getDeclaringClass(); + + if ($stubPhpDocPair === null && $methodDeclaringClass->isTrait()) { + if (! $methodReflection->getDeclaringClass()->isTrait() || $methodDeclaringClass->getName() !== $methodReflection->getDeclaringClass()->getName()) { + $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors( + $this->reflectionProviderProvider->getReflectionProvider()->getClass($methodDeclaringClass->getName()), + $methodReflection->getName(), + array_map( + static fn (ReflectionParameter $parameter): string => $parameter->getName(), + $methodReflection->getParameters(), + ), + ); + } + } + } + if ($stubPhpDocPair !== null) { [$resolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; } diff --git a/tests/PHPStan/Analyser/TraitStubFilesTest.php b/tests/PHPStan/Analyser/TraitStubFilesTest.php new file mode 100644 index 0000000000..9858c6389d --- /dev/null +++ b/tests/PHPStan/Analyser/TraitStubFilesTest.php @@ -0,0 +1,35 @@ +gatherAssertTypes(__DIR__ . '/data/trait-stubs.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/trait-stubs.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/trait-stubs.php b/tests/PHPStan/Analyser/data/trait-stubs.php new file mode 100644 index 0000000000..2227e1e35d --- /dev/null +++ b/tests/PHPStan/Analyser/data/trait-stubs.php @@ -0,0 +1,28 @@ +doFoo(5)); +assertType('int', (new Bar)->notTypedFoo); diff --git a/tests/PHPStan/Analyser/data/trait-stubs.stub b/tests/PHPStan/Analyser/data/trait-stubs.stub new file mode 100644 index 0000000000..e3b314e362 --- /dev/null +++ b/tests/PHPStan/Analyser/data/trait-stubs.stub @@ -0,0 +1,17 @@ +doFoo('test'); $foo->doFoo($string); }; + +trait StubbedTrait +{ + public function doFoo($int) + { + + } +} + +class ClassUsingStubbedTrait +{ + use StubbedTrait; +} + +function (ClassUsingStubbedTrait $foo): void { + $foo->doFoo('string'); +}; diff --git a/tests/notAutoloaded/stubs-methods.php b/tests/notAutoloaded/stubs-methods.php index 4339142393..997d76a531 100644 --- a/tests/notAutoloaded/stubs-methods.php +++ b/tests/notAutoloaded/stubs-methods.php @@ -90,3 +90,16 @@ public function doFoo($i) } } + +trait StubbedTrait +{ + /** + * @param int $int + * + * @return void + */ + public function doFoo($int) + { + + } +}