Skip to content

Commit

Permalink
Add new *ParameterClosureTypeExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural authored May 24, 2024
1 parent 7187c0e commit a57efdf
Show file tree
Hide file tree
Showing 16 changed files with 745 additions and 6 deletions.
4 changes: 4 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ services:
class: PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider
factory: PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider

-
class: PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider
factory: PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider

-
class: PHPStan\File\FileHelper
arguments:
Expand Down
62 changes: 56 additions & 6 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\CallLike;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\ErrorSuppress;
Expand Down Expand Up @@ -62,6 +63,7 @@
use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\File\FileReader;
use PHPStan\Node\BooleanAndNode;
Expand Down Expand Up @@ -244,6 +246,7 @@ public function __construct(
private readonly TypeSpecifier $typeSpecifier,
private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider,
private readonly ScopeFactory $scopeFactory,
private readonly bool $polluteScopeWithLoopInitialAssignments,
private readonly bool $polluteScopeWithAlwaysIterableForeach,
Expand Down Expand Up @@ -2283,7 +2286,7 @@ static function (): void {
if ($parametersAcceptor !== null) {
$expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr;
}
$result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context);
$result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context);
$scope = $result->getScope();
$hasYield = $result->hasYield();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
Expand Down Expand Up @@ -2537,7 +2540,7 @@ static function (): void {
$methodReflection,
$methodReflection !== null ? $scope->getNakedMethod($calledOnType, $methodReflection->getName()) : null,
$parametersAcceptor,
$expr->getArgs(),
$expr,
$scope,
$nodeCallback,
$context,
Expand Down Expand Up @@ -2717,7 +2720,7 @@ static function (): void {
if ($parametersAcceptor !== null) {
$expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr;
}
$result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context, $closureBindScope ?? null);
$result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context, $closureBindScope ?? null);
$scope = $result->getScope();
$scopeFunction = $scope->getFunction();

Expand Down Expand Up @@ -3232,7 +3235,7 @@ static function (): void {
}
}

$result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context);
$result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context);
$scope = $result->getScope();
$hasYield = $hasYield || $result->hasYield();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
Expand Down Expand Up @@ -4276,21 +4279,22 @@ private function processAttributeGroups(

/**
* @param MethodReflection|FunctionReflection|null $calleeReflection
* @param Node\Arg[] $args
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
private function processArgs(
Node\Stmt $stmt,
$calleeReflection,
?ExtendedMethodReflection $nakedMethodReflection,
?ParametersAcceptor $parametersAcceptor,
array $args,
CallLike $callLike,
MutatingScope $scope,
callable $nodeCallback,
ExpressionContext $context,
?MutatingScope $closureBindScope = null,
): ExpressionResult
{
$args = $callLike->getArgs();

if ($parametersAcceptor !== null) {
$parameters = $parametersAcceptor->getParameters();
}
Expand Down Expand Up @@ -4378,6 +4382,14 @@ private function processArgs(
$scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType());
}

if ($parameter !== null) {
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);

if ($overwritingParameterType !== null) {
$parameterType = $overwritingParameterType;
}
}

$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null);
if ($callCallbackImmediately) {
Expand Down Expand Up @@ -4422,6 +4434,14 @@ private function processArgs(
$scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType());
}

if ($parameter !== null) {
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);

if ($overwritingParameterType !== null) {
$parameterType = $overwritingParameterType;
}
}

$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
$arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $parameterType ?? null);
if ($callCallbackImmediately) {
Expand Down Expand Up @@ -4540,6 +4560,36 @@ private function processArgs(
return new ExpressionResult($scope, $hasYield, $throwPoints, $impurePoints);
}

/**
* @param MethodReflection|FunctionReflection|null $calleeReflection
*/
private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type
{
if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) {
foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) {
if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) {
return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope);
}
}
} elseif ($calleeReflection instanceof MethodReflection) {
if ($callLike instanceof StaticCall) {
foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) {
if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) {
return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope);
}
}
} elseif ($callLike instanceof MethodCall) {
foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) {
if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) {
return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope);
}
}
}
}

