Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract MethodCallReturnTypeHelper from MutatingScope #3410

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -88,6 +89,7 @@ public function create(
$this->constantResolver,
$context,
$this->phpVersion,
$this->methodCallReturnTypeHelper,
$declareStrictTypes,
$function,
$namespace,
Expand Down
1 change: 1 addition & 0 deletions src/Analyser/LazyInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
163 changes: 163 additions & 0 deletions src/Analyser/MethodCallReturnTypeHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Type\DynamicReturnTypeExtensionRegistry;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use function count;

final class MethodCallReturnTypeHelper
{

private DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry;

public function __construct(
DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
)
{
$this->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;
}

}
119 changes: 23 additions & 96 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()),
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions src/Testing/PHPStanTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Loading