|
20 | 20 | use PhpParser\Node\Expr\BinaryOp\BooleanOr;
|
21 | 21 | use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
22 | 22 | use PhpParser\Node\Expr\BooleanNot;
|
| 23 | +use PhpParser\Node\Expr\CallLike; |
23 | 24 | use PhpParser\Node\Expr\Cast;
|
24 | 25 | use PhpParser\Node\Expr\ConstFetch;
|
25 | 26 | use PhpParser\Node\Expr\ErrorSuppress;
|
|
62 | 63 | use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
|
63 | 64 | use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
|
64 | 65 | use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
|
| 66 | +use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; |
65 | 67 | use PHPStan\File\FileHelper;
|
66 | 68 | use PHPStan\File\FileReader;
|
67 | 69 | use PHPStan\Node\BooleanAndNode;
|
@@ -244,6 +246,7 @@ public function __construct(
|
244 | 246 | private readonly TypeSpecifier $typeSpecifier,
|
245 | 247 | private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
|
246 | 248 | private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
|
| 249 | + private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, |
247 | 250 | private readonly ScopeFactory $scopeFactory,
|
248 | 251 | private readonly bool $polluteScopeWithLoopInitialAssignments,
|
249 | 252 | private readonly bool $polluteScopeWithAlwaysIterableForeach,
|
@@ -2283,7 +2286,7 @@ static function (): void {
|
2283 | 2286 | if ($parametersAcceptor !== null) {
|
2284 | 2287 | $expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr;
|
2285 | 2288 | }
|
2286 |
| - $result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); |
| 2289 | + $result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); |
2287 | 2290 | $scope = $result->getScope();
|
2288 | 2291 | $hasYield = $result->hasYield();
|
2289 | 2292 | $throwPoints = array_merge($throwPoints, $result->getThrowPoints());
|
@@ -2537,7 +2540,7 @@ static function (): void {
|
2537 | 2540 | $methodReflection,
|
2538 | 2541 | $methodReflection !== null ? $scope->getNakedMethod($calledOnType, $methodReflection->getName()) : null,
|
2539 | 2542 | $parametersAcceptor,
|
2540 |
| - $expr->getArgs(), |
| 2543 | + $expr, |
2541 | 2544 | $scope,
|
2542 | 2545 | $nodeCallback,
|
2543 | 2546 | $context,
|
@@ -2717,7 +2720,7 @@ static function (): void {
|
2717 | 2720 | if ($parametersAcceptor !== null) {
|
2718 | 2721 | $expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr;
|
2719 | 2722 | }
|
2720 |
| - $result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context, $closureBindScope ?? null); |
| 2723 | + $result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context, $closureBindScope ?? null); |
2721 | 2724 | $scope = $result->getScope();
|
2722 | 2725 | $scopeFunction = $scope->getFunction();
|
2723 | 2726 |
|
@@ -3232,7 +3235,7 @@ static function (): void {
|
3232 | 3235 | }
|
3233 | 3236 | }
|
3234 | 3237 |
|
3235 |
| - $result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); |
| 3238 | + $result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); |
3236 | 3239 | $scope = $result->getScope();
|
3237 | 3240 | $hasYield = $hasYield || $result->hasYield();
|
3238 | 3241 | $throwPoints = array_merge($throwPoints, $result->getThrowPoints());
|
@@ -4276,21 +4279,22 @@ private function processAttributeGroups(
|
4276 | 4279 |
|
4277 | 4280 | /**
|
4278 | 4281 | * @param MethodReflection|FunctionReflection|null $calleeReflection
|
4279 |
| - * @param Node\Arg[] $args |
4280 | 4282 | * @param callable(Node $node, Scope $scope): void $nodeCallback
|
4281 | 4283 | */
|
4282 | 4284 | private function processArgs(
|
4283 | 4285 | Node\Stmt $stmt,
|
4284 | 4286 | $calleeReflection,
|
4285 | 4287 | ?ExtendedMethodReflection $nakedMethodReflection,
|
4286 | 4288 | ?ParametersAcceptor $parametersAcceptor,
|
4287 |
| - array $args, |
| 4289 | + CallLike $callLike, |
4288 | 4290 | MutatingScope $scope,
|
4289 | 4291 | callable $nodeCallback,
|
4290 | 4292 | ExpressionContext $context,
|
4291 | 4293 | ?MutatingScope $closureBindScope = null,
|
4292 | 4294 | ): ExpressionResult
|
4293 | 4295 | {
|
| 4296 | + $args = $callLike->getArgs(); |
| 4297 | + |
4294 | 4298 | if ($parametersAcceptor !== null) {
|
4295 | 4299 | $parameters = $parametersAcceptor->getParameters();
|
4296 | 4300 | }
|
@@ -4378,6 +4382,14 @@ private function processArgs(
|
4378 | 4382 | $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType());
|
4379 | 4383 | }
|
4380 | 4384 |
|
| 4385 | + if ($parameter !== null) { |
| 4386 | + $overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 4387 | + |
| 4388 | + if ($overwritingParameterType !== null) { |
| 4389 | + $parameterType = $overwritingParameterType; |
| 4390 | + } |
| 4391 | + } |
| 4392 | + |
4381 | 4393 | $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
|
4382 | 4394 | $closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null);
|
4383 | 4395 | if ($callCallbackImmediately) {
|
@@ -4422,6 +4434,14 @@ private function processArgs(
|
4422 | 4434 | $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType());
|
4423 | 4435 | }
|
4424 | 4436 |
|
| 4437 | + if ($parameter !== null) { |
| 4438 | + $overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 4439 | + |
| 4440 | + if ($overwritingParameterType !== null) { |
| 4441 | + $parameterType = $overwritingParameterType; |
| 4442 | + } |
| 4443 | + } |
| 4444 | + |
4425 | 4445 | $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
|
4426 | 4446 | $arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $parameterType ?? null);
|
4427 | 4447 | if ($callCallbackImmediately) {
|
@@ -4540,6 +4560,36 @@ private function processArgs(
|
4540 | 4560 | return new ExpressionResult($scope, $hasYield, $throwPoints, $impurePoints);
|
4541 | 4561 | }
|
4542 | 4562 |
|
| 4563 | + /** |
| 4564 | + * @param MethodReflection|FunctionReflection|null $calleeReflection |
| 4565 | + */ |
| 4566 | + private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type |
| 4567 | + { |
| 4568 | + if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { |
| 4569 | + foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { |
| 4570 | + if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { |
| 4571 | + return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); |
| 4572 | + } |
| 4573 | + } |
| 4574 | + } elseif ($calleeReflection instanceof MethodReflection) { |
| 4575 | + if ($callLike instanceof StaticCall) { |
| 4576 | + foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { |
| 4577 | + if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { |
| 4578 | + return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); |
| 4579 | + } |
| 4580 | + } |
| 4581 | + } elseif ($callLike instanceof MethodCall) { |
| 4582 | + foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { |
| 4583 | + if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { |
| 4584 | + return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); |
| 4585 | + } |
| 4586 | + } |
| 4587 | + } |
| 4588 | + } |
| 4589 | + |
| 4590 | + return null; |
| 4591 | + } |
| 4592 | + |
4543 | 4593 | /**
|
4544 | 4594 | * @param callable(Node $node, Scope $scope): void $nodeCallback
|
4545 | 4595 | * @param Closure(MutatingScope $scope): ExpressionResult $processExprCallback
|
|
0 commit comments