Skip to content

Commit 57f589c

Browse files
authored
Improve descendant detection for const fetches (#176)
1 parent f314ac7 commit 57f589c

File tree

8 files changed

+90
-24
lines changed

8 files changed

+90
-24
lines changed

src/Collector/ConstantFetchCollector.php

+10-13
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private function registerFetch(ClassConstFetch $node, Scope $scope): void
131131
{
132132
if ($node->class instanceof Expr) {
133133
$ownerType = $scope->getType($node->class);
134-
$possibleDescendantFetch = true;
134+
$possibleDescendantFetch = null;
135135
} else {
136136
$ownerType = $scope->resolveTypeByName($node->class);
137137
$possibleDescendantFetch = $node->class->toString() === 'static';
@@ -146,11 +146,11 @@ private function registerFetch(ClassConstFetch $node, Scope $scope): void
146146
continue; // reserved for class name fetching
147147
}
148148

149-
foreach ($this->getDeclaringTypesWithConstant($ownerType, $constantName) as $className) {
149+
foreach ($this->getDeclaringTypesWithConstant($ownerType, $constantName, $possibleDescendantFetch) as $constantRef) {
150150
$this->registerUsage(
151151
new ClassConstantUsage(
152152
UsageOrigin::createRegular($node, $scope),
153-
new ClassConstantRef($className, $constantName, $possibleDescendantFetch),
153+
$constantRef,
154154
),
155155
$node,
156156
$scope,
@@ -160,29 +160,26 @@ private function registerFetch(ClassConstFetch $node, Scope $scope): void
160160
}
161161

162162
/**
163-
* @return list<class-string<object>|null>
163+
* @return list<ClassConstantRef>
164164
*/
165165
private function getDeclaringTypesWithConstant(
166166
Type $type,
167-
string $constantName
167+
string $constantName,
168+
?bool $isPossibleDescendant
168169
): array
169170
{
170-
$typeNormalized = TypeUtils::toBenevolentUnion($type); // extract possible calls even from Class|int
171+
$typeNormalized = TypeUtils::toBenevolentUnion($type); // extract possible fetches even from Class|int
171172
$classReflections = $typeNormalized->getObjectTypeOrClassStringObjectType()->getObjectClassReflections();
172173

173174
$result = [];
174175

175176
foreach ($classReflections as $classReflection) {
176-
if ($classReflection->hasConstant($constantName)) {
177-
$result[] = $classReflection->getConstant($constantName)->getDeclaringClass()->getName();
178-
179-
} else { // unknown constant fetch (might be present on children)
180-
$result[] = $classReflection->getName();
181-
}
177+
$possibleDescendant = $isPossibleDescendant ?? !$classReflection->isFinal();
178+
$result[] = new ClassConstantRef($classReflection->getName(), $constantName, $possibleDescendant);
182179
}
183180

184181
if ($result === []) {
185-
$result[] = null; // call over unknown type
182+
$result[] = new ClassConstantRef(null, $constantName, true); // call over unknown type
186183
}
187184

188185
return $result;

src/Rule/DeadCodeRule.php

+11-6
Original file line numberDiff line numberDiff line change
@@ -427,15 +427,16 @@ private function getAlternativeMemberKeys(ClassMemberRef $member): array
427427
}
428428

429429
private function findDefinerMemberKey(
430-
ClassMemberRef $origin,
430+
ClassMemberRef $memberRef,
431431
string $className,
432432
bool $includeParentLookup = true
433433
): ?string
434434
{
435-
$memberName = $origin->getMemberName();
436-
$memberKey = $origin::buildKey($className, $memberName);
435+
$memberName = $memberRef->getMemberName();
436+
$memberKey = $memberRef::buildKey($className, $memberName);
437+
$memberType = $memberRef->getMemberType();
437438

438-
if ($this->hasMember($className, $memberName, $origin->getMemberType())) {
439+
if ($this->hasMember($className, $memberName, $memberRef->getMemberType())) {
439440
return $memberKey;
440441
}
441442

@@ -447,9 +448,13 @@ private function findDefinerMemberKey(
447448
}
448449

449450
if ($includeParentLookup) {
451+
$parentNames = $memberType === MemberType::CONSTANT
452+
? $this->getAncestorNames($className) // constants can be declared in interfaces
453+
: $this->getParentNames($className);
454+
450455
// search for definition in parents (and its traits)
451-
foreach ($this->getParentNames($className) as $parentName) {
452-
$found = $this->findDefinerMemberKey($origin, $parentName, false);
456+
foreach ($parentNames as $parentName) {
457+
$found = $this->findDefinerMemberKey($memberRef, $parentName, false);
453458

454459
if ($found !== null) {
455460
return $found;

tests/Rule/DeadCodeRuleTest.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -547,12 +547,15 @@ public static function provideFiles(): iterable
547547
// constants
548548
yield 'const-basic' => [__DIR__ . '/data/constants/basic.php'];
549549
yield 'const-function' => [__DIR__ . '/data/constants/constant-function.php'];
550+
yield 'const-descendant-1' => [__DIR__ . '/data/constants/descendant-1.php'];
551+
yield 'const-descendant-2' => [__DIR__ . '/data/constants/descendant-2.php'];
552+
yield 'const-descendant-3' => [__DIR__ . '/data/constants/descendant-3.php'];
553+
yield 'const-descendant-4' => [__DIR__ . '/data/constants/descendant-4.php'];
550554
yield 'const-dynamic' => [__DIR__ . '/data/constants/dynamic.php'];
551555
yield 'const-expr' => [__DIR__ . '/data/constants/expr.php'];
552556
yield 'const-magic' => [__DIR__ . '/data/constants/magic.php'];
553557
yield 'const-mixed' => [__DIR__ . '/data/constants/mixed/tracked.php'];
554558
yield 'const-override' => [__DIR__ . '/data/constants/override.php'];
555-
yield 'const-static' => [__DIR__ . '/data/constants/static.php'];
556559
yield 'const-traits-1' => [__DIR__ . '/data/constants/traits-1.php'];
557560
yield 'const-traits-2' => [__DIR__ . '/data/constants/traits-2.php'];
558561
yield 'const-traits-3' => [__DIR__ . '/data/constants/traits-3.php'];
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace DeadConstDescendant1;
4+
5+
6+
class ParentClass
7+
{
8+
const CONSTANT = 1;
9+
}
10+
11+
class ChildClass extends ParentClass
12+
{
13+
const CONSTANT = 1; // error: Unused DeadConstDescendant1\ChildClass::CONSTANT
14+
}
15+
16+
echo (new ParentClass())::CONSTANT;
17+
18+
// test is similar to tests/Rule/data/methods/parent-call-5.php
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace DeadConstDescendant2;
4+
5+
6+
class ParentClass
7+
{
8+
const CONSTANT = 1;
9+
}
10+
11+
class ChildClass extends ParentClass
12+
{
13+
const CONSTANT = 1;
14+
}
15+
16+
function test(ParentClass $class) {
17+
echo $class::CONSTANT;
18+
}
19+
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace DeadConstDescendant3;
4+
5+
6+
class P {
7+
8+
const CONSTANT = 1;
9+
10+
public function test() {
11+
echo self::CONSTANT;
12+
}
13+
}
14+
15+
class C extends P {
16+
const CONSTANT = 2; // error: Unused DeadConstDescendant3\C::CONSTANT
17+
}
18+
19+
$c = new C();
20+
$c->test(); // prints 1

tests/Rule/data/constants/static.php renamed to tests/Rule/data/constants/descendant-4.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types = 1);
22

3-
namespace DeadConstStatic;
3+
namespace DeadConstDescendant4;
44

55

66
class P {

tests/Rule/data/constants/traits-14.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
namespace DeadTraitConst14;
44

55
trait SomeTrait {
6-
const FOO = 1; // error: Unused DeadTraitConst14\SomeTrait::FOO
6+
const FOO = 1;
77
}
88

99
class ParentClass {
10-
const FOO = 1;
10+
const FOO = 1; // error: Unused DeadTraitConst14\ParentClass::FOO
1111
}
1212

1313
class User extends ParentClass {
1414
use SomeTrait;
1515
}
1616

17-
echo User::FOO; // verify by var_dump((new ReflectionClass('User'))->getReflectionConstants());
17+
echo User::FOO;
18+
19+
// this test does not fully comply with result of var_dump((new ReflectionClass('User'))->getReflectionConstants());
20+
// this behaviour is kept for simplicity as it has equal behaviour with methods (see methods/traits-14.php)
21+
// also, overridden constants are ensured to have the same value
1822

0 commit comments

Comments
 (0)