Skip to content

Commit 77a92fc

Browse files
committed
Override methods definitions with annotations
1 parent bf521bd commit 77a92fc

File tree

5 files changed

+130
-23
lines changed

5 files changed

+130
-23
lines changed

src/Reflection/ClassReflection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public function hasNativeMethod(string $methodName): bool
168168

169169
public function getNativeMethod(string $methodName): PhpMethodReflection
170170
{
171-
return $this->getPhpExtension()->getMethod($this, $methodName);
171+
return $this->getPhpExtension()->getNativeMethod($this, $methodName);
172172
}
173173

174174
private function getPhpExtension(): PhpClassReflectionExtension

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Broker\Broker;
66
use PHPStan\PhpDoc\PhpDocBlock;
7+
use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension;
78
use PHPStan\Reflection\BrokerAwareClassReflectionExtension;
89
use PHPStan\Reflection\ClassReflection;
910
use PHPStan\Reflection\MethodReflection;
@@ -24,22 +25,30 @@ class PhpClassReflectionExtension
2425
/** @var \PHPStan\Type\FileTypeMapper */
2526
private $fileTypeMapper;
2627

28+
/** @var \PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension */
29+
private $annotationsMethodsClassReflectionExtension;
30+
2731
/** @var \PHPStan\Broker\Broker */
2832
private $broker;
2933

3034
/** @var \PHPStan\Reflection\Php\PhpPropertyReflection[][] */
3135
private $properties = [];
3236

37+
/** @var \PHPStan\Reflection\MethodReflection[][] */
38+
private $methodsIncludingAnnotations = [];
39+
3340
/** @var \PHPStan\Reflection\Php\PhpMethodReflection[][] */
34-
private $methods = [];
41+
private $nativeMethods = [];
3542

3643
public function __construct(
3744
PhpMethodReflectionFactory $methodReflectionFactory,
38-
FileTypeMapper $fileTypeMapper
45+
FileTypeMapper $fileTypeMapper,
46+
AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension
3947
)
4048
{
4149
$this->methodReflectionFactory = $methodReflectionFactory;
4250
$this->fileTypeMapper = $fileTypeMapper;
51+
$this->annotationsMethodsClassReflectionExtension = $annotationsMethodsClassReflectionExtension;
4352
}
4453

4554
public function setBroker(Broker $broker)
@@ -126,27 +135,39 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):
126135
return $classReflection->getNativeReflection()->hasMethod($methodName);
127136
}
128137

129-
/**
130-
* @param \PHPStan\Reflection\ClassReflection $classReflection
131-
* @param string $methodName
132-
* @return \PHPStan\Reflection\Php\PhpMethodReflection
133-
*/
134138
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
135139
{
136-
if (!isset($this->methods[$classReflection->getName()])) {
137-
$this->methods[$classReflection->getName()] = $this->createMethods($classReflection);
140+
if (!isset($this->methodsIncludingAnnotations[$classReflection->getName()])) {
141+
$this->methodsIncludingAnnotations[$classReflection->getName()] = $this->createMethods($classReflection, true);
138142
}
139143

140144
$nativeMethodReflection = $classReflection->getNativeReflection()->getMethod($methodName);
141145

142-
return $this->methods[$classReflection->getName()][$nativeMethodReflection->getName()];
146+
return $this->methodsIncludingAnnotations[$classReflection->getName()][$nativeMethodReflection->getName()];
147+
}
148+
149+
public function getNativeMethod(ClassReflection $classReflection, string $methodName): PhpMethodReflection
150+
{
151+
if (!isset($this->nativeMethods[$classReflection->getName()])) {
152+
/** @var \PHPStan\Reflection\Php\PhpMethodReflection[] $methods */
153+
$methods = $this->createMethods($classReflection, false);
154+
$this->nativeMethods[$classReflection->getName()] = $methods;
155+
}
156+
157+
$nativeMethodReflection = $classReflection->getNativeReflection()->getMethod($methodName);
158+
159+
return $this->nativeMethods[$classReflection->getName()][$nativeMethodReflection->getName()];
143160
}
144161

145162
/**
146163
* @param \PHPStan\Reflection\ClassReflection $classReflection
147-
* @return \PHPStan\Reflection\Php\PhpMethodReflection[]
164+
* @param bool $includingAnnotations
165+
* @return \PHPStan\Reflection\MethodReflection[]
148166
*/
149-
private function createMethods(ClassReflection $classReflection): array
167+
private function createMethods(
168+
ClassReflection $classReflection,
169+
bool $includingAnnotations
170+
): array
150171
{
151172
$methods = [];
152173
$reflectionMethods = $classReflection->getNativeReflection()->getMethods();
@@ -162,7 +183,22 @@ private function createMethods(ClassReflection $classReflection): array
162183
$reflectionMethods[] = $classReflection->getNativeReflection()->getMethod('__invoke');
163184
}
164185
}
186+
$hierarchyDistances = $classReflection->getClassHierarchyDistances();
165187
foreach ($reflectionMethods as $methodReflection) {
188+
if ($includingAnnotations && $this->annotationsMethodsClassReflectionExtension->hasMethod($classReflection, $methodReflection->getName())) {
189+
$annotationMethod = $this->annotationsMethodsClassReflectionExtension->getMethod($classReflection, $methodReflection->getName());
190+
if (!isset($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()])) {
191+
throw new \PHPStan\ShouldNotHappenException();
192+
}
193+
if (!isset($hierarchyDistances[$methodReflection->getDeclaringClass()->getName()])) {
194+
throw new \PHPStan\ShouldNotHappenException();
195+
}
196+
197+
if ($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()] < $hierarchyDistances[$methodReflection->getDeclaringClass()->getName()]) {
198+
$methods[$methodReflection->getName()] = $annotationMethod;
199+
continue;
200+
}
201+
}
166202
$declaringClass = $this->broker->getClass($methodReflection->getDeclaringClass()->getName());
167203

