Skip to content

Commit ee33f1e

Browse files
committed
CallToMethodStatementWithoutImpurePointsRule - fix for methods called on unions
1 parent 9340249 commit ee33f1e

File tree

4 files changed

+84
-27
lines changed

4 files changed

+84
-27
lines changed

src/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRule.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,22 @@ public function processNode(Node $node, Scope $scope): array
3838

3939
$errors = [];
4040
foreach ($node->get(PossiblyPureMethodCallCollector::class) as $filePath => $data) {
41-
foreach ($data as [$className, $method, $line]) {
42-
$className = strtolower($className);
41+
foreach ($data as [$classNames, $method, $line]) {
42+
$originalMethodName = null;
43+
foreach ($classNames as $className) {
44+
$className = strtolower($className);
4345

44-
if (!array_key_exists($className, $methods)) {
45-
continue;
46-
}
46+
if (!array_key_exists($className, $methods)) {
47+
continue 2;
48+
}
4749

48-
$lowerMethod = strtolower($method);
49-
if (!array_key_exists($lowerMethod, $methods[$className])) {
50-
continue;
51-
}
50+
$lowerMethod = strtolower($method);
51+
if (!array_key_exists($lowerMethod, $methods[$className])) {
52+
continue 2;
53+
}
5254

53-
$originalMethodName = $methods[$className][$lowerMethod];
55+
$originalMethodName = $methods[$className][$lowerMethod];
56+
}
5457

5558
$errors[] = RuleErrorBuilder::message(sprintf(
5659
'Call to method %s() on a separate line has no effect.',

src/Rules/DeadCode/PossiblyPureMethodCallCollector.php

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
use PhpParser\Node\Stmt\Expression;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Collectors\Collector;
9-
use function count;
109

1110
/**
12-
* @implements Collector<Node\Stmt\Expression, array{class-string, string, int}>
11+
* @implements Collector<Node\Stmt\Expression, array{non-empty-list<class-string>, string, int}>
1312
*/
1413
class PossiblyPureMethodCallCollector implements Collector
1514
{
@@ -34,32 +33,42 @@ public function processNode(Node $node, Scope $scope)
3433

3534
$methodName = $node->expr->name->toString();
3635
$calledOnType = $scope->getType($node->expr->var);
37-
$methodReflection = $scope->getMethodReflection($calledOnType, $methodName);
38-
if ($methodReflection === null) {
36+
if (!$calledOnType->hasMethod($methodName)->yes()) {
3937
return null;
4038
}
41-
if (
42-
!$methodReflection->isPrivate()
43-
&& !$methodReflection->isFinal()->yes()
44-
&& !$methodReflection->getDeclaringClass()->isFinal()
45-
) {
46-
$typeClassReflections = $calledOnType->getObjectClassReflections();
47-
if (count($typeClassReflections) !== 1) {
39+
40+
$classNames = [];
41+
$methodReflection = null;
42+
foreach ($calledOnType->getObjectClassReflections() as $classReflection) {
43+
if (!$classReflection->hasMethod($methodName)) {
4844
return null;
4945
}
5046

51-
if (!$typeClassReflections[0]->isFinal()) {
47+
$methodReflection = $classReflection->getMethod($methodName, $scope);
48+
if (
49+
!$methodReflection->isPrivate()
50+
&& !$methodReflection->isFinal()->yes()
51+
&& !$methodReflection->getDeclaringClass()->isFinal()
52+
) {
53+
if (!$classReflection->isFinal()) {
54+
return null;
55+
}
56+
}
57+
if (!$methodReflection->isPure()->maybe()) {
5258
return null;
5359
}
60+
if (!$methodReflection->hasSideEffects()->maybe()) {
61+
return null;
62+
}
63+
64+
$classNames[] = $methodReflection->getDeclaringClass()->getName();
5465
}
55-
if (!$methodReflection->isPure()->maybe()) {
56-
return null;
57-
}
58-
if (!$methodReflection->hasSideEffects()->maybe()) {
66+
67+
if ($methodReflection === null) {
5968
return null;
6069
}
6170

62-
return [$methodReflection->getDeclaringClass()->getName(), $methodReflection->getName(), $node->getStartLine()];
71+
return [$classNames, $methodReflection->getName(), $node->getStartLine()];
6372
}
6473

6574
}

tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ public function testRule(): void
5858
]);
5959
}
6060

61+
public function testBug11011(): void
62+
{
63+
$this->analyse([__DIR__ . '/data/bug-11011.php'], [
64+
[
65+
'Call to method Bug11011\AnotherPureImpl::doFoo() on a separate line has no effect.',
66+
32,
67+
],
68+
]);
69+
}
70+
6171
protected function getCollectors(): array
6272
{
6373
return [
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Bug11011;
4+
5+
final class ImpureImpl {
6+
/** @phpstan-impure */
7+
public function doFoo() {
8+
echo "yes";
9+
$_SESSION['ab'] = 1;
10+
}
11+
}
12+
13+
final class PureImpl {
14+
public function doFoo(): bool {
15+
return true;
16+
}
17+
}
18+
19+
final class AnotherPureImpl {
20+
public function doFoo(): bool {
21+
return true;
22+
}
23+
}
24+
25+
class User {
26+
function doBar(PureImpl|ImpureImpl $f): bool {
27+
$f->doFoo();
28+
return true;
29+
}
30+
31+
function doBar2(PureImpl|AnotherPureImpl $f): bool {
32+
$f->doFoo();
33+
return true;
34+
}
35+
}

0 commit comments

Comments
 (0)