diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 3e42c9c42e..03b6e5fc8c 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -2,6 +2,8 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\Scope; @@ -20,6 +22,7 @@ use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\UnionType; use function count; +use function ltrim; #[AutowiredService] final class MethodExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension @@ -64,7 +67,7 @@ public function specifyTypes( $objectType = $scope->getType($args[0]->value); if ($objectType->isString()->yes()) { if ($objectType->isClassString()->yes()) { - return $this->typeSpecifier->create( + $result = $this->typeSpecifier->create( $args[0]->value, new IntersectionType([ $objectType, @@ -73,6 +76,31 @@ public function specifyTypes( $context, $scope, ); + + if (!$args[0]->value instanceof ClassConstFetch) { + foreach ($objectType->getConstantStrings() as $constantString) { + $className = ltrim($constantString->getValue(), '\\'); + if ($className === '') { + continue; + } + $classConstFetch = new Expr\ClassConstFetch( + new FullyQualified($className), + 'class', + ); + $classConstFetchType = $scope->getType($classConstFetch); + $result = $result->unionWith($this->typeSpecifier->create( + $classConstFetch, + new IntersectionType([ + $classConstFetchType, + new HasMethodType($methodNameType->getValue()), + ]), + $context, + $scope, + )); + } + } + + return $result; } return new SpecifiedTypes([], []); diff --git a/tests/PHPStan/Analyser/nsrt/bug-9592.php b/tests/PHPStan/Analyser/nsrt/bug-9592.php new file mode 100644 index 0000000000..f2a1ceb05f --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9592.php @@ -0,0 +1,19 @@ +checkThisOnly = false; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/bug-9592.php'], []); + } + public function testBug1267(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-9592.php b/tests/PHPStan/Rules/Methods/data/bug-9592.php new file mode 100644 index 0000000000..165440e95f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-9592.php @@ -0,0 +1,30 @@ +