Skip to content

Commit 89a38bd

Browse files
authored
Move most reflection work to collectors (#47)
1 parent 712aa81 commit 89a38bd

File tree

6 files changed

+114
-110
lines changed

6 files changed

+114
-110
lines changed

src/Collector/ClassDefinitionCollector.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use PHPStan\Node\InClassNode;
99

1010
/**
11-
* @implements Collector<InClassNode, array{string}>
11+
* @implements Collector<InClassNode, array<string, string>>
1212
*/
1313
class ClassDefinitionCollector implements Collector
1414
{
@@ -20,14 +20,25 @@ public function getNodeType(): string
2020

2121
/**
2222
* @param InClassNode $node
23-
* @return array{string}
23+
* @return array<string, string>
2424
*/
2525
public function processNode(
2626
Node $node,
2727
Scope $scope
2828
): array
2929
{
30-
return [$node->getClassReflection()->getName()];
30+
$pairs = [];
31+
$origin = $node->getClassReflection();
32+
33+
foreach ($origin->getAncestors() as $ancestor) {
34+
if ($ancestor->isTrait() || $ancestor === $origin) {
35+
continue;
36+
}
37+
38+
$pairs[$ancestor->getName()] = $origin->getName();
39+
}
40+
41+
return $pairs;
3142
}
3243

3344
}

src/Collector/MethodDefinitionCollector.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use function strpos;
1111

1212
/**
13-
* @implements Collector<InClassNode, list<array{string, int}>>
13+
* @implements Collector<InClassNode, list<array{line: int, methodKey: string, overrides: array<string, string>, traitOrigin: ?string}>>
1414
*/
1515
class MethodDefinitionCollector implements Collector
1616
{
@@ -22,7 +22,7 @@ public function getNodeType(): string
2222

2323
/**
2424
* @param InClassNode $node
25-
* @return list<array{string, int}>|null
25+
* @return list<array{line: int, methodKey: string, overrides: array<string, string>, traitOrigin: ?string}>|null
2626
*/
2727
public function processNode(
2828
Node $node,
@@ -33,6 +33,10 @@ public function processNode(
3333
$nativeReflection = $reflection->getNativeReflection();
3434
$result = [];
3535

36+
if ($reflection->isAnonymous()) {
37+
return null; // https://github.com/phpstan/phpstan/issues/8410
38+
}
39+
3640
foreach ($nativeReflection->getMethods() as $method) {
3741
if ($method->isDestructor()) {
3842
continue;
@@ -64,8 +68,37 @@ public function processNode(
6468
continue;
6569
}
6670

67-
$methodKey = DeadCodeHelper::composeMethodKey($method->getDeclaringClass()->getName(), $method->getName());
68-
$result[] = [$methodKey, $line];
71+
$className = $method->getDeclaringClass()->getName();
72+
$methodName = $method->getName();
73+
$methodKey = DeadCodeHelper::composeMethodKey($className, $methodName);
74+
75+
$declaringTraitMethodKey = DeadCodeHelper::getDeclaringTraitMethodKey($reflection, $methodName);
76+
77+
$methodOverrides = [];
78+
79+
foreach ($reflection->getAncestors() as $ancestor) {
80+
if ($ancestor === $reflection) {
81+
continue;
82+
}
83+
84+
if (!$ancestor->hasMethod($methodName)) {
85+
continue;
86+
}
87+
88+
if ($ancestor->isTrait()) {
89+
continue;
90+
}
91+
92+
$ancestorMethodKey = DeadCodeHelper::composeMethodKey($ancestor->getName(), $methodName);
93+
$methodOverrides[$ancestorMethodKey] = $methodKey;
94+
}
95+
96+
$result[] = [
97+
'line' => $line,
98+
'methodKey' => $methodKey,
99+
'overrides' => $methodOverrides,
100+
'traitOrigin' => $declaringTraitMethodKey,
101+
];
69102
}
70103

71104
return $result !== [] ? $result : null;

src/Reflection/ClassHierarchy.php

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace ShipMonk\PHPStan\DeadCode\Reflection;
44

5-
use PHPStan\Reflection\ClassReflection;
65
use function array_keys;
76

87
class ClassHierarchy
@@ -16,32 +15,48 @@ class ClassHierarchy
1615
private array $classDescendants = [];
1716

1817
/**
19-
* parentMethodKey => childrenMethodKey[] that can mark parent as used
18+
* parentMethodKey => childrenMethodKey[]
2019
*
2120
* @var array<string, list<string>>
2221
*/
2322
private array $methodDescendants = [];
2423

24+
/**
25+
* childrenMethodKey => parentMethodKey[]
26+
*
27+
* @var array<string, list<string>>
28+
*/
29+
private array $methodAncestors = [];
30+
2531
/**
2632
* traitMethodKey => traitUserMethodKey[]
2733
*
2834
* @var array<string, list<string>>
2935
*/
3036
private array $methodTraitUsages = [];
3137

32-
public function registerClassPair(ClassReflection $ancestor, ClassReflection $descendant): void
38+
/**
39+
* traitUserMethodKey => declaringTraitMethodKey
40+
*
41+
* @var array<string, string>
42+
*/
43+
private array $declaringTraits = [];
44+
45+
public function registerClassPair(string $ancestorName, string $descendantName): void
3346
{
34-
$this->classDescendants[$ancestor->getName()][$descendant->getName()] = true;
47+
$this->classDescendants[$ancestorName][$descendantName] = true;
3548
}
3649

3750
public function registerMethodPair(string $ancestorMethodKey, string $descendantMethodKey): void
3851
{
3952
$this->methodDescendants[$ancestorMethodKey][] = $descendantMethodKey;
53+
$this->methodAncestors[$descendantMethodKey][] = $ancestorMethodKey;
4054
}
4155

42-
public function registerMethodTraitUsage(string $declaringTraitMethodKey, string $methodKey): void
56+
public function registerMethodTraitUsage(string $declaringTraitMethodKey, string $traitUsageMethodKey): void
4357
{
44-
$this->methodTraitUsages[$declaringTraitMethodKey][] = $methodKey;
58+
$this->methodTraitUsages[$declaringTraitMethodKey][] = $traitUsageMethodKey;
59+
$this->declaringTraits[$traitUsageMethodKey] = $declaringTraitMethodKey;
4560
}
4661

4762
/**
@@ -62,6 +77,14 @@ public function getMethodDescendants(string $methodKey): array
6277
return $this->methodDescendants[$methodKey] ?? [];
6378
}
6479

80+
/**
81+
* @return list<string>
82+
*/
83+
public function getMethodAncestors(string $methodKey): array
84+
{
85+
return $this->methodAncestors[$methodKey] ?? [];
86+
}
87+
6588
/**
6689
* @return list<string>
6790
*/
@@ -70,4 +93,9 @@ public function getMethodTraitUsages(string $traitMethodKey): array
7093
return $this->methodTraitUsages[$traitMethodKey] ?? [];
7194
}
7295

96+
public function getDeclaringTraitMethodKey(string $methodKey): ?string
97+
{
98+
return $this->declaringTraits[$methodKey] ?? null;
99+
}
100+
73101
}

src/Rule/DeadMethodRule.php

Lines changed: 27 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,32 @@ public function processNode(
7878
$declaredMethods = [];
7979

8080
foreach ($classDeclarationData as $file => $classesInFile) {
81-
foreach ($classesInFile as $classes) {
82-
foreach ($classes as $declaredClassName) {
83-
$this->registerClassToHierarchy($declaredClassName);
81+
foreach ($classesInFile as $classPairs) {
82+
foreach ($classPairs as $ancestor => $descendant) {
83+
$this->classHierarchy->registerClassPair($ancestor, $descendant);
8484
}
8585
}
8686
}
8787

88+
unset($classDeclarationData);
89+
8890
foreach ($methodDeclarationData as $file => $methodsInFile) {
8991
foreach ($methodsInFile as $declared) {
90-
foreach ($declared as [$declaredMethodKey, $line]) {
91-
if ($this->isAnonymousClass($declaredMethodKey)) {
92-
continue;
92+
foreach ($declared as [
93+
'line' => $line,
94+
'methodKey' => $methodKey,
95+
'overrides' => $methodOverrides,
96+
'traitOrigin' => $declaringTraitMethodKey,
97+
]) {
98+
$declaredMethods[$methodKey] = [$file, $line];
99+
100+
if ($declaringTraitMethodKey !== null) {
101+
$this->classHierarchy->registerMethodTraitUsage($declaringTraitMethodKey, $methodKey);
93102
}
94103

95-
$declaredMethods[$declaredMethodKey] = [$file, $line];
96-
97-
$this->fillHierarchy($declaredMethodKey);
104+
foreach ($methodOverrides as $ancestorMethodKey => $descendantMethodKey) {
105+
$this->classHierarchy->registerMethodPair($ancestorMethodKey, $descendantMethodKey);
106+
}
98107
}
99108
}
100109
}
@@ -139,79 +148,16 @@ private function isAnonymousClass(string $methodKey): bool
139148
*/
140149
private function getMethodsToMarkAsUsed(string $methodKey): array
141150
{
142-
$classAndMethod = DeadCodeHelper::splitMethodKey($methodKey);
143-
144-
if (!$this->reflectionProvider->hasClass($classAndMethod->className)) {
145-
return []; // e.g. attributes
146-
}
147-
148-
$reflection = $this->reflectionProvider->getClass($classAndMethod->className);
149-
$traitMethodKey = DeadCodeHelper::getDeclaringTraitMethodKey($reflection, $classAndMethod->methodName);
150-
151-
$result = array_merge(
152-
$this->getDescendantsToMarkAsUsed($methodKey),
153-
$this->getTraitUsersToMarkAsUsed($traitMethodKey),
151+
$traitMethodKey = $this->classHierarchy->getDeclaringTraitMethodKey($methodKey);
152+
153+
return array_merge(
154+
[$methodKey],
155+
$this->classHierarchy->getMethodDescendants($methodKey),
156+
$this->classHierarchy->getMethodAncestors($methodKey),
157+
$traitMethodKey !== null
158+
? $this->classHierarchy->getMethodTraitUsages($traitMethodKey)
159+
: [],
154160
);
155-
156-
foreach ($reflection->getAncestors() as $ancestor) {
157-
if (!$ancestor->hasMethod($classAndMethod->methodName)) {
158-
continue;
159-
}
160-
161-
$ancestorMethodKey = DeadCodeHelper::composeMethodKey($ancestor->getName(), $classAndMethod->methodName);
162-
$result[] = $ancestorMethodKey;
163-
}
164-
165-
return $result;
166-
}
167-
168-
/**
169-
* @return list<string>
170-
*/
171-
private function getTraitUsersToMarkAsUsed(?string $traitMethodKey): array
172-
{
173-
if ($traitMethodKey === null) {
174-
return [];
175-
}
176-
177-
return $this->classHierarchy->getMethodTraitUsages($traitMethodKey);
178-
}
179-
180-
/**
181-
* @return list<string>
182-
*/
183-
private function getDescendantsToMarkAsUsed(string $methodKey): array
184-
{
185-
return $this->classHierarchy->getMethodDescendants($methodKey);
186-
}
187-
188-
private function fillHierarchy(string $methodKey): void
189-
{
190-
$classAndMethod = DeadCodeHelper::splitMethodKey($methodKey);
191-
$reflection = $this->reflectionProvider->getClass($classAndMethod->className);
192-
193-
$declaringTraitMethodKey = DeadCodeHelper::getDeclaringTraitMethodKey($reflection, $classAndMethod->methodName);
194-
195-
if ($declaringTraitMethodKey !== null) {
196-
$this->classHierarchy->registerMethodTraitUsage($declaringTraitMethodKey, $methodKey);
197-
}
198-
199-
foreach ($reflection->getAncestors() as $ancestor) {
200-
if ($ancestor === $reflection) {
201-
continue;
202-
}
203-
204-
if (!$ancestor->hasMethod($classAndMethod->methodName)) {
205-
continue;
206-
}
207-
208-
if ($ancestor->isTrait()) {
209-
continue;
210-
}
211-
212-
$ancestorMethodKey = DeadCodeHelper::composeMethodKey($ancestor->getName(), $classAndMethod->methodName);
213-
$this->classHierarchy->registerMethodPair($ancestorMethodKey, $methodKey);
214-
}
215161
}
216162

217163
private function raiseError(
@@ -264,19 +210,4 @@ private function isEntryPoint(string $methodKey): bool
264210
return false;
265211
}
266212

267-
private function registerClassToHierarchy(string $className): void
268-
{
269-
if ($this->reflectionProvider->hasClass($className)) {
270-
$origin = $this->reflectionProvider->getClass($className);
271-
272-
foreach ($origin->getAncestors() as $ancestor) {
273-
if ($ancestor->isTrait() || $ancestor === $origin) {
274-
continue;
275-
}
276-
277-
$this->classHierarchy->registerClassPair($ancestor, $origin);
278-
}
279-
}
280-
}
281-
282213
}

tests/Rule/data/DeadMethodRule/attribute.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public function __construct(string $name)
1212
}
1313

1414
#[Attr("arg")]
15+
#[Unknown("arg")]
1516
class AttrUser
1617
{
1718

tests/Rule/data/DeadMethodRule/indirect-interface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
interface FooInterface
66
{
7-
public function foo(): void; // error: Unused DeadIndirect\FooInterface::foo
7+
public function foo(): void;
88
}
99

1010
abstract class FooAbstract

0 commit comments

Comments
 (0)