Skip to content

Commit 099b87a

Browse files
committed
Rules about #[NoDiscard] report when call is in (void) cast but should not
1 parent d00b769 commit 099b87a

9 files changed

+148
-21
lines changed

src/Rules/Functions/CallToFunctionStatementWithNoDiscardRule.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,46 @@ public function getNodeType(): string
3434

3535
public function processNode(Node $node, Scope $scope): array
3636
{
37-
if (!$node->expr instanceof Node\Expr\FuncCall) {
37+
$funcCall = $node->expr;
38+
$isInVoidCast = false;
39+
if ($funcCall instanceof Node\Expr\Cast\Void_) {
40+
$isInVoidCast = true;
41+
$funcCall = $funcCall->expr;
42+
}
43+
44+
if (!$funcCall instanceof Node\Expr\FuncCall) {
3845
return [];
3946
}
4047

41-
if ($node->expr->isFirstClassCallable()) {
48+
if ($funcCall->isFirstClassCallable()) {
4249
return [];
4350
}
4451

4552
if (!$this->phpVersion->supportsNoDiscardAttribute()) {
4653
return [];
4754
}
4855

49-
$funcCall = $node->expr;
5056
if ($funcCall->name instanceof Node\Name) {
5157
if (!$this->reflectionProvider->hasFunction($funcCall->name, $scope)) {
5258
return [];
5359
}
5460

5561
$function = $this->reflectionProvider->getFunction($funcCall->name, $scope);
56-
if (!$function->mustUseReturnValue()->yes()) {
62+
$mustUseReturnValue = $function->mustUseReturnValue();
63+
if ($isInVoidCast) {
64+
if ($mustUseReturnValue->no()) {
65+
return [
66+
RuleErrorBuilder::message(sprintf(
67+
'Call to function %s() in (void) cast but function allows discarding return value.',
68+
$function->getName(),
69+
))->identifier('function.inVoidCast')->build(),
70+
];
71+
}
72+
73+
return [];
74+
}
75+
76+
if (!$mustUseReturnValue->yes()) {
5777
return [];
5878
}
5979

@@ -75,6 +95,19 @@ public function processNode(Node $node, Scope $scope): array
7595
$mustUseReturnValue = $mustUseReturnValue->or($callableParametersAcceptor->mustUseReturnValue());
7696
}
7797

98+
if ($isInVoidCast) {
99+
if ($mustUseReturnValue->no()) {
100+
return [
101+
RuleErrorBuilder::message(sprintf(
102+
'Call to callable %s in (void) cast but callable allows discarding return value.',
103+
$callableType->describe(VerbosityLevel::value()),
104+
))->identifier('callable.inVoidCast')->build(),
105+
];
106+
}
107+
108+
return [];
109+
}
110+
78111
if (!$mustUseReturnValue->yes()) {
79112
return [];
80113
}

src/Rules/Methods/CallToMethodStatementWithNoDiscardRule.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,35 @@ public function getNodeType(): string
3535

3636
public function processNode(Node $node, Scope $scope): array
3737
{
38-
if (!$node->expr instanceof Node\Expr\MethodCall
39-
&& !$node->expr instanceof Node\Expr\NullsafeMethodCall
38+
$methodCall = $node->expr;
39+
$isInVoidCast = false;
40+
if ($methodCall instanceof Node\Expr\Cast\Void_) {
41+
$isInVoidCast = true;
42+
$methodCall = $methodCall->expr;
43+
}
44+
45+
if (!$methodCall instanceof Node\Expr\MethodCall
46+
&& !$methodCall instanceof Node\Expr\NullsafeMethodCall
4047
) {
4148
return [];
4249
}
4350

44-
if ($node->expr->isFirstClassCallable()) {
51+
if ($methodCall->isFirstClassCallable()) {
4552
return [];
4653
}
4754

4855
if (!$this->phpVersion->supportsNoDiscardAttribute()) {
4956
return [];
5057
}
5158

52-
$funcCall = $node->expr;
53-
if (!$funcCall->name instanceof Node\Identifier) {
59+
if (!$methodCall->name instanceof Node\Identifier) {
5460
return [];
5561
}
56-
$methodName = $funcCall->name->toString();
62+
$methodName = $methodCall->name->toString();
5763

5864
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
5965
$scope,
60-
NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $funcCall->var),
66+
NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $methodCall->var),
6167
'',
6268
static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(),
6369
);
@@ -74,8 +80,23 @@ public function processNode(Node $node, Scope $scope): array
7480
}
7581

7682
$method = $calledOnType->getMethod($methodName, $scope);
83+
$mustUseReturnValue = $method->mustUseReturnValue();
84+
if ($isInVoidCast) {
85+
if ($mustUseReturnValue->no()) {
86+
return [
87+
RuleErrorBuilder::message(sprintf(
88+
'Call to %s %s::%s() in (void) cast but method allows discarding return value.',
89+
$method->isStatic() ? 'static method' : 'method',
90+
$method->getDeclaringClass()->getDisplayName(),
91+
$method->getName(),
92+
))->identifier('method.inVoidCast')->build(),
93+
];
94+
}
95+
96+
return [];
97+
}
7798

78-
if (!$method->mustUseReturnValue()->yes()) {
99+
if (!$mustUseReturnValue->yes()) {
79100
return [];
80101
}
81102

src/Rules/Methods/CallToStaticMethodStatementWithNoDiscardRule.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,31 @@ public function getNodeType(): string
3838

3939
public function processNode(Node $node, Scope $scope): array
4040
{
41-
if (!$node->expr instanceof Node\Expr\StaticCall) {
41+
$methodCall = $node->expr;
42+
$isInVoidCast = false;
43+
if ($methodCall instanceof Node\Expr\Cast\Void_) {
44+
$isInVoidCast = true;
45+
$methodCall = $methodCall->expr;
46+
}
47+
if (!$methodCall instanceof Node\Expr\StaticCall) {
4248
return [];
4349
}
4450

45-
if ($node->expr->isFirstClassCallable()) {
51+
if ($methodCall->isFirstClassCallable()) {
4652
return [];
4753
}
4854

4955
if (!$this->phpVersion->supportsNoDiscardAttribute()) {
5056
return [];
5157
}
5258

53-
$funcCall = $node->expr;
54-
if (!$funcCall->name instanceof Node\Identifier) {
59+
if (!$methodCall->name instanceof Node\Identifier) {
5560
return [];
5661
}
5762

58-
$methodName = $funcCall->name->toString();
59-
if ($funcCall->class instanceof Node\Name) {
60-
$className = $scope->resolveName($funcCall->class);
63+
$methodName = $methodCall->name->toString();
64+
if ($methodCall->class instanceof Node\Name) {
65+
$className = $scope->resolveName($methodCall->class);
6166
if (!$this->reflectionProvider->hasClass($className)) {
6267
return [];
6368
}
@@ -66,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array
6671
} else {
6772
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
6873
$scope,
69-
NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $funcCall->class),
74+
NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $methodCall->class),
7075
'',
7176
static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(),
7277
);
@@ -85,8 +90,23 @@ public function processNode(Node $node, Scope $scope): array
8590
}
8691

8792
$method = $calledOnType->getMethod($methodName, $scope);
93+
$mustUseReturnValue = $method->mustUseReturnValue();
94+
if ($isInVoidCast) {
95+
if ($mustUseReturnValue->no()) {
96+
return [
97+
RuleErrorBuilder::message(sprintf(
98+
'Call to %s %s::%s() in (void) cast but method allows discarding return value.',
99+
$method->isStatic() ? 'static method' : 'method',
100+
$method->getDeclaringClass()->getDisplayName(),
101+
$method->getName(),
102+
))->identifier('staticMethod.inVoidCast')->build(),
103+
];
104+
}
105+
106+
return [];
107+
}
88108

89-
if (!$method->mustUseReturnValue()->yes()) {
109+
if (!$mustUseReturnValue->yes()) {
90110
return [];
91111
}
92112

tests/PHPStan/Rules/Functions/CallToFunctionStatementWithNoDiscardRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ public function testRule(): void
4747
'Call to callable Closure(): 1 on a separate line discards return value.',
4848
45,
4949
],
50+
[
51+
'Call to function FunctionCallStatementResultDiscarded\canDiscard() in (void) cast but function allows discarding return value.',
52+
55,
53+
],
54+
[
55+
'Call to callable \'FunctionCallStateme…\' in (void) cast but callable allows discarding return value.',
56+
59,
57+
],
5058
]);
5159
}
5260

tests/PHPStan/Rules/Functions/data/function-call-statement-result-discarded.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,15 @@ function differentCase(): array {
4545
$arrowWithNoDiscard();
4646

4747
withSideEffects(...);
48+
49+
function canDiscard(): int
50+
{
51+
return 1;
52+
}
53+
54+
canDiscard();
55+
(void) canDiscard();
56+
57+
$canDiscardCb = 'FunctionCallStatementResultDiscarded\\canDiscard';
58+
$canDiscardCb();
59+
(void) $canDiscardCb();

tests/PHPStan/Rules/Methods/CallToMethodStatementWithNoDiscardRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public function testRule(): void
3636
'Call to method MethodCallStatementResultDiscarded\ClassWithInstanceSideEffects::differentCase() on a separate line discards return value.',
3737
30,
3838
],
39+
[
40+
'Call to method MethodCallStatementResultDiscarded\Foo::canDiscard() in (void) cast but method allows discarding return value.',
41+
45,
42+
],
3943
]);
4044
}
4145

tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithNoDiscardRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public function testRule(): void
3737
'Call to static method MethodCallStatementResultDiscarded\ClassWithStaticSideEffects::differentCase() on a separate line discards return value.',
3838
27,
3939
],
40+
[
41+
'Call to static method MethodCallStatementResultDiscarded\Foo::canDiscard() in (void) cast but method allows discarding return value.',
42+
41,
43+
],
4044
]);
4145
}
4246

tests/PHPStan/Rules/Methods/data/method-call-statement-result-discarded.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,16 @@ public function differentCase(): array {
3030
$o->differentCase();
3131

3232
$o->instanceMethod(...);
33+
34+
class Foo
35+
{
36+
37+
public function canDiscard(): array {
38+
return [];
39+
}
40+
41+
}
42+
43+
$foo = new Foo();
44+
$foo->canDiscard();
45+
(void) $foo->canDiscard();

tests/PHPStan/Rules/Methods/data/static-method-call-statement-result-discarded.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ public static function differentCase(): array {
2727
ClassWithStaticSideEffects::differentCase();
2828

2929
ClassWithStaticSideEffects::staticMethod(...);
30+
31+
class Foo
32+
{
33+
34+
public static function canDiscard(): array {
35+
return [];
36+
}
37+
38+
}
39+
40+
Foo::canDiscard();
41+
(void) Foo::canDiscard();

0 commit comments

Comments
 (0)