Skip to content

Commit

Permalink
OverridingMethodRule - search for method prototype in traits
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Nov 13, 2023
1 parent 22e21f0 commit 2df14af
Show file tree
Hide file tree
Showing 18 changed files with 158 additions and 3 deletions.
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,9 @@ public function supportsNativeTypesInClassConstants(): bool
return $this->versionId >= 80300;
}

public function supportsAbstractTraitMethods(): bool
{
return $this->versionId >= 80000;
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Annotations/AnnotationMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
11 changes: 11 additions & 0 deletions src/Reflection/Dummy/ChangedTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;
use function is_bool;

class ChangedTypeMethodReflection implements ExtendedMethodReflection
{
Expand Down Expand Up @@ -110,4 +111,14 @@ public function returnsByReference(): TrinaryLogic
return $this->reflection->returnsByReference();
}

public function isAbstract(): TrinaryLogic
{
$abstract = $this->reflection->isAbstract();
if (is_bool($abstract)) {
return TrinaryLogic::createFromBoolean($abstract);
}

return $abstract;
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Dummy/DummyConstructorReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,9 @@ public function isFinalByKeyword(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Dummy/DummyMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
2 changes: 2 additions & 0 deletions src/Reflection/ExtendedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ public function returnsByReference(): TrinaryLogic;

public function isFinalByKeyword(): TrinaryLogic;

public function isAbstract(): TrinaryLogic|bool;

}
4 changes: 2 additions & 2 deletions src/Reflection/Native/NativeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public function isPublic(): bool
return $this->reflection->isPublic();
}

public function isAbstract(): bool
public function isAbstract(): TrinaryLogic
{
return $this->reflection->isAbstract();
return TrinaryLogic::createFromBoolean($this->reflection->isAbstract());
}

public function getPrototype(): ClassMemberReflection
Expand Down
11 changes: 11 additions & 0 deletions src/Reflection/Php/ClosureCallMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PHPStan\Type\Type;
use function array_map;
use function array_unshift;
use function is_bool;

final class ClosureCallMethodReflection implements ExtendedMethodReflection
{
Expand Down Expand Up @@ -152,4 +153,14 @@ public function returnsByReference(): TrinaryLogic
return $this->nativeMethodReflection->returnsByReference();
}

public function isAbstract(): TrinaryLogic
{
$abstract = $this->nativeMethodReflection->isAbstract();
if (is_bool($abstract)) {
return TrinaryLogic::createFromBoolean($abstract);
}

return $abstract;
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Php/EnumCasesMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::createNo();
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Php/PhpMethodFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::createFromBoolean($this->getClassMethod()->returnsByRef());
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->getClassMethod()->isAbstract());
}

}
11 changes: 11 additions & 0 deletions src/Reflection/ResolvedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
use PHPStan\Type\Type;
use function is_bool;

class ResolvedMethodReflection implements ExtendedMethodReflection
{
Expand Down Expand Up @@ -163,4 +164,14 @@ public function returnsByReference(): TrinaryLogic
return $this->reflection->returnsByReference();
}

public function isAbstract(): TrinaryLogic
{
$abstract = $this->reflection->isAbstract();
if (is_bool($abstract)) {
return TrinaryLogic::createFromBoolean($abstract);
}

return $abstract;
}

}
6 changes: 6 additions & 0 deletions src/Reflection/Type/IntersectionTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use function array_map;
use function count;
use function implode;
use function is_bool;

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

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract());
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Type/UnionTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->returnsByReference());
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract());
}

}
5 changes: 5 additions & 0 deletions src/Reflection/WrappedExtendedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,9 @@ public function returnsByReference(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function isAbstract(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
26 changes: 25 additions & 1 deletion src/Rules/Methods/OverridingMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PHPStan\Type\VerbosityLevel;
use function array_merge;
use function count;
use function is_bool;
use function sprintf;
use function strtolower;

Expand Down Expand Up @@ -274,6 +275,24 @@ private function findPrototype(ClassReflection $classReflection, string $methodN
}
}

if ($this->phpVersion->supportsAbstractTraitMethods()) {
foreach ($classReflection->getTraits(true) as $trait) {
if (!$trait->hasNativeMethod($methodName)) {
continue;
}

$method = $trait->getNativeMethod($methodName);
$isAbstract = $method->isAbstract();
if (is_bool($isAbstract)) {
if ($isAbstract) {
return $method;
}
} elseif ($isAbstract->yes()) {
return $method;
}
}
}

$parentClass = $classReflection->getParentClass();
if ($parentClass === null) {
return null;
Expand All @@ -293,7 +312,12 @@ private function findPrototype(ClassReflection $classReflection, string $methodN
if ($method->getName() === $declaringClass->getConstructor()->getName()) {
$prototype = $method->getPrototype();
if ($prototype instanceof PhpMethodReflection || $prototype instanceof MethodPrototypeReflection || $prototype instanceof NativeMethodReflection) {
if (!$prototype->isAbstract()) {
$abstract = $prototype->isAbstract();
if (is_bool($abstract)) {
if (!$abstract) {
return null;
}
} elseif (!$abstract->yes()) {
return null;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/Rules/Methods/StaticMethodCallCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ public function check(
$nativeMethodReflection = $classReflection->getNativeMethod($methodName);
if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) {
$isAbstract = $nativeMethodReflection->isAbstract();
if ($isAbstract instanceof TrinaryLogic) {
$isAbstract = $isAbstract->yes();
}
}
}
} else {
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -709,4 +709,19 @@ public function testBug9615(): void
]);
}

public function testTraits(): void
{
$errors = [];
if (PHP_VERSION_ID >= 80000) {
$errors = [
[
'Parameter #1 $i (int) of method OverridingTraitMethods\Bar::doBar() is not contravariant with parameter #1 $i (string) of method OverridingTraitMethods\Foo::doBar().',
27,
],
];
}
$this->phpVersionId = PHP_VERSION_ID;
$this->analyse([__DIR__ . '/data/overriding-trait-methods.php'], $errors);
}

}
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Methods/data/overriding-trait-methods.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php // lint >= 8.0

namespace OverridingTraitMethods;

trait Foo
{

public function doFoo(string $i): int
{

}

abstract public function doBar(string $i): int;

}

class Bar
{

use Foo;

public function doFoo(int $i): string
{
// ok, trait method not abstract
}

public function doBar(int $i): int
{
// error
}

}

0 comments on commit 2df14af

Please sign in to comment.