return null;
}

/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
* @param Closure(MutatingScope $scope): ExpressionResult $processExprCallback
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\DependencyInjection\Type;

use PHPStan\DependencyInjection\Container;

class LazyParameterClosureTypeExtensionProvider implements ParameterClosureTypeExtensionProvider
{

public const FUNCTION_TAG = 'phpstan.functionParameterClosureTypeExtension';
public const METHOD_TAG = 'phpstan.methodParameterClosureTypeExtension';
public const STATIC_METHOD_TAG = 'phpstan.staticMethodParameterClosureTypeExtension';

public function __construct(private Container $container)
{
}

public function getFunctionParameterClosureTypeExtensions(): array
{
return $this->container->getServicesByTag(self::FUNCTION_TAG);
}

public function getMethodParameterClosureTypeExtensions(): array
{
return $this->container->getServicesByTag(self::METHOD_TAG);
}

public function getStaticMethodParameterClosureTypeExtensions(): array
{
return $this->container->getServicesByTag(self::STATIC_METHOD_TAG);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

namespace PHPStan\DependencyInjection\Type;

use PHPStan\Type\FunctionParameterClosureTypeExtension;
use PHPStan\Type\MethodParameterClosureTypeExtension;
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;

interface ParameterClosureTypeExtensionProvider
{

/** @return FunctionParameterClosureTypeExtension[] */
public function getFunctionParameterClosureTypeExtensions(): array;

/** @return MethodParameterClosureTypeExtension[] */
public function getMethodParameterClosureTypeExtensions(): array;

/** @return StaticMethodParameterClosureTypeExtension[] */
public function getStaticMethodParameterClosureTypeExtensions(): array;

}
2 changes: 2 additions & 0 deletions src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Collectors\Registry as CollectorRegistry;
use PHPStan\Dependency\DependencyResolver;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
Expand Down Expand Up @@ -91,6 +92,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
$readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
$this->shouldPolluteScopeWithLoopInitialAssignments(),
$this->shouldPolluteScopeWithAlwaysIterableForeach(),
Expand Down
2 changes: 2 additions & 0 deletions src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\ScopeContext;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
Expand Down Expand Up @@ -62,6 +63,7 @@ public static function processFile(
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'),
self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'),
Expand Down
32 changes: 32 additions & 0 deletions src/Type/FunctionParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParameterReflection;

/**
* This is the interface for parameter closure type extensions for functions.
*
* To register it in the configuration file use the `phpstan.functionParameterClosureTypeExtension` service tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyExtension
* tags:
* - phpstan.functionParameterClosureTypeExtension
* ```
*
* @api
*/
interface FunctionParameterClosureTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool;

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type;

}
32 changes: 32 additions & 0 deletions src/Type/MethodParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;

/**
* This is the interface for parameter closure type extensions for methods.
*
* To register it in the configuration file use the `phpstan.methodParameterClosureTypeExtension` service tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyExtension
* tags:
* - phpstan.methodParameterClosureTypeExtension
* ```
*
* @api
*/
interface MethodParameterClosureTypeExtension
{

public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool;

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type;

}
32 changes: 32 additions & 0 deletions src/Type/StaticMethodParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;

/**
* This is the interface for parameter closure type extensions for static methods.
*
* To register it in the configuration file use the `phpstan.staticMethodParameterClosureTypeExtension` service tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyExtension
* tags:
* - phpstan.staticMethodParameterClosureTypeExtension
* ```
*
* @api
*/
interface StaticMethodParameterClosureTypeExtension
{

public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool;

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type;

}
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PHPStan\Dependency\DependencyResolver;
use PHPStan\Dependency\ExportedNodeResolver;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Node\Printer\Printer;
use PHPStan\Parser\RichParser;
Expand Down Expand Up @@ -702,6 +703,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
false,
true,
Expand Down
Loading

0 comments on commit a57efdf

Please sign in to comment.