Skip to content

Commit 33c2cb1

Browse files
committed
Fix recursion with object shapes in @property referencing other class and then back in recursive manner
1 parent 0c4660d commit 33c2cb1

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

src/Type/ObjectType.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
use PHPStan\Reflection\ClassMemberAccessAnswerer;
2525
use PHPStan\Reflection\ClassReflection;
2626
use PHPStan\Reflection\ConstantReflection;
27+
use PHPStan\Reflection\Dummy\DummyPropertyReflection;
2728
use PHPStan\Reflection\ExtendedMethodReflection;
2829
use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
2930
use PHPStan\Reflection\PropertyReflection;
3031
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
3132
use PHPStan\Reflection\TrivialParametersAcceptor;
33+
use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
3234
use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection;
3335
use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection;
3436
use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
@@ -159,7 +161,8 @@ public function hasProperty(string $propertyName): TrinaryLogic
159161
return TrinaryLogic::createMaybe();
160162
}
161163

162-
if ($classReflection->hasProperty($propertyName)) {
164+
$classHasProperty = RecursionGuard::run($this, static fn (): bool => $classReflection->hasProperty($propertyName));
165+
if ($classHasProperty === true || $classHasProperty instanceof ErrorType) {
163166
return TrinaryLogic::createYes();
164167
}
165168

@@ -225,7 +228,17 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
225228
throw new ClassNotFoundException($this->className);
226229
}
227230

228-
$property = $nakedClassReflection->getProperty($propertyName, $scope);
231+
$property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getProperty($propertyName, $scope));
232+
if ($property instanceof ErrorType) {
233+
$property = new DummyPropertyReflection();
234+
235+
return new CallbackUnresolvedPropertyPrototypeReflection(
236+
$property,
237+
$property->getDeclaringClass(),
238+
false,
239+
static fn (Type $type): Type => $type,
240+
);
241+
}
229242

230243
$ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
231244
$resolvedClassReflection = null;
@@ -247,6 +260,9 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
247260
);
248261
}
249262

263+
/**
264+
* @deprecated Not in use anymore.
265+
*/
250266
public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
251267
{
252268
$classReflection = $this->getNakedClassReflection();

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,12 @@ public function testBug6442(): void
555555
$this->assertSame(9, $errors[1]->getLine());
556556
}
557557

558+
public function testBug13057(): void
559+
{
560+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-13057.php');
561+
$this->assertNoErrors($errors);
562+
}
563+
558564
public function testBug6375(): void
559565
{
560566
$errors = $this->runAnalyse(__DIR__ . '/data/bug-6375.php');
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13057;
4+
5+
use AllowDynamicProperties;
6+
use function PHPStan\Testing\assertType;
7+
8+
/**
9+
* @property ModelB & object{extra: string} $modelB
10+
*/
11+
#[AllowDynamicProperties]
12+
class ModelA
13+
{}
14+
15+
/**
16+
* @property ModelA & object{extra: string} $modelA
17+
*/
18+
#[AllowDynamicProperties]
19+
class ModelB
20+
{}
21+
22+
function (ModelA $a, ModelB $b): void {
23+
assertType('string', $a->modelB->extra);
24+
assertType('string', $b->modelA->extra);
25+
};

0 commit comments

Comments
 (0)