Skip to content

Commit 4ff91bc

Browse files
phpstan-botclaude
andcommitted
Unify native callable parameter logic with callable parameter logic
Make createNativeCallableParameters follow the same structure as createCallableParameters by extracting shared logic into a doCreateCallableParameters helper that accepts a type-getter closure. In ClosureTypeResolver, compute $nativeCallableParameters following the same case structure as $callableParameters (array_map, array_filter, immediately-invoked, inFunctionCallsStack) instead of delegating to a separate method with hardcoded checks. Pass $nativePassedToType through processClosureNode and processArrowFunctionNode so that the general callable parameter resolution mechanism works for native types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 316cf69 commit 4ff91bc

2 files changed

Lines changed: 59 additions & 30 deletions

File tree

src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
use PHPStan\Node\ExecutionEndNode;
2121
use PHPStan\Node\InvalidateExprNode;
2222
use PHPStan\Node\PropertyAssignNode;
23+
use PHPStan\Parser\ArrayFilterArgVisitor;
2324
use PHPStan\Parser\ArrayMapArgVisitor;
2425
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
26+
use PHPStan\Reflection\ExtendedParameterReflection;
2527
use PHPStan\Reflection\Callables\SimpleImpurePoint;
2628
use PHPStan\Reflection\Callables\SimpleThrowPoint;
2729
use PHPStan\Reflection\Native\NativeParameterReflection;
@@ -97,27 +99,36 @@ public function getClosureType(
9799
}
98100

