Skip to content

Commit f37593e

Browse files
authored
Utilize Type->isFinal for possible descendants detection (#151)
1 parent ca68fad commit f37593e

File tree

6 files changed

+34
-31
lines changed

6 files changed

+34
-31
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"require": {
1515
"php": "^7.4 || ^8.0",
16-
"phpstan/phpstan": "^2.0"
16+
"phpstan/phpstan": "^2.1.7"
1717
},
1818
"require-dev": {
1919
"doctrine/orm": "^2.19 || ^3.0",

composer.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Collector/MethodCallCollector.php

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ private function registerMethodCall(
114114

115115
if ($methodCall instanceof New_) {
116116
if ($methodCall->class instanceof Expr) {
117-
$callerType = $scope->getType($methodCall->class);
118-
$possibleDescendantCall = true;
117+
$callerType = $scope->getType($methodCall);
118+
$possibleDescendantCall = null;
119119

120120
} elseif ($methodCall->class instanceof Name) {
121121
$callerType = $scope->resolveTypeByName($methodCall->class);
@@ -126,15 +126,15 @@ private function registerMethodCall(
126126
}
127127
} else {
128128
$callerType = $scope->getType($methodCall->var);
129-
$possibleDescendantCall = true;
129+
$possibleDescendantCall = null;
130130
}
131131

132132
foreach ($methodNames as $methodName) {
133-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) {
133+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo(), $possibleDescendantCall) as $methodRef) {
134134
$this->registerUsage(
135135
new ClassMethodUsage(
136136
$this->usageOriginDetector->detectOrigin($scope),
137-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
137+
$methodRef,
138138
),
139139
$methodCall,
140140
$scope,
@@ -152,19 +152,19 @@ private function registerStaticCall(
152152

153153
if ($staticCall->class instanceof Expr) {
154154
$callerType = $scope->getType($staticCall->class);
155-
$possibleDescendantCall = true;
155+
$possibleDescendantCall = null;
156156

157157
} else {
158158
$callerType = $scope->resolveTypeByName($staticCall->class);
159159
$possibleDescendantCall = $staticCall->class->toString() === 'static';
160160
}
161161

162162
foreach ($methodNames as $methodName) {
163-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createYes()) as $className) {
163+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createYes(), $possibleDescendantCall) as $methodRef) {
164164
$this->registerUsage(
165165
new ClassMethodUsage(
166166
$this->usageOriginDetector->detectOrigin($scope),
167-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
167+
$methodRef,
168168
),
169169
$staticCall,
170170
$scope,
@@ -186,14 +186,11 @@ private function registerArrayCallable(
186186
$caller = $typeAndName->getType();
187187
$methodName = $typeAndName->getMethod();
188188

189-
// currently always true, see https://github.com/phpstan/phpstan-src/pull/3372
190-
$possibleDescendantCall = !$caller->isClassString()->yes();
191-
192-
foreach ($this->getDeclaringTypesWithMethod($scope, $caller, $methodName, TrinaryLogic::createMaybe()) as $className) {
189+
foreach ($this->getDeclaringTypesWithMethod($methodName, $caller, TrinaryLogic::createMaybe()) as $methodRef) {
193190
$this->registerUsage(
194191
new ClassMethodUsage(
195192
$this->usageOriginDetector->detectOrigin($scope),
196-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
193+
$methodRef,
197194
),
198195
$array,
199196
$scope,
@@ -221,11 +218,11 @@ private function registerClone(Clone_ $node, Scope $scope): void
221218
$methodName = '__clone';
222219
$callerType = $scope->getType($node->expr);
223220

224-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) {
221+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo()) as $methodRef) {
225222
$this->registerUsage(
226223
new ClassMethodUsage(
227224
$this->usageOriginDetector->detectOrigin($scope),
228-
new ClassMethodRef($className, $methodName, true),
225+
$methodRef,
229226
),
230227
$node,
231228
$scope,
@@ -257,13 +254,13 @@ private function getMethodName(CallLike $call, Scope $scope): array
257254
}
258255

259256
/**
260-
* @return list<class-string<object>|null>
257+
* @return list<ClassMethodRef>
261258
*/
262259
private function getDeclaringTypesWithMethod(
263-
Scope $scope,
264-
Type $type,
265260
string $methodName,
266-
TrinaryLogic $isStaticCall
261+
Type $type,
262+
TrinaryLogic $isStaticCall,
263+
?bool $isPossibleDescendant = null
267264
): array
268265
{
269266
$typeNoNull = TypeCombinator::removeNull($type); // remove null to support nullsafe calls
@@ -273,15 +270,16 @@ private function getDeclaringTypesWithMethod(
273270
$result = [];
274271

275272
foreach ($classReflections as $classReflection) {
276-
$result[] = $classReflection->getName();
273+
$possibleDescendant = $isPossibleDescendant ?? !$classReflection->isFinal();
274+
$result[] = new ClassMethodRef($classReflection->getName(), $methodName, $possibleDescendant);
277275
}
278276

279277
if ($this->trackMixedAccess) {
280278
$canBeObjectCall = !$typeNoNull->isObject()->no() && !$isStaticCall->yes();
281279
$canBeClassStringCall = !$typeNoNull->isClassString()->no() && !$isStaticCall->no();
282280

283281
if ($result === [] && ($canBeObjectCall || $canBeClassStringCall)) {
284-
$result[] = null; // call over unknown type
282+
$result[] = new ClassMethodRef(null, $methodName, true); // call over unknown type
285283
}
286284
}
287285

tests/Rule/data/methods/array-map-1.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public function __construct()
1212
[$this, 'method4'];
1313
[self::class, 'method5'];
1414
['static', 'method6']; // https://github.com/phpstan/phpstan/issues/11594
15+
[new self(), 'method7'];
1516
}
1617

1718
public function method1(string $foo): void {} // error: Unused DeadMap\ArrayMapTest::method1
@@ -20,6 +21,7 @@ public function method3(): void {}
2021
public function method4(): void {}
2122
public static function method5(): void {}
2223
public static function method6(): void {} // error: Unused DeadMap\ArrayMapTest::method6
24+
public function method7(): void {}
2325
}
2426

2527
class Child extends ArrayMapTest {
@@ -29,6 +31,7 @@ public function method3(): void {}
2931
public function method4(): void {}
3032
public static function method5(): void {} // should be reported (https://github.com/phpstan/phpstan-src/pull/3372)
3133
public static function method6(): void {} // error: Unused DeadMap\Child::method6
34+
public function method7(): void {} // error: Unused DeadMap\Child::method7
3235

3336
}
3437

tests/Rule/data/methods/clone.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ class CloneClass1 {
66
public function __clone() {}
77
}
88

9+
class CloneClassChild extends CloneClass1 {
10+
public function __clone() {} // error: Unused DeadClone\CloneClassChild::__clone
11+
}
12+
913
class CloneClass2 {
1014
public function __clone() {} // error: Unused DeadClone\CloneClass2::__clone
1115
}

tests/Rule/data/methods/parent-call-5.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ public function method() {}
1010

1111
class ChildClass extends ParentClass
1212
{
13-
public function method() {}
13+
public function method() {} // error: Unused ParentCall5\ChildClass::method
1414
}
1515

16-
// this cannot be call over descendant, but there is no such info in PHPStan's ObjectType
17-
// ideally, ChildClass::method() should be marked as dead, but it is currently impossible
1816
(new ParentClass())->method();

0 commit comments

Comments
 (0)