From e3acc778e23d5ad5ac8d9c87929cfd3a41dd380f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 6 Sep 2024 11:42:31 +0200 Subject: [PATCH] Extract MethodCallReturnTypeHelper from MutatingScope --- conf/config.neon | 3 + src/Analyser/DirectInternalScopeFactory.php | 2 + src/Analyser/LazyInternalScopeFactory.php | 1 + src/Analyser/MethodCallReturnTypeHelper.php | 163 ++++++++++++++++++++ src/Analyser/MutatingScope.php | 119 +++----------- src/Testing/PHPStanTestCase.php | 2 + 6 files changed, 194 insertions(+), 96 deletions(-) create mode 100644 src/Analyser/MethodCallReturnTypeHelper.php diff --git a/conf/config.neon b/conf/config.neon index aa096cc686..eb0efc102b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2156,6 +2156,9 @@ services: - class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory + - + class: PHPStan\Analyser\MethodCallReturnTypeHelper + php8Lexer: class: PhpParser\Lexer\Emulative factory: @PHPStan\Parser\LexerFactory::createEmulative() diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 49a3395d2e..2addd8316f 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -35,6 +35,7 @@ public function __construct( private Parser $parser, private NodeScopeResolver $nodeScopeResolver, private PhpVersion $phpVersion, + private MethodCallReturnTypeHelper $methodCallReturnTypeHelper, private bool $explicitMixedInUnknownGenericNew, private bool $explicitMixedForGlobalVariables, private ConstantResolver $constantResolver, @@ -88,6 +89,7 @@ public function create( $this->constantResolver, $context, $this->phpVersion, + $this->methodCallReturnTypeHelper, $declareStrictTypes, $function, $namespace, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index fec1521768..4591592e6e 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -82,6 +82,7 @@ public function create( $this->container->getByType(ConstantResolver::class), $context, $this->container->getByType(PhpVersion::class), + $this->container->getByType(MethodCallReturnTypeHelper::class), $declareStrictTypes, $function, $namespace, diff --git a/src/Analyser/MethodCallReturnTypeHelper.php b/src/Analyser/MethodCallReturnTypeHelper.php new file mode 100644 index 0000000000..5641584cd0 --- /dev/null +++ b/src/Analyser/MethodCallReturnTypeHelper.php @@ -0,0 +1,163 @@ +dynamicReturnTypeExtensionRegistry = $dynamicReturnTypeExtensionRegistryProvider->getRegistry(); + } + + public function constructorReturnType( + ParametersAcceptor $parametersAcceptor, + Scope $scope, + StaticCall $methodCall, + ExtendedMethodReflection $constructorMethod, + string $className, + ): ?Type + { + $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); + + $resolvedTypes = []; + if ($normalizedMethodCall !== null) { + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { + if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) { + continue; + } + + $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( + $constructorMethod, + $normalizedMethodCall, + $scope, + ); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; + } + } + + if (count($resolvedTypes) > 0) { + return TypeCombinator::union(...$resolvedTypes); + } + + return null; + } + + /** + * @param MethodCall|StaticCall $methodCall + */ + public function methodCallReturnType( + ParametersAcceptor $parametersAcceptor, + Scope $scope, + Type $typeWithMethod, + string $methodName, + Expr $methodCall, + ): ?Type + { + $typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName); + if ($typeWithMethod === null) { + return null; + } + + $methodReflection = $typeWithMethod->getMethod($methodName, $scope); + + if ($methodCall instanceof MethodCall) { + $normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall); + } else { + $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); + } + if ($normalizedMethodCall === null) { + return $parametersAcceptor->getReturnType(); + } + + $resolvedTypes = []; + foreach ($typeWithMethod->getObjectClassNames() as $className) { + if ($normalizedMethodCall instanceof MethodCall) { + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) { + if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) { + continue; + } + + $resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall( + $methodReflection, + $normalizedMethodCall, + $scope, + ); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; + } + } else { + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { + if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) { + continue; + } + + $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( + $methodReflection, + $normalizedMethodCall, + $scope, + ); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; + } + } + } + + if (count($resolvedTypes) > 0) { + return TypeCombinator::union(...$resolvedTypes); + } + + return $parametersAcceptor->getReturnType(); + } + + public function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type + { + if ($typeWithMethod instanceof UnionType) { + $newTypes = []; + foreach ($typeWithMethod->getTypes() as $innerType) { + if (!$innerType->hasMethod($methodName)->yes()) { + continue; + } + + $newTypes[] = $innerType; + } + if (count($newTypes) === 0) { + return null; + } + $typeWithMethod = TypeCombinator::union(...$newTypes); + } + + if (!$typeWithMethod->hasMethod($methodName)->yes()) { + return null; + } + + return $typeWithMethod; + } + +} diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d8cc9faf06..740ce3a92b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -207,6 +207,7 @@ public function __construct( private ConstantResolver $constantResolver, private ScopeContext $context, private PhpVersion $phpVersion, + private MethodCallReturnTypeHelper $methodCallReturnTypeHelper, private bool $declareStrictTypes = false, private PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, @@ -5485,7 +5486,6 @@ private function exactInstantiation(New_ $node, string $className): ?Type $constructorMethod = new DummyConstructorReflection($classReflection); } - $resolvedTypes = []; $methodCall = new Expr\StaticCall( new Name($resolvedClassName), new Node\Identifier($constructorMethod->getName()), @@ -5498,29 +5498,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type $constructorMethod->getVariants(), $constructorMethod->getNamedArgumentsVariants(), ); - $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); - - if ($normalizedMethodCall !== null) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) { - continue; - } - - $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( - $constructorMethod, - $normalizedMethodCall, - $this, - ); - if ($resolvedType === null) { - continue; - } - - $resolvedTypes[] = $resolvedType; - } - } - - if (count($resolvedTypes) > 0) { - return TypeCombinator::union(...$resolvedTypes); + $returnType = $this->methodCallReturnTypeHelper->constructorReturnType( + $parametersAcceptor, + $this, + $methodCall, + $constructorMethod, + $classReflection->getName(), + ); + if ($returnType !== null) { + return $returnType; } $methodResult = $this->getType($methodCall); @@ -5619,34 +5605,10 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } - private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type - { - if ($typeWithMethod instanceof UnionType) { - $newTypes = []; - foreach ($typeWithMethod->getTypes() as $innerType) { - if (!$innerType->hasMethod($methodName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithMethod = TypeCombinator::union(...$newTypes); - } - - if (!$typeWithMethod->hasMethod($methodName)->yes()) { - return null; - } - - return $typeWithMethod; - } - /** @api */ public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection { - $type = $this->filterTypeWithMethod($typeWithMethod, $methodName); + $type = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName); if ($type === null) { return null; } @@ -5657,7 +5619,7 @@ public function getMethodReflection(Type $typeWithMethod, string $methodName): ? /** @api */ public function getNakedMethod(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection { - $type = $this->filterTypeWithMethod($typeWithMethod, $methodName); + $type = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName); if ($type === null) { return null; } @@ -5670,7 +5632,7 @@ public function getNakedMethod(Type $typeWithMethod, string $methodName): ?Exten */ private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type { - $typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName); + $typeWithMethod = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName); if ($typeWithMethod === null) { return null; } @@ -5682,55 +5644,20 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, $methodReflection->getVariants(), $methodReflection->getNamedArgumentsVariants(), ); - if ($methodCall instanceof MethodCall) { - $normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall); - } else { - $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); - } - if ($normalizedMethodCall === null) { - return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall); - } - - $resolvedTypes = []; - foreach ($typeWithMethod->getObjectClassNames() as $className) { - if ($normalizedMethodCall instanceof MethodCall) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) { - if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) { - continue; - } - - $resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $this); - if ($resolvedType === null) { - continue; - } - - $resolvedTypes[] = $resolvedType; - } - } else { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) { - continue; - } - - $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( - $methodReflection, - $normalizedMethodCall, - $this, - ); - if ($resolvedType === null) { - continue; - } - $resolvedTypes[] = $resolvedType; - } - } - } + $returnType = $this->methodCallReturnTypeHelper->methodCallReturnType( + $parametersAcceptor, + $this, + $typeWithMethod, + $methodName, + $methodCall, + ); - if (count($resolvedTypes) > 0) { - return $this->transformVoidToNull(TypeCombinator::union(...$resolvedTypes), $methodCall); + if ($returnType !== null) { + return $this->transformVoidToNull($returnType, $methodCall); } - return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall); + return null; } /** @api */ diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 14efcc7480..bbc586b2f0 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -5,6 +5,7 @@ use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\DirectInternalScopeFactory; use PHPStan\Analyser\Error; +use PHPStan\Analyser\MethodCallReturnTypeHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\ScopeFactory; @@ -188,6 +189,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider self::getParser(), $container->getByType(NodeScopeResolver::class), $container->getByType(PhpVersion::class), + $container->getByType(MethodCallReturnTypeHelper::class), $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], $container->getParameter('featureToggles')['explicitMixedForGlobalVariables'], $constantResolver,