Skip to content

Commit 1740286

Browse files
staabmondrejmirtes
authored andcommitted
Implement require-extends reflection plumbing
1 parent 8900a43 commit 1740286

18 files changed

+516
-0
lines changed

conf/config.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,12 @@ services:
723723
-
724724
class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository
725725

726+
-
727+
class: PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension
728+
729+
-
730+
class: PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension
731+
726732
-
727733
class: PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension
728734
tags:

src/Analyser/NodeScopeResolver.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,8 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
16931693
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
16941694
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
16951695
$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(),
1696+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsPropertyClassReflectionExtension(),
1697+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsMethodsClassReflectionExtension(),
16961698
$betterReflectionClass->getName(),
16971699
$betterReflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($betterReflectionClass) : new ReflectionClass($betterReflectionClass),
16981700
null,

src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
1010
use PHPStan\Reflection\ClassReflectionExtensionRegistry;
1111
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
12+
use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension;
13+
use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension;
1214
use function array_merge;
1315

1416
class LazyClassReflectionExtensionRegistryProvider implements ClassReflectionExtensionRegistryProvider
@@ -32,6 +34,8 @@ public function getRegistry(): ClassReflectionExtensionRegistry
3234
array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension]),
3335
array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension]),
3436
$this->container->getServicesByTag(BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG),
37+
$this->container->getByType(RequireExtendsPropertiesClassReflectionExtension::class),
38+
$this->container->getByType(RequireExtendsMethodsClassReflectionExtension::class),
3539
);
3640
}
3741

src/Reflection/BetterReflection/BetterReflectionProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public function getClass(string $className): ClassReflection
143143
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
144144
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
145145
$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(),
146+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsPropertyClassReflectionExtension(),
147+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsMethodsClassReflectionExtension(),
146148
$reflectionClass->getName(),
147149
$reflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($reflectionClass) : new ReflectionClass($reflectionClass),
148150
null,
@@ -221,6 +223,8 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $
221223
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
222224
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
223225
$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(),
226+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsPropertyClassReflectionExtension(),
227+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsMethodsClassReflectionExtension(),
224228
sprintf('class@anonymous/%s:%s', $filename, $classNode->getLine()),
225229
new ReflectionClass($reflectionClass),
226230
$scopeFile,

src/Reflection/ClassReflection.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
3030
use PHPStan\Reflection\Php\PhpPropertyReflection;
3131
use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
32+
use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension;
33+
use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension;
3234
use PHPStan\Reflection\SignatureMap\SignatureMapProvider;
3335
use PHPStan\ShouldNotHappenException;
3436
use PHPStan\Type\CircularTypeAliasDefinitionException;
@@ -146,6 +148,8 @@ public function __construct(
146148
private array $propertiesClassReflectionExtensions,
147149
private array $methodsClassReflectionExtensions,
148150
private array $allowedSubTypesClassReflectionExtensions,
151+
private RequireExtendsPropertiesClassReflectionExtension $requireExtendsPropertiesClassReflectionExtension,
152+
private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension,
149153
private string $displayName,
150154
private ReflectionClass|ReflectionEnum $reflection,
151155
private ?string $anonymousFilename,
@@ -435,6 +439,10 @@ public function hasProperty(string $propertyName): bool
435439
}
436440
}
437441

442+
if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
443+
return true;
444+
}
445+
438446
return false;
439447
}
440448

@@ -446,6 +454,10 @@ public function hasMethod(string $methodName): bool
446454
}
447455
}
448456

457+
if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
458+
return true;
459+
}
460+
449461
return false;
450462
}
451463

@@ -455,6 +467,7 @@ public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope):
455467
if ($scope->isInClass()) {
456468
$key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
457469
}
470+
458471
if (!isset($this->methods[$key])) {
459472
foreach ($this->methodsClassReflectionExtensions as $extension) {
460473
if (!$extension->hasMethod($this, $methodName)) {
@@ -469,6 +482,13 @@ public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope):
469482
}
470483
}
471484

485+
if (!isset($this->methods[$key])) {
486+
if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
487+
$method = $this->requireExtendsMethodsClassReflectionExtension->getMethod($this, $methodName);
488+
$this->methods[$key] = $method;
489+
}
490+
}
491+
472492
if (!isset($this->methods[$key])) {
473493
throw new MissingMethodFromReflectionException($this->getName(), $methodName);
474494
}
@@ -577,11 +597,13 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
577597
if ($scope->isInClass()) {
578598
$key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
579599
}
600+
580601
if (!isset($this->properties[$key])) {
581602
foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
582603
if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
583604
break;
584605
}
606+
585607
if (!$extension->hasProperty($this, $propertyName)) {
586608
continue;
587609
}
@@ -594,6 +616,13 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
594616
}
595617
}
596618

619+
if (!isset($this->properties[$key])) {
620+
if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
621+
$property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName);
622+
$this->properties[$key] = $property;
623+
}
624+
}
625+
597626
if (!isset($this->properties[$key])) {
598627
throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
599628
}
@@ -1410,6 +1439,8 @@ public function withTypes(array $types): self
14101439
$this->propertiesClassReflectionExtensions,
14111440
$this->methodsClassReflectionExtensions,
14121441
$this->allowedSubTypesClassReflectionExtensions,
1442+
$this->requireExtendsPropertiesClassReflectionExtension,
1443+
$this->requireExtendsMethodsClassReflectionExtension,
14131444
$this->displayName,
14141445
$this->reflection,
14151446
$this->anonymousFilename,
@@ -1437,6 +1468,8 @@ public function withVariances(array $variances): self
14371468
$this->propertiesClassReflectionExtensions,
14381469
$this->methodsClassReflectionExtensions,
14391470
$this->allowedSubTypesClassReflectionExtensions,
1471+
$this->requireExtendsPropertiesClassReflectionExtension,
1472+
$this->requireExtendsMethodsClassReflectionExtension,
14401473
$this->displayName,
14411474
$this->reflection,
14421475
$this->anonymousFilename,

