diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index 508074e4be..e2dac5a160 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\ShouldNotHappenException; use PHPStan\Type\TypeUtils; +use PHPStan\Type\VerbosityLevel; use function array_intersect; use function count; @@ -17,6 +18,9 @@ class MixinMethodsClassReflectionExtension implements MethodsClassReflectionExte /** @var string[] */ private array $mixinExcludeClasses; + /** @var array> */ + private array $inProcess = []; + /** * @param string[] $mixinExcludeClasses */ @@ -48,11 +52,22 @@ private function findMethod(ClassReflection $classReflection, string $methodName continue; } + $typeDescription = $type->describe(VerbosityLevel::typeOnly()); + if (isset($this->inProcess[$typeDescription][$methodName])) { + continue; + } + + $this->inProcess[$typeDescription][$methodName] = true; + if (!$type->hasMethod($methodName)->yes()) { + unset($this->inProcess[$typeDescription][$methodName]); continue; } $method = $type->getMethod($methodName, new OutOfClassScope()); + + unset($this->inProcess[$typeDescription][$methodName]); + $static = $method->isStatic(); if ( !$static diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index 3f0c947184..86b9242745 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\PropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\TypeUtils; +use PHPStan\Type\VerbosityLevel; use function array_intersect; use function count; @@ -17,6 +18,9 @@ class MixinPropertiesClassReflectionExtension implements PropertiesClassReflecti /** @var string[] */ private array $mixinExcludeClasses; + /** @var array> */ + private array $inProcess = []; + /** * @param string[] $mixinExcludeClasses */ @@ -48,11 +52,22 @@ private function findProperty(ClassReflection $classReflection, string $property continue; } + $typeDescription = $type->describe(VerbosityLevel::typeOnly()); + if (isset($this->inProcess[$typeDescription][$propertyName])) { + continue; + } + + $this->inProcess[$typeDescription][$propertyName] = true; + if (!$type->hasProperty($propertyName)->yes()) { + unset($this->inProcess[$typeDescription][$propertyName]); continue; } - return $type->getProperty($propertyName, new OutOfClassScope()); + $property = $type->getProperty($propertyName, new OutOfClassScope()); + unset($this->inProcess[$typeDescription][$propertyName]); + + return $property; } foreach ($classReflection->getParents() as $parentClass) { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 5b28bbfe5b..9d11389b7e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -489,6 +489,17 @@ public function testBug6255(): void $this->assertNoErrors($errors); } + public function testBug6300(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-6300.php'); + $this->assertCount(2, $errors); + $this->assertSame('Call to an undefined method Bug6300\Bar::get().', $errors[0]->getMessage()); + $this->assertSame(23, $errors[0]->getLine()); + + $this->assertSame('Access to an undefined property Bug6300\Bar::$fooProp.', $errors[1]->getMessage()); + $this->assertSame(24, $errors[1]->getLine()); + } + /** * @return Error[] */ diff --git a/tests/PHPStan/Analyser/data/bug-6300.php b/tests/PHPStan/Analyser/data/bug-6300.php new file mode 100644 index 0000000000..c9244c6cec --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6300.php @@ -0,0 +1,26 @@ +get(); + echo $b->fooProp; +}; +