33namespace PHPStan \Rules \Functions ;
44
55use PhpParser \Node ;
6- use PhpParser \Node \Arg ;
76use PHPStan \Analyser \Scope ;
87use PHPStan \DependencyInjection \RegisteredRule ;
8+ use PHPStan \Node \NoopExpressionNode ;
99use PHPStan \Reflection \ReflectionProvider ;
1010use PHPStan \Rules \Rule ;
1111use PHPStan \Rules \RuleErrorBuilder ;
1212use PHPStan \Type \NeverType ;
13- use PHPStan \Type \Type ;
1413use function count ;
1514use function in_array ;
1615use function sprintf ;
1716
1817/**
19- * @implements Rule<Node\Stmt\Expression >
18+ * @implements Rule<NoopExpressionNode >
2019 */
2120#[RegisteredRule(level: 4 )]
2221final class CallToFunctionStatementWithoutSideEffectsRule implements Rule
2322{
2423
25- private const SIDE_EFFECT_FLIP_PARAMETERS = [
26- // functionName => [name, pos, testName]
27- 'print_r ' => ['return ' , 1 , 'isTruthy ' ],
28- 'var_export ' => ['return ' , 1 , 'isTruthy ' ],
29- 'highlight_string ' => ['return ' , 1 , 'isTruthy ' ],
30-
31- ];
32-
3324 public const PHPSTAN_TESTING_FUNCTIONS = [
3425 'PHPStan \\dumpNativeType ' ,
3526 'PHPStan \\dumpType ' ,
@@ -47,16 +38,20 @@ public function __construct(private ReflectionProvider $reflectionProvider)
4738
4839 public function getNodeType (): string
4940 {
50- return Node \ Stmt \Expression ::class;
41+ return NoopExpressionNode ::class;
5142 }
5243
5344 public function processNode (Node $ node , Scope $ scope ): array
5445 {
55- if (!$ node ->expr instanceof Node \Expr \FuncCall) {
46+ $ expr = $ node ->getOriginalExpr ();
47+ if ($ expr instanceof Node \Expr \BinaryOp \Pipe) {
48+ $ expr = $ expr ->right ;
49+ }
50+ if (!$ expr instanceof Node \Expr \FuncCall) {
5651 return [];
5752 }
5853
59- $ funcCall = $ node -> expr ;
54+ $ funcCall = $ expr ;
6055 if (!($ funcCall ->name instanceof Node \Name)) {
6156 return [];
6257 }
@@ -71,79 +66,21 @@ public function processNode(Node $node, Scope $scope): array
7166 }
7267
7368 $ functionName = $ function ->getName ();
74- $ functionHasSideEffects = !$ function ->hasSideEffects ()->no ();
75-
7669 if (in_array ($ functionName , self ::PHPSTAN_TESTING_FUNCTIONS , true )) {
7770 return [];
7871 }
7972
80- if (isset (self ::SIDE_EFFECT_FLIP_PARAMETERS [$ functionName ])) {
81- [
82- $ flipParameterName ,
83- $ flipParameterPosition ,
84- $ testName ,
85- ] = self ::SIDE_EFFECT_FLIP_PARAMETERS [$ functionName ];
86-
87- $ sideEffectFlipped = false ;
88- $ hasNamedParameter = false ;
89- $ checker = [
90- 'isNotNull ' => static fn (Type $ type ) => $ type ->isNull ()->no (),
91- 'isTruthy ' => static fn (Type $ type ) => $ type ->toBoolean ()->isTrue ()->yes (),
92- ][$ testName ];
93-
94- foreach ($ funcCall ->getRawArgs () as $ i => $ arg ) {
95- if (!$ arg instanceof Arg) {
96- return [];
97- }
98-
99- $ isFlipParameter = false ;
100-
101- if ($ arg ->name !== null ) {
102- $ hasNamedParameter = true ;
103- if ($ arg ->name ->name === $ flipParameterName ) {
104- $ isFlipParameter = true ;
105- }
106- }
107-
108- if (!$ hasNamedParameter && $ i === $ flipParameterPosition ) {
109- $ isFlipParameter = true ;
110- }
111-
112- if ($ isFlipParameter ) {
113- $ sideEffectFlipped = $ checker ($ scope ->getType ($ arg ->value ));
114- break ;
115- }
116- }
117-
118- if (!$ sideEffectFlipped ) {
119- return [];
120- }
121-
122- $ functionHasSideEffects = false ;
123- }
124-
125- if (!$ functionHasSideEffects || $ node ->expr ->isFirstClassCallable ()) {
126- if (!$ node ->expr ->isFirstClassCallable ()) {
127- $ throwsType = $ function ->getThrowType ();
128- if ($ throwsType !== null && !$ throwsType ->isVoid ()->yes ()) {
129- return [];
130- }
131- }
132-
133- $ functionResult = $ scope ->getType ($ funcCall );
134- if ($ functionResult instanceof NeverType && $ functionResult ->isExplicit ()) {
135- return [];
136- }
137-
138- return [
139- RuleErrorBuilder::message (sprintf (
140- 'Call to function %s() on a separate line has no effect. ' ,
141- $ function ->getName (),
142- ))->identifier ('function.resultUnused ' )->build (),
143- ];
73+ $ functionResult = $ scope ->getType ($ funcCall );
74+ if ($ functionResult instanceof NeverType && $ functionResult ->isExplicit ()) {
75+ return [];
14476 }
14577
146- return [];
78+ return [
79+ RuleErrorBuilder::message (sprintf (
80+ 'Call to function %s() on a separate line has no effect. ' ,
81+ $ function ->getName (),
82+ ))->identifier ('function.resultUnused ' )->build (),
83+ ];
14784 }
14885
14986}
0 commit comments