Skip to content

Commit 2df14af

Browse files
committed
OverridingMethodRule - search for method prototype in traits
1 parent 22e21f0 commit 2df14af

18 files changed

+158
-3
lines changed

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,9 @@ public function supportsNativeTypesInClassConstants(): bool
247247
return $this->versionId >= 80300;
248248
}
249249

250+
public function supportsAbstractTraitMethods(): bool
251+
{
252+
return $this->versionId >= 80000;
253+
}
254+
250255
}

src/Reflection/Annotations/AnnotationMethodReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,9 @@ public function returnsByReference(): TrinaryLogic
140140
return TrinaryLogic::createMaybe();
141141
}
142142

143+
public function isAbstract(): TrinaryLogic
144+
{
145+
return TrinaryLogic::createNo();
146+
}
147+
143148
}

src/Reflection/Dummy/ChangedTypeMethodReflection.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
1010
use PHPStan\TrinaryLogic;
1111
use PHPStan\Type\Type;
12+
use function is_bool;
1213

1314
class ChangedTypeMethodReflection implements ExtendedMethodReflection
1415
{
@@ -110,4 +111,14 @@ public function returnsByReference(): TrinaryLogic
110111
return $this->reflection->returnsByReference();
111112
}
112113

114+
public function isAbstract(): TrinaryLogic
115+
{
116+
$abstract = $this->reflection->isAbstract();
117+
if (is_bool($abstract)) {
118+
return TrinaryLogic::createFromBoolean($abstract);
119+
}
120+
121+
return $abstract;
122+
}
123+
113124
}

src/Reflection/Dummy/DummyConstructorReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,9 @@ public function isFinalByKeyword(): TrinaryLogic
121121
return TrinaryLogic::createMaybe();
122122
}
123123

124+
public function isAbstract(): TrinaryLogic
125+
{
126+
return TrinaryLogic::createNo();
127+
}
128+
124129
}

src/Reflection/Dummy/DummyMethodReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,9 @@ public function returnsByReference(): TrinaryLogic
113113
return TrinaryLogic::createMaybe();
114114
}
115115

116+
public function isAbstract(): TrinaryLogic
117+
{
118+
return TrinaryLogic::createNo();
119+
}
120+
116121
}

src/Reflection/ExtendedMethodReflection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ public function returnsByReference(): TrinaryLogic;
3535

3636
public function isFinalByKeyword(): TrinaryLogic;
3737

38+
public function isAbstract(): TrinaryLogic|bool;
39+
3840
}

src/Reflection/Native/NativeMethodReflection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ public function isPublic(): bool
5656
return $this->reflection->isPublic();
5757
}
5858

59-
public function isAbstract(): bool
59+
public function isAbstract(): TrinaryLogic
6060
{
61-
return $this->reflection->isAbstract();
61+
return TrinaryLogic::createFromBoolean($this->reflection->isAbstract());
6262
}
6363

6464
public function getPrototype(): ClassMemberReflection

src/Reflection/Php/ClosureCallMethodReflection.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\Type\Type;
1919
use function array_map;
2020
use function array_unshift;
21+
use function is_bool;
2122

2223
final class ClosureCallMethodReflection implements ExtendedMethodReflection
2324
{
@@ -152,4 +153,14 @@ public function returnsByReference(): TrinaryLogic
152153
return $this->nativeMethodReflection->returnsByReference();
153154
}
154155

156+
public function isAbstract(): TrinaryLogic
157+
{
158+
$abstract = $this->nativeMethodReflection->isAbstract();
159+
if (is_bool($abstract)) {
160+
return TrinaryLogic::createFromBoolean($abstract);
161+
}
162+
163+
return $abstract;
164+
}
165+
155166
}

src/Reflection/Php/EnumCasesMethodReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,9 @@ public function returnsByReference(): TrinaryLogic
125125
return TrinaryLogic::createNo();
126126
}
127127

128+
public function isAbstract(): TrinaryLogic
129+
{
130+
return TrinaryLogic::createNo();
131+
}
132+
128133
}

src/Reflection/Php/PhpMethodFromParserNodeReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,9 @@ public function returnsByReference(): TrinaryLogic
163163
return TrinaryLogic::createFromBoolean($this->getClassMethod()->returnsByRef());
164164
}
165165

166+
public function isAbstract(): TrinaryLogic
167+
{
168+
return TrinaryLogic::createFromBoolean($this->getClassMethod()->isAbstract());
169+
}
170+
166171
}

src/Reflection/ResolvedMethodReflection.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Type\Generic\TemplateTypeVariance;
1010
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
1111
use PHPStan\Type\Type;
12+
use function is_bool;
1213

1314
class ResolvedMethodReflection implements ExtendedMethodReflection
1415
{
@@ -163,4 +164,14 @@ public function returnsByReference(): TrinaryLogic
163164
return $this->reflection->returnsByReference();
164165
}
165166

