Skip to content

Commit bcb59fe

Browse files
committed
Utilize Type->isFinal for possible descendants detection
1 parent ca68fad commit bcb59fe

File tree

4 files changed

+23
-39
lines changed

4 files changed

+23
-39
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: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -113,28 +113,21 @@ private function registerMethodCall(
113113
$methodNames = $this->getMethodName($methodCall, $scope);
114114

115115
if ($methodCall instanceof New_) {
116-
if ($methodCall->class instanceof Expr) {
117-
$callerType = $scope->getType($methodCall->class);
118-
$possibleDescendantCall = true;
119-
120-
} elseif ($methodCall->class instanceof Name) {
121-
$callerType = $scope->resolveTypeByName($methodCall->class);
122-
$possibleDescendantCall = $methodCall->class->toString() === 'static';
123-
116+
if ($methodCall->class instanceof Expr || $methodCall->class instanceof Name) {
117+
$callerType = $scope->getType($methodCall);
124118
} else {
125119
return;
126120
}
127121
} else {
128122
$callerType = $scope->getType($methodCall->var);
129-
$possibleDescendantCall = true;
130123
}
131124

132125
foreach ($methodNames as $methodName) {
133-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) {
126+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo()) as $methodRef) {
134127
$this->registerUsage(
135128
new ClassMethodUsage(
136129
$this->usageOriginDetector->detectOrigin($scope),
137-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
130+
$methodRef,
138131
),
139132
$methodCall,
140133
$scope,
@@ -152,19 +145,16 @@ private function registerStaticCall(
152145

153146
if ($staticCall->class instanceof Expr) {
154147
$callerType = $scope->getType($staticCall->class);
155-
$possibleDescendantCall = true;
156-
157148
} else {
158-
$callerType = $scope->resolveTypeByName($staticCall->class);
159-
$possibleDescendantCall = $staticCall->class->toString() === 'static';
149+
$callerType = $scope->resolveTypeByName($staticCall->class); // broken in PHPStan, the type here is marked as NOT final
160150
}
161151

162152
foreach ($methodNames as $methodName) {
163-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createYes()) as $className) {
153+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createYes()) as $methodRef) {
164154
$this->registerUsage(
165155
new ClassMethodUsage(
166156
$this->usageOriginDetector->detectOrigin($scope),
167-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
157+
$methodRef,
168158
),
169159
$staticCall,
170160
$scope,
@@ -186,14 +176,11 @@ private function registerArrayCallable(
186176
$caller = $typeAndName->getType();
187177
$methodName = $typeAndName->getMethod();
188178

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) {
179+
foreach ($this->getDeclaringTypesWithMethod($methodName, $caller, TrinaryLogic::createMaybe()) as $methodRef) {
193180
$this->registerUsage(
194181
new ClassMethodUsage(
195182
$this->usageOriginDetector->detectOrigin($scope),
196-
new ClassMethodRef($className, $methodName, $possibleDescendantCall),
183+
$methodRef,
197184
),
198185
$array,
199186
$scope,
@@ -221,11 +208,11 @@ private function registerClone(Clone_ $node, Scope $scope): void
221208
$methodName = '__clone';
222209
$callerType = $scope->getType($node->expr);
223210

224-
foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) {
211+
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo()) as $methodRef) {
225212
$this->registerUsage(
226213
new ClassMethodUsage(
227214
$this->usageOriginDetector->detectOrigin($scope),
228-
new ClassMethodRef($className, $methodName, true),
215+
$methodRef,
229216
),
230217
$node,
231218
$scope,
@@ -257,12 +244,11 @@ private function getMethodName(CallLike $call, Scope $scope): array
257244
}
258245

259246
/**
260-
* @return list<class-string<object>|null>
247+
* @return list<ClassMethodRef>
261248
*/
262249
private function getDeclaringTypesWithMethod(
263-
Scope $scope,
264-
Type $type,
265250
string $methodName,
251+
Type $type,
266252
TrinaryLogic $isStaticCall
267253
): array
268254
{
@@ -273,15 +259,15 @@ private function getDeclaringTypesWithMethod(
273259
$result = [];
274260

275261
foreach ($classReflections as $classReflection) {
276-
$result[] = $classReflection->getName();
262+
$result[] = new ClassMethodRef($classReflection->getName(), $methodName, !$classReflection->isFinal());
277263
}
278264

279265
if ($this->trackMixedAccess) {
280266
$canBeObjectCall = !$typeNoNull->isObject()->no() && !$isStaticCall->yes();
281267
$canBeClassStringCall = !$typeNoNull->isClassString()->no() && !$isStaticCall->no();
282268

283269
if ($result === [] && ($canBeObjectCall || $canBeClassStringCall)) {
284-
$result[] = null; // call over unknown type
270+
$result[] = new ClassMethodRef(null, $methodName, true); // call over unknown type
285271
}
286272
}
287273

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)