Skip to content

Commit

Permalink
Improve closure parameter in-call type inference
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 22, 2024
1 parent da4fd7a commit 5cebee3
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 24 deletions.
63 changes: 44 additions & 19 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1207,25 +1207,6 @@ private function resolveType(string $exprString, Expr $node): Type
}
}

foreach ($node->params as $i => $param) {
if ($param->variadic) {
$isVariadic = true;
}
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new ShouldNotHappenException();
}
$parameters[] = new NativeParameterReflection(
$param->var->name,
$firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex,
$this->getFunctionType($param->type, $this->isParameterValueNullable($param), false),
$param->byRef
? PassedByReference::createCreatesNewVariable()
: PassedByReference::createNo(),
$param->variadic,
$param->default !== null ? $this->getType($param->default) : null,
);
}

$callableParameters = null;
$arrayMapArgs = $node->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
if ($arrayMapArgs !== null) {
Expand All @@ -1245,6 +1226,28 @@ private function resolveType(string $exprString, Expr $node): Type

if ($node instanceof Expr\ArrowFunction) {
$arrowScope = $this->enterArrowFunctionWithoutReflection($node, $callableParameters);
foreach ($node->params as $i => $param) {
if ($param->variadic) {
$isVariadic = true;
}
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new ShouldNotHappenException();
}
$parameterType = $arrowScope->getType($param->var);
if ($param->variadic) {
$parameterType = $parameterType->getIterableValueType();
}
$parameters[] = new NativeParameterReflection(
$param->var->name,
$firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex,
$parameterType,
$param->byRef
? PassedByReference::createCreatesNewVariable()
: PassedByReference::createNo(),
$param->variadic,
$param->default !== null ? $this->getType($param->default) : null,
);
}

if ($node->expr instanceof Expr\Yield_ || $node->expr instanceof Expr\YieldFrom) {
$yieldNode = $node->expr;
Expand Down Expand Up @@ -1316,6 +1319,28 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
$usedVariables = [];
} else {
$closureScope = $this->enterAnonymousFunctionWithoutReflection($node, $callableParameters);
foreach ($node->params as $i => $param) {
if ($param->variadic) {
$isVariadic = true;
}
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new ShouldNotHappenException();
}
$parameterType = $closureScope->getType($param->var);
if ($param->variadic) {
$parameterType = $parameterType->getIterableValueType();
}
$parameters[] = new NativeParameterReflection(
$param->var->name,
$firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex,
$parameterType,
$param->byRef
? PassedByReference::createCreatesNewVariable()
: PassedByReference::createNo(),
$param->variadic,
$param->default !== null ? $this->getType($param->default) : null,
);
}
$closureReturnStatements = [];
$closureYieldStatements = [];
$closureExecutionEnds = [];
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/TestClosureTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public function testRule(): void
{
$this->analyse([__DIR__ . '/data/closure-passed-to-type.php'], [
[
'Closure type: Closure(mixed): (1|2|3)',
'Closure type: Closure(1|2|3): (1|2|3)',
25,
],
[
'Closure type: Closure(mixed): (1|2|3)',
'Closure type: Closure(1|2|3): (1|2|3)',
35,
],
]);
Expand Down
3 changes: 2 additions & 1 deletion tests/PHPStan/Analyser/data/bug-3158.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ function (): void {
assertType(A::class, $proxy);

$proxy = createProxy2(function(A $a, B $o):void {});
assertType(B::class, $proxy);
// assertType(B::class, $proxy);
assertType('Bug3158\B&T of object (function Bug3158\createProxy2(), parameter)', $proxy);
};

function (): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void
$this->checkExplicitMixed = $checkExplicitMixed;
$this->analyse([__DIR__ . '/data/array_map_multiple.php'], [
[
'Parameter #1 $callback of function array_map expects (callable(1|2, \'bar\'|\'foo\'): mixed)|null, Closure(int, int): void given.',
'Parameter #1 $callback of function array_map expects (callable(1|2, \'bar\'|\'foo\'): mixed)|null, Closure(1|2, int): void given.',
58,
],
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public function test(): void
$cb = $this->callback(function (int $i): bool {
return true;
});
assertType(Callback::class . '<int>', $cb);
// assertType(Callback::class . '<int>', $cb);
assertType('UnableToResolveCallbackParameterType\Callback<CallbackInput (method UnableToResolveCallbackParameterType\Foo::callback(), parameter)&int>', $cb);
}

}

0 comments on commit 5cebee3

Please sign in to comment.