167+
public function isAbstract(): TrinaryLogic
168+
{
169+
$abstract = $this->reflection->isAbstract();
170+
if (is_bool($abstract)) {
171+
return TrinaryLogic::createFromBoolean($abstract);
172+
}
173+
174+
return $abstract;
175+
}
176+
166177
}

src/Reflection/Type/IntersectionTypeMethodReflection.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use function array_map;
1717
use function count;
1818
use function implode;
19+
use function is_bool;
1920

2021
class IntersectionTypeMethodReflection implements ExtendedMethodReflection
2122
{
@@ -186,4 +187,9 @@ public function returnsByReference(): TrinaryLogic
186187
return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->returnsByReference());
187188
}
188189

190+
public function isAbstract(): TrinaryLogic
191+
{
192+
return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract());
193+
}
194+
189195
}

src/Reflection/Type/UnionTypeMethodReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,9 @@ public function returnsByReference(): TrinaryLogic
168168
return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->returnsByReference());
169169
}
170170

171+
public function isAbstract(): TrinaryLogic
172+
{
173+
return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract());
174+
}
175+
171176
}

src/Reflection/WrappedExtendedMethodReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,9 @@ public function returnsByReference(): TrinaryLogic
135135
return TrinaryLogic::createMaybe();
136136
}
137137

138+
public function isAbstract(): TrinaryLogic
139+
{
140+
return TrinaryLogic::createNo();
141+
}
142+
138143
}

src/Rules/Methods/OverridingMethodRule.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHPStan\Type\VerbosityLevel;
2121
use function array_merge;
2222
use function count;
23+
use function is_bool;
2324
use function sprintf;
2425
use function strtolower;
2526

@@ -274,6 +275,24 @@ private function findPrototype(ClassReflection $classReflection, string $methodN
274275
}
275276
}
276277

278+
if ($this->phpVersion->supportsAbstractTraitMethods()) {
279+
foreach ($classReflection->getTraits(true) as $trait) {
280+
if (!$trait->hasNativeMethod($methodName)) {
281+
continue;
282+
}
283+
284+
$method = $trait->getNativeMethod($methodName);
285+
$isAbstract = $method->isAbstract();
286+
if (is_bool($isAbstract)) {
287+
if ($isAbstract) {
288+
return $method;
289+
}
290+
} elseif ($isAbstract->yes()) {
291+
return $method;
292+
}
293+
}
294+
}
295+
277296
$parentClass = $classReflection->getParentClass();
278297
if ($parentClass === null) {
279298
return null;
@@ -293,7 +312,12 @@ private function findPrototype(ClassReflection $classReflection, string $methodN
293312
if ($method->getName() === $declaringClass->getConstructor()->getName()) {
294313
$prototype = $method->getPrototype();
295314
if ($prototype instanceof PhpMethodReflection || $prototype instanceof MethodPrototypeReflection || $prototype instanceof NativeMethodReflection) {
296-
if (!$prototype->isAbstract()) {
315+
$abstract = $prototype->isAbstract();
316+
if (is_bool($abstract)) {
317+
if (!$abstract) {
318+
return null;
319+
}
320+
} elseif (!$abstract->yes()) {
297321
return null;
298322
}
299323
}

src/Rules/Methods/StaticMethodCallCheck.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ public function check(
140140
$nativeMethodReflection = $classReflection->getNativeMethod($methodName);
141141
if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) {
142142
$isAbstract = $nativeMethodReflection->isAbstract();
143+
if ($isAbstract instanceof TrinaryLogic) {
144+
$isAbstract = $isAbstract->yes();
145+
}
143146
}
144147
}
145148
} else {

tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,4 +709,19 @@ public function testBug9615(): void
709709
]);
710710
}
711711

712+
public function testTraits(): void
713+
{
714+
$errors = [];
715+
if (PHP_VERSION_ID >= 80000) {
716+
$errors = [
717+
[
718+
'Parameter #1 $i (int) of method OverridingTraitMethods\Bar::doBar() is not contravariant with parameter #1 $i (string) of method OverridingTraitMethods\Foo::doBar().',
719+
27,
720+
],
721+
];
722+
}
723+
$this->phpVersionId = PHP_VERSION_ID;
724+
$this->analyse([__DIR__ . '/data/overriding-trait-methods.php'], $errors);
725+
}
726+
712727
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php // lint >= 8.0
2+
3+
namespace OverridingTraitMethods;
4+
5+
trait Foo
6+
{
7+
8+
public function doFoo(string $i): int
9+
{
10+
11+
}
12+
13+
abstract public function doBar(string $i): int;
14+
15+
}
16+
17+
class Bar
18+
{
19+
20+
use Foo;
21+
22+
public function doFoo(int $i): string
23+
{
24+
// ok, trait method not abstract
25+
}
26+
27+
public function doBar(int $i): int
28+
{
29+
// error
30+
}
31+
32+
}

0 commit comments

Comments
 (0)