diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index b0d522f439..0bc36dff83 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -8,7 +8,10 @@ use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionCallParametersCheck; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; +use PHPStan\Type\Constant\ConstantStringType; +use function array_map; use function array_merge; /** @@ -31,12 +34,33 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->name instanceof Node\Identifier) { - return []; + if ($node->name instanceof Node\Identifier) { + $methodNames = [$node->name->name]; + } else { + $methodNames = array_map( + static fn (ConstantStringType $constantString): string => $constantString->getValue(), + $scope->getType($node->name)->getConstantStrings(), + ); + if ($methodNames === []) { + return []; + } } - $methodName = $node->name->name; + foreach ($methodNames as $methodName) { + $errors = $this->processMethod($methodName, $node, $scope); + if ($errors !== []) { + return $errors; + } + } + + return []; + } + /** + * @return list + */ + private function processMethod(string $methodName, MethodCall $node, Scope $scope): array + { [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodName, $node->var); if ($methodReflection === null) { return $errors; diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 0f98eca2d9..b2ed84b209 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -8,7 +8,10 @@ use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionCallParametersCheck; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; +use PHPStan\Type\Constant\ConstantStringType; +use function array_map; use function array_merge; use function sprintf; @@ -32,11 +35,33 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->name instanceof Node\Identifier) { - return []; + if ($node->name instanceof Node\Identifier) { + $methodNames = [$node->name->name]; + } else { + $methodNames = array_map( + static fn (ConstantStringType $constantString): string => $constantString->getValue(), + $scope->getType($node->name)->getConstantStrings(), + ); + if ($methodNames === []) { + return []; + } } - $methodName = $node->name->name; + foreach ($methodNames as $methodName) { + $errors = $this->processMethod($methodName, $node, $scope); + if ($errors !== []) { + return $errors; + } + } + + return []; + } + + /** + * @return list + */ + private function processMethod(string $methodName, StaticCall $node, Scope $scope): array + { [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class); if ($method === null) { return $errors; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 7342c880fe..eb39bbe703 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3412,4 +3412,26 @@ public function testBug1953(): void ]); } + public function testBug2920(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-2920.php'], [ + [ + 'Call to an undefined method Bug2920MethodCall\HelloWorld::a().', + 13, + ], + [ + 'Parameter #1 $s of method Bug2920MethodCall\HelloWorld::b() expects string, int given.', + 14, + ], + [ + 'Parameter #1 $s of method Bug2920MethodCall\HelloWorld::b() expects string, int given.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index c3842d461b..2bb7db8822 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -852,4 +852,26 @@ public function testBug10872(): void $this->analyse([__DIR__ . '/data/bug-10872.php'], []); } + public function testBug2920(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-2920b.php'], [ + [ + 'Call to an undefined static method Bug2920bStaticCall\HelloWorld::a().', + 13, + ], + [ + 'Parameter #1 $s of static method Bug2920bStaticCall\HelloWorld::b() expects string, int given.', + 14, + ], + [ + 'Parameter #1 $s of static method Bug2920bStaticCall\HelloWorld::b() expects string, int given.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2920.php b/tests/PHPStan/Rules/Methods/data/bug-2920.php new file mode 100644 index 0000000000..1f76e4cc5d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-2920.php @@ -0,0 +1,26 @@ +$a(); + $this->$b(1); + $this->$b("1"); + + $this->b(1); + $this->b("1"); + } + + public function b(string $s): void + { + } + +} + diff --git a/tests/PHPStan/Rules/Methods/data/bug-2920b.php b/tests/PHPStan/Rules/Methods/data/bug-2920b.php new file mode 100644 index 0000000000..9e88bbfa19 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-2920b.php @@ -0,0 +1,26 @@ +