diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index 64fb1adf87..48f50f9dff 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -109,6 +109,11 @@ public function check( $classType = $scope->resolveTypeByName($class); } else { + $classStringType = $scope->getType(new Expr\ClassConstFetch($class, 'class')); + if ($classStringType->hasMethod($methodName)->yes()) { + return [[], null]; + } + if (!$this->reflectionProvider->hasClass($className)) { if ($scope->isInClassExists($className)) { return [[], null]; diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 08ea58759e..d3a4471b75 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -64,6 +64,14 @@ public function getValue(): string return $this->value; } + public function hasMethod(string $methodName): TrinaryLogic + { + if ($this->isClassString()) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createNo(); + } + public function isClassString(): bool { if ($this->isClassString) { diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 1498960929..21485d6a75 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -14,7 +14,6 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\IntersectionType; -use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\UnionType; use function count; @@ -47,15 +46,26 @@ public function specifyTypes( TypeSpecifierContext $context, ): SpecifiedTypes { + $methodNameType = $scope->getType($node->getArgs()[1]->value); + if (!$methodNameType instanceof ConstantStringType) { + return new SpecifiedTypes([], []); + } + $objectType = $scope->getType($node->getArgs()[0]->value); - if (!$objectType instanceof ObjectType) { - if ($objectType->isString()->yes()) { - return new SpecifiedTypes([], []); + if ($objectType->isString()->yes()) { + if ($objectType instanceof ConstantStringType && $objectType->isClassString()) { + return $this->typeSpecifier->create( + $node->getArgs()[0]->value, + new IntersectionType([ + $objectType, + new HasMethodType($methodNameType->getValue()), + ]), + $context, + false, + $scope, + ); } - } - $methodNameType = $scope->getType($node->getArgs()[1]->value); - if (!$methodNameType instanceof ConstantStringType) { return new SpecifiedTypes([], []); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a827d06f3b..e300c57340 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1124,6 +1124,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8272.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8277.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/strtr.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/static-has-method.php'); } /** diff --git a/tests/PHPStan/Analyser/data/static-has-method.php b/tests/PHPStan/Analyser/data/static-has-method.php new file mode 100644 index 0000000000..b00640bc91 --- /dev/null +++ b/tests/PHPStan/Analyser/data/static-has-method.php @@ -0,0 +1,21 @@ +analyse([__DIR__ . '/data/bug-7489.php'], []); } + public function testHasMethodStaticCall(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/static-has-method.php'], [ + [ + 'Call to an undefined static method StaticHasMethodCall\rex_var::doesNotExist().', + 38, + ], + [ + 'Call to an undefined static method StaticHasMethodCall\rex_var::doesNotExist().', + 48, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/static-has-method.php b/tests/PHPStan/Rules/Methods/data/static-has-method.php new file mode 100644 index 0000000000..177bde0ab4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/static-has-method.php @@ -0,0 +1,51 @@ +