168204
$phpDocParameterTypes = [];

tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Analyser\Scope;
66
use PHPStan\Broker\Broker;
7+
use PHPStan\Reflection\Php\PhpMethodReflection;
78

89
class AnnotationsMethodsClassReflectionExtensionTest extends \PHPStan\TestCase
910
{
@@ -413,10 +414,49 @@ public function dataMethods(): array
413414
],
414415
],
415416
],
417+
'overridenMethod' => [
418+
'class' => \AnnotationsMethods\Foo::class,
419+
'returnType' => \AnnotationsMethods\Foo::class,
420+
'isStatic' => false,
421+
'isVariadic' => false,
422+
'parameters' => [],
423+
],
424+
'overridenMethodWithAnnotation' => [
425+
'class' => \AnnotationsMethods\Foo::class,
426+
'returnType' => \AnnotationsMethods\Foo::class,
427+
'isStatic' => false,
428+
'isVariadic' => false,
429+
'parameters' => [],
430+
],
416431
];
417-
$barMethods = $fooMethods;
418-
$bazMethods = array_merge(
432+
$barMethods = array_merge(
419433
$fooMethods,
434+
[
435+
'overridenMethod' => [
436+
'class' => \AnnotationsMethods\Bar::class,
437+
'returnType' => \AnnotationsMethods\Bar::class,
438+
'isStatic' => false,
439+
'isVariadic' => false,
440+
'parameters' => [],
441+
],
442+
'overridenMethodWithAnnotation' => [
443+
'class' => \AnnotationsMethods\Bar::class,
444+
'returnType' => \AnnotationsMethods\Bar::class,
445+
'isStatic' => false,
446+
'isVariadic' => false,
447+
'parameters' => [],
448+
],
449+
'conflictingMethod' => [
450+
'class' => \AnnotationsMethods\Bar::class,
451+
'returnType' => \AnnotationsMethods\Bar::class,
452+
'isStatic' => false,
453+
'isVariadic' => false,
454+
'parameters' => [],
455+
],
456+
]
457+
);
458+
$bazMethods = array_merge(
459+
$barMethods,
420460
[
421461
'doSomething' => [
422462
'class' => \AnnotationsMethods\Baz::class,
@@ -918,35 +958,37 @@ public function testMethods(string $className, array $methods)
918958
$broker = $this->getContainer()->getByType(Broker::class);
919959
$class = $broker->getClass($className);
920960
$scope = $this->createMock(Scope::class);
921-
$scope->method('isInClass')->willReturn(false);
961+
$scope->method('isInClass')->willReturn(true);
962+
$scope->method('getClassReflection')->willReturn($class);
963+
$scope->method('canCallMethod')->willReturn(true);
922964
foreach ($methods as $methodName => $expectedMethodData) {
923-
$this->assertTrue($class->hasMethod($methodName), sprintf('Method %s not found in class %s.', $methodName, $className));
965+
$this->assertTrue($class->hasMethod($methodName), sprintf('Method %s() not found in class %s.', $methodName, $className));
924966

925967
$method = $class->getMethod($methodName, $scope);
926968
$this->assertSame(
927969
$expectedMethodData['class'],
928970
$method->getDeclaringClass()->getName(),
929-
sprintf('Declaring class of method $%s does not match.', $methodName)
971+
sprintf('Declaring class of method %s() does not match.', $methodName)
930972
);
931973
$this->assertEquals(
932974
$expectedMethodData['returnType'],
933975
$method->getReturnType()->describe(),
934-
sprintf('Return type of method %s::%s does not match', $className, $methodName)
976+
sprintf('Return type of method %s::%s() does not match', $className, $methodName)
935977
);
936978
$this->assertEquals(
937979
$expectedMethodData['isStatic'],
938980
$method->isStatic(),
939-
sprintf('Scope of method %s::%s does not match', $className, $methodName)
981+
sprintf('Scope of method %s::%s() does not match', $className, $methodName)
940982
);
941983
$this->assertEquals(
942984
$expectedMethodData['isVariadic'],
943985
$method->isVariadic(),
944-
sprintf('Method %s::%s does not match expected variadicity', $className, $methodName)
986+
sprintf('Method %s::%s() does not match expected variadicity', $className, $methodName)
945987
);
946988
$this->assertCount(
947989
count($expectedMethodData['parameters']),
948990
$method->getParameters(),
949-
sprintf('Method %s::%s does not match expected count of parameters', $className, $methodName)
991+
sprintf('Method %s::%s() does not match expected count of parameters', $className, $methodName)
950992
);
951993
foreach ($method->getParameters() as $i => $parameter) {
952994
$this->assertEquals(
@@ -973,4 +1015,12 @@ public function testMethods(string $className, array $methods)
9731015
}
9741016
}
9751017

1018+
public function testOverridingNativeMethodsWithAnnotationsDoesNotBreakGetNativeMethod()
1019+
{
1020+
$broker = $this->getContainer()->getByType(Broker::class);
1021+
$class = $broker->getClass(\AnnotationsMethods\Bar::class);
1022+
$this->assertTrue($class->hasNativeMethod('overridenMethodWithAnnotation'));
1023+
$this->assertInstanceOf(PhpMethodReflection::class, $class->getNativeMethod('overridenMethodWithAnnotation'));
1024+
}
1025+
9761026
}

tests/PHPStan/Reflection/Annotations/data/annotations-methods.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @method static string|float aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams A Description.
4343
* @method \Aws\Result publish(array $args = [])
4444
* @method Image rotate(float $angle, $backgroundColor)
45+
* @method Foo overridenMethod()
4546
*
4647
* Problem signatures
4748
* ==================
@@ -53,11 +54,30 @@
5354
class Foo implements FooInterface
5455
{
5556

57+
public function overridenMethodWithAnnotation(): Foo
58+
{
59+
60+
}
61+
5662
}
5763

64+
/**
65+
* @method Bar overridenMethodWithAnnotation()
66+
* @method Foo conflictingMethod()
67+
*/
5868
class Bar extends Foo
5969
{
6070

71+
public function overridenMethod(): Bar
72+
{
73+
74+
}
75+
76+
public function conflictingMethod(): Bar
77+
{
78+
79+
}
80+
6181
}
6282

6383
/**

tests/TestCase.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Parser\DirectParser;
1111
use PHPStan\Parser\FunctionCallStatementFinder;
1212
use PHPStan\Parser\Parser;
13+
use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension;
1314
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
1415
use PHPStan\Reflection\ClassReflection;
1516
use PHPStan\Reflection\FunctionReflection;
@@ -113,7 +114,7 @@ public function create(
113114
}
114115
};
115116
$fileTypeMapper = new FileTypeMapper($parser, $this->createMock(Cache::class));
116-
$phpExtension = new PhpClassReflectionExtension($methodReflectionFactory, $fileTypeMapper);
117+
$phpExtension = new PhpClassReflectionExtension($methodReflectionFactory, $fileTypeMapper, new AnnotationsMethodsClassReflectionExtension($fileTypeMapper));
117118
$functionReflectionFactory = new class($this->getParser(), $functionCallStatementFinder, $cache) implements FunctionReflectionFactory {
118119
/** @var \PHPStan\Parser\Parser */
119120
private $parser;

0 commit comments

Comments
 (0)