src/Reflection/ClassReflectionExtensionRegistry.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace PHPStan\Reflection;
44

55
use PHPStan\Broker\Broker;
6+
use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension;
7+
use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension;
68
use function array_merge;
79

810
class ClassReflectionExtensionRegistry
@@ -18,6 +20,8 @@ public function __construct(
1820
private array $propertiesClassReflectionExtensions,
1921
private array $methodsClassReflectionExtensions,
2022
private array $allowedSubTypesClassReflectionExtensions,
23+
private RequireExtendsPropertiesClassReflectionExtension $requireExtendsPropertiesClassReflectionExtension,
24+
private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension,
2125
)
2226
{
2327
foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions, $allowedSubTypesClassReflectionExtensions) as $extension) {
@@ -53,4 +57,14 @@ public function getAllowedSubTypesClassReflectionExtensions(): array
5357
return $this->allowedSubTypesClassReflectionExtensions;
5458
}
5559

60+
public function getRequireExtendsPropertyClassReflectionExtension(): RequireExtendsPropertiesClassReflectionExtension
61+
{
62+
return $this->requireExtendsPropertiesClassReflectionExtension;
63+
}
64+
65+
public function getRequireExtendsMethodsClassReflectionExtension(): RequireExtendsMethodsClassReflectionExtension
66+
{
67+
return $this->requireExtendsMethodsClassReflectionExtension;
68+
}
69+
5670
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection\RequireExtension;
4+
5+
use PHPStan\Analyser\OutOfClassScope;
6+
use PHPStan\Reflection\ClassReflection;
7+
use PHPStan\Reflection\ExtendedMethodReflection;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\MethodsClassReflectionExtension;
10+
use PHPStan\ShouldNotHappenException;
11+
12+
class RequireExtendsMethodsClassReflectionExtension implements MethodsClassReflectionExtension
13+
{
14+
15+
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
16+
{
17+
return $this->findMethod($classReflection, $methodName) !== null;
18+
}
19+
20+
/**
21+
* @return ExtendedMethodReflection
22+
*/
23+
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
24+
{
25+
$method = $this->findMethod($classReflection, $methodName);
26+
if ($method === null) {
27+
throw new ShouldNotHappenException();
28+
}
29+
30+
return $method;
31+
}
32+
33+
/**
34+
* @return ExtendedMethodReflection|null
35+
*/
36+
private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection
37+
{
38+
if (!$classReflection->isInterface()) {
39+
return null;
40+
}
41+
42+
$extendsTags = $classReflection->getRequireExtendsTags();
43+
foreach ($extendsTags as $extendsTag) {
44+
$type = $extendsTag->getType();
45+
46+
if (!$type->hasMethod($methodName)->yes()) {
47+
continue;
48+
}
49+
50+
return $type->getMethod($methodName, new OutOfClassScope());
51+
}
52+
53+
$interfaces = $classReflection->getInterfaces();
54+
foreach ($interfaces as $interface) {
55+
$method = $this->findMethod($interface, $methodName);
56+
if ($method !== null) {
57+
return $method;
58+
}
59+
}
60+
61+
return null;
62+
}
63+
64+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection\RequireExtension;
4+
5+
use PHPStan\Analyser\OutOfClassScope;
6+
use PHPStan\Reflection\ClassReflection;
7+
use PHPStan\Reflection\PropertiesClassReflectionExtension;
8+
use PHPStan\Reflection\PropertyReflection;
9+
use PHPStan\ShouldNotHappenException;
10+
11+
class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension
12+
{
13+
14+
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
15+
{
16+
return $this->findProperty($classReflection, $propertyName) !== null;
17+
}
18+
19+
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
20+
{
21+
$property = $this->findProperty($classReflection, $propertyName);
22+
if ($property === null) {
23+
throw new ShouldNotHappenException();
24+
}
25+
26+
return $property;
27+
}
28+
29+
private function findProperty(ClassReflection $classReflection, string $propertyName): ?PropertyReflection
30+
{
31+
if (!$classReflection->isInterface()) {
32+
return null;
33+
}
34+
35+
$requireExtendsTags = $classReflection->getRequireExtendsTags();
36+
foreach ($requireExtendsTags as $requireExtendsTag) {
37+
$type = $requireExtendsTag->getType();
38+
39+
if (!$type->hasProperty($propertyName)->yes()) {
40+
continue;
41+
}
42+
43+
return $type->getProperty($propertyName, new OutOfClassScope());
44+
}
45+
46+
$interfaces = $classReflection->getInterfaces();
47+
foreach ($interfaces as $interface) {
48+
$property = $this->findProperty($interface, $propertyName);
49+
if ($property !== null) {
50+
return $property;
51+
}
52+
}
53+
54+
return null;
55+
}
56+
57+
}

src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class InvalidPHPStanDocTagRule implements Rule
5252
'@phpstan-allow-private-mutation',
5353
'@phpstan-readonly',
5454
'@phpstan-readonly-allow-private-mutation',
55+
'@phpstan-require-extends',
56+
'@phpstan-require-implements',
5557
];
5658

5759
public function __construct(

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,9 @@ public function dataFileAsserts(): iterable
14191419
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5961.php');
14201420
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10189.php');
14211421
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10317.php');
1422+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-interface-extends.php');
1423+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-extends.php');
1424+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-implements.php');
14221425
}
14231426

14241427
/**

0 commit comments

Comments
 (0)