Skip to content

Commit 9ab4e6f

Browse files
committed
Fix infinite recursion in mixin extensions
1 parent c87284a commit 9ab4e6f

File tree

4 files changed

+68
-1
lines changed

4 files changed

+68
-1
lines changed

src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Reflection\MethodsClassReflectionExtension;
99
use PHPStan\ShouldNotHappenException;
1010
use PHPStan\Type\TypeUtils;
11+
use PHPStan\Type\VerbosityLevel;
1112
use function array_intersect;
1213
use function count;
1314

@@ -17,6 +18,9 @@ class MixinMethodsClassReflectionExtension implements MethodsClassReflectionExte
1718
/** @var string[] */
1819
private array $mixinExcludeClasses;
1920

21+
/** @var array<string, array<string, true>> */
22+
private array $inProcess = [];
23+
2024
/**
2125
* @param string[] $mixinExcludeClasses
2226
*/
@@ -48,11 +52,22 @@ private function findMethod(ClassReflection $classReflection, string $methodName
4852
continue;
4953
}
5054

55+
$typeDescription = $type->describe(VerbosityLevel::typeOnly());
56+
if (isset($this->inProcess[$typeDescription][$methodName])) {
57+
continue;
58+
}
59+
60+
$this->inProcess[$typeDescription][$methodName] = true;
61+
5162
if (!$type->hasMethod($methodName)->yes()) {
63+
unset($this->inProcess[$typeDescription][$methodName]);
5264
continue;
5365
}
5466

5567
$method = $type->getMethod($methodName, new OutOfClassScope());
68+
69+
unset($this->inProcess[$typeDescription][$methodName]);
70+
5671
$static = $method->isStatic();
5772
if (
5873
!$static

src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Reflection\PropertyReflection;
99
use PHPStan\ShouldNotHappenException;
1010
use PHPStan\Type\TypeUtils;
11+
use PHPStan\Type\VerbosityLevel;
1112
use function array_intersect;
1213
use function count;
1314

@@ -17,6 +18,9 @@ class MixinPropertiesClassReflectionExtension implements PropertiesClassReflecti
1718
/** @var string[] */
1819
private array $mixinExcludeClasses;
1920

21+
/** @var array<string, array<string, true>> */
22+
private array $inProcess = [];
23+
2024
/**
2125
* @param string[] $mixinExcludeClasses
2226
*/
@@ -48,11 +52,22 @@ private function findProperty(ClassReflection $classReflection, string $property
4852
continue;
4953
}
5054

55+
$typeDescription = $type->describe(VerbosityLevel::typeOnly());
56+
if (isset($this->inProcess[$typeDescription][$propertyName])) {
57+
continue;
58+
}
59+
60+
$this->inProcess[$typeDescription][$propertyName] = true;
61+
5162
if (!$type->hasProperty($propertyName)->yes()) {
63+
unset($this->inProcess[$typeDescription][$propertyName]);
5264
continue;
5365
}
5466

55-
return $type->getProperty($propertyName, new OutOfClassScope());
67+
$property = $type->getProperty($propertyName, new OutOfClassScope());
68+
unset($this->inProcess[$typeDescription][$propertyName]);
69+
70+
return $property;
5671
}
5772

5873
foreach ($classReflection->getParents() as $parentClass) {

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,17 @@ public function testBug6255(): void
489489
$this->assertNoErrors($errors);
490490
}
491491

492+
public function testBug6300(): void
493+
{
494+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-6300.php');
495+
$this->assertCount(2, $errors);
496+
$this->assertSame('Call to an undefined method Bug6300\Bar::get().', $errors[0]->getMessage());
497+
$this->assertSame(23, $errors[0]->getLine());
498+
499+
$this->assertSame('Access to an undefined property Bug6300\Bar::$fooProp.', $errors[1]->getMessage());
500+
$this->assertSame(24, $errors[1]->getLine());
501+
}
502+
492503
/**
493504
* @return Error[]
494505
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Bug6300;
4+
5+
/**
6+
* @mixin Bar
7+
*/
8+
class Foo
9+
{
10+
11+
}
12+
13+
/**
14+
* @mixin Foo
15+
*/
16+
class Bar
17+
{
18+
19+
}
20+
21+
function (Bar $b): void
22+
{
23+
$b->get();
24+
echo $b->fooProp;
25+
};
26+

0 commit comments

Comments
 (0)