99101
$callableParameters = null;
102+
$nativeCallableParameters = null;
100103
$arrayMapArgs = $expr->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
104+
$arrayFilterArrayArg = $expr->getAttribute(ArrayFilterArgVisitor::CALLBACK_ATTRIBUTE_NAME);
101105
$immediatelyInvokedArgs = $expr->getAttribute(ImmediatelyInvokedClosureVisitor::ARGS_ATTRIBUTE_NAME);
102106
if ($arrayMapArgs !== null) {
103107
$callableParameters = [];
108+
$nativeCallableParameters = [];
104109
foreach ($arrayMapArgs as $funcCallArg) {
105110
$callableParameters[] = new DummyParameter('item', $scope->getType($funcCallArg->value)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
111+
$nativeCallableParameters[] = new DummyParameter('item', $scope->getNativeType($funcCallArg->value)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
106112
}
113+
} elseif ($arrayFilterArrayArg !== null) {
114+
$callableParameters = [new DummyParameter('item', $scope->getType($arrayFilterArrayArg)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null)];
115+
$nativeCallableParameters = [new DummyParameter('item', $scope->getNativeType($arrayFilterArrayArg)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null)];
107116
} elseif ($immediatelyInvokedArgs !== null) {
108117
foreach ($immediatelyInvokedArgs as $immediatelyInvokedArg) {
109118
$callableParameters[] = new DummyParameter('item', $scope->getType($immediatelyInvokedArg->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
119+
$nativeCallableParameters[] = new DummyParameter('item', $scope->getNativeType($immediatelyInvokedArg->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
110120
}
111121
} else {
112122
$inFunctionCallsStackCount = count($scope->inFunctionCallsStack);
113123
if ($inFunctionCallsStackCount > 0) {
114124
[, $inParameter] = $scope->inFunctionCallsStack[$inFunctionCallsStackCount - 1];
115125
if ($inParameter !== null) {
116126
$callableParameters = $this->nodeScopeResolver->createCallableParameters($scope, $expr, null, $inParameter->getType());
127+
$nativePassedToType = $inParameter instanceof ExtendedParameterReflection ? $inParameter->getNativeType() : $inParameter->getType();
128+
$nativeCallableParameters = $this->nodeScopeResolver->createNativeCallableParameters($scope, $expr, null, $nativePassedToType);
117129
}
118130
}
119131
}
120-
$nativeCallableParameters = $this->nodeScopeResolver->createNativeCallableParameters($scope, $expr);
121132

122133
if ($expr instanceof ArrowFunction) {
123134
$arrowScope = $scope->enterArrowFunctionWithoutReflection($expr, $callableParameters, $nativeCallableParameters);

src/Analyser/NodeScopeResolver.php

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,7 @@ public function processClosureNode(
27082708
callable $nodeCallback,
27092709
ExpressionContext $context,
27102710
?Type $passedToType,
2711+
?Type $nativePassedToType = null,
27112712
): ProcessClosureResult
27122713
{
27132714
foreach ($expr->params as $param) {
@@ -2773,7 +2774,7 @@ public function processClosureNode(
27732774
$this->callNodeCallback($nodeCallback, $expr->returnType, $scope, $storage);
27742775
}
27752776

2776-
$nativeCallableParameters = $this->createNativeCallableParameters($scope, $expr);
2777+
$nativeCallableParameters = $this->createNativeCallableParameters($scope, $expr, $closureCallArgs, $nativePassedToType);
27772778
$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters, $nativeCallableParameters);
27782779
$closureScope = $closureScope->processClosureScope($scope, null, $byRefUses);
27792780
$closureType = $closureScope->getAnonymousFunctionReflection();
@@ -2921,6 +2922,7 @@ public function processArrowFunctionNode(
29212922
ExpressionResultStorage $storage,
29222923
callable $nodeCallback,
29232924
?Type $passedToType,
2925+
?Type $nativePassedToType = null,
29242926
): ExpressionResult
29252927
{
29262928
foreach ($expr->params as $param) {
@@ -2931,7 +2933,7 @@ public function processArrowFunctionNode(
29312933
}
29322934

29332935
$arrowFunctionCallArgs = $expr->getAttribute(ArrowFunctionArgVisitor::ATTRIBUTE_NAME);
2934-
$nativeCallableParameters = $this->createNativeCallableParameters($scope, $expr);
2936+
$nativeCallableParameters = $this->createNativeCallableParameters($scope, $expr, $arrowFunctionCallArgs, $nativePassedToType);
29352937
$arrowFunctionScope = $scope->enterArrowFunction($expr, $this->createCallableParameters(
29362938
$scope,
29372939
$expr,
@@ -2949,14 +2951,52 @@ public function processArrowFunctionNode(
29492951
}
29502952

29512953
/**
2952-
* @param Node\Arg[] $args
2954+
* @param Node\Arg[]|null $args
29532955
* @return ParameterReflection[]|null
29542956
*/
29552957
public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array $args, ?Type $passedToType): ?array
2958+
{
2959+
return $this->doCreateCallableParameters($scope, $closureExpr, $args, $passedToType, static fn (Scope $s, Expr $e) => $s->getType($e));
2960+
}
2961+
2962+
/**
2963+
* @param Node\Arg[]|null $args
2964+
* @return ParameterReflection[]|null
2965+
*/
2966+
public function createNativeCallableParameters(Scope $scope, Expr $closureExpr, ?array $args, ?Type $nativePassedToType): ?array
2967+
{
2968+
$result = $this->doCreateCallableParameters($scope, $closureExpr, $args, $nativePassedToType, static fn (Scope $s, Expr $e) => $s->getNativeType($e));
2969+
if ($result !== null) {
2970+
return $result;
2971+
}
2972+
2973+
$arrayMapArgs = $closureExpr->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
2974+
if ($arrayMapArgs !== null) {
2975+
$nativeCallableParameters = [];
2976+
foreach ($arrayMapArgs as $funcCallArg) {
2977+
$nativeCallableParameters[] = new DummyParameter('item', $scope->getNativeType($funcCallArg->value)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
2978+
}
2979+
return $nativeCallableParameters;
2980+
}
2981+
2982+
$arrayFilterArrayArg = $closureExpr->getAttribute(ArrayFilterArgVisitor::CALLBACK_ATTRIBUTE_NAME);
2983+
if ($arrayFilterArrayArg !== null) {
2984+
return [new DummyParameter('item', $scope->getNativeType($arrayFilterArrayArg)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null)];
2985+
}
2986+
2987+
return null;
2988+
}
2989+
2990+
/**
2991+
* @param Node\Arg[]|null $args
2992+
* @param Closure(Scope, Expr): Type $typeGetter
2993+
* @return ParameterReflection[]|null
2994+
*/
2995+
private function doCreateCallableParameters(Scope $scope, Expr $closureExpr, ?array $args, ?Type $passedToType, Closure $typeGetter): ?array
29562996
{
29572997
$callableParameters = null;
29582998
if ($args !== null) {
2959-
$closureType = $scope->getType($closureExpr);
2999+
$closureType = $typeGetter($scope, $closureExpr);
29603000

29613001
if ($closureType->isCallable()->no()) {
29623002
return null;
@@ -2971,7 +3011,7 @@ public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array
29713011
continue;
29723012
}
29733013

2974-
$type = $scope->getType($args[$index]->value);
3014+
$type = $typeGetter($scope, $args[$index]->value);
29753015
$callableParameters[$index] = new NativeParameterReflection(
29763016
$callableParameter->getName(),
29773017
$callableParameter->isOptional(),
@@ -3029,28 +3069,6 @@ public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array
30293069
return $callableParameters;
30303070
}
30313071

3032-
/**
3033-
* @return ParameterReflection[]|null
3034-
*/
3035-
public function createNativeCallableParameters(MutatingScope $scope, Expr $closureExpr): ?array
3036-
{
3037-
$arrayMapArgs = $closureExpr->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
3038-
if ($arrayMapArgs !== null) {
3039-
$nativeCallableParameters = [];
3040-
foreach ($arrayMapArgs as $funcCallArg) {
3041-
$nativeCallableParameters[] = new DummyParameter('item', $scope->getNativeType($funcCallArg->value)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
3042-
}
3043-
return $nativeCallableParameters;
3044-
}
3045-
3046-
$arrayFilterArrayArg = $closureExpr->getAttribute(ArrayFilterArgVisitor::CALLBACK_ATTRIBUTE_NAME);
3047-
if ($arrayFilterArrayArg !== null) {
3048-
return [new DummyParameter('item', $scope->getNativeType($arrayFilterArrayArg)->getIterableValueType(), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null)];
3049-
}
3050-
3051-
return null;
3052-
}
3053-
30543072
/**
30553073
* @param callable(Node $node, Scope $scope): void $nodeCallback
30563074
*/
@@ -3411,7 +3429,7 @@ public function processArgs(
34113429
}
34123430

34133431
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
3414-
$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context, $parameterType ?? null);
3432+
$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context, $parameterType ?? null, $parameterNativeType);
34153433
if ($this->callCallbackImmediately($parameter, $parameterType, $calleeReflection)) {
34163434
$throwPoints = array_merge($throwPoints, array_map(static fn (InternalThrowPoint $throwPoint) => $throwPoint->isExplicit() ? InternalThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : InternalThrowPoint::createImplicit($scope, $arg->value), $closureResult->getThrowPoints()));
34173435
$impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints());
@@ -3470,7 +3488,7 @@ public function processArgs(
34703488
}
34713489

34723490
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
3473-
$arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $parameterType ?? null);
3491+
$arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $parameterType ?? null, $parameterNativeType);
34743492
if ($this->callCallbackImmediately($parameter, $parameterType, $calleeReflection)) {
34753493
$throwPoints = array_merge($throwPoints, array_map(static fn (InternalThrowPoint $throwPoint) => $throwPoint->isExplicit() ? InternalThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : InternalThrowPoint::createImplicit($scope, $arg->value), $arrowFunctionResult->getThrowPoints()));
34763494
$impurePoints = array_merge($impurePoints, $arrowFunctionResult->getImpurePoints());

0 commit comments

Comments
 (0)