Skip to content

Commit 3702305

Browse files
committed
Fix return type inference from immediately invoked closure
1 parent 389abe8 commit 3702305

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

src/Analyser/MutatingScope.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
use PHPStan\Node\Printer\ExprPrinter;
5151
use PHPStan\Node\PropertyAssignNode;
5252
use PHPStan\Parser\ArrayMapArgVisitor;
53+
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
5354
use PHPStan\Parser\NewAssignedToPropertyVisitor;
5455
use PHPStan\Parser\Parser;
5556
use PHPStan\Php\PhpVersion;
@@ -1374,11 +1375,16 @@ private function resolveType(string $exprString, Expr $node): Type
13741375

13751376
$callableParameters = null;
13761377
$arrayMapArgs = $node->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
1378+
$immediatelyInvokedArgs = $node->getAttribute(ImmediatelyInvokedClosureVisitor::ARGS_ATTRIBUTE_NAME);
13771379
if ($arrayMapArgs !== null) {
13781380
$callableParameters = [];
13791381
foreach ($arrayMapArgs as $funcCallArg) {
13801382
$callableParameters[] = new DummyParameter('item', $this->getType($funcCallArg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null);
13811383
}
1384+
} elseif ($immediatelyInvokedArgs !== null) {
1385+
foreach ($immediatelyInvokedArgs as $immediatelyInvokedArg) {
1386+
$callableParameters[] = new DummyParameter('item', $this->getType($immediatelyInvokedArg->value), false, PassedByReference::createNo(), false, null);
1387+
}
13821388
} else {
13831389
$inFunctionCallsStackCount = count($this->inFunctionCallsStack);
13841390
if ($inFunctionCallsStackCount > 0) {

src/Parser/ImmediatelyInvokedClosureVisitor.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ final class ImmediatelyInvokedClosureVisitor extends NodeVisitorAbstract
1212
{
1313

1414
public const ATTRIBUTE_NAME = 'isImmediatelyInvokedClosure';
15+
public const ARGS_ATTRIBUTE_NAME = 'immediatelyInvokedClosureArgs';
1516

1617
#[Override]
1718
public function enterNode(Node $node): ?Node
1819
{
19-
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\Closure && !$node->isFirstClassCallable()) {
20+
if (
21+
$node instanceof Node\Expr\FuncCall
22+
&& ($node->name instanceof Node\Expr\Closure || $node->name instanceof Node\Expr\ArrowFunction)
23+
&& !$node->isFirstClassCallable()
24+
) {
2025
$node->name->setAttribute(self::ATTRIBUTE_NAME, true);
26+
$node->name->setAttribute(self::ARGS_ATTRIBUTE_NAME, $node->args);
2127
}
2228

2329
return null;

tests/PHPStan/Analyser/nsrt/pipe-operator.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,125 @@ public function doBaz2(): void
8181
(fn (int $x) => assertType('int<1, max>', $x))(doFoo());
8282
}
8383

84+
/**
85+
* @param array<positive-int> $ints
86+
*/
87+
public function doArrayMap(array $ints): void
88+
{
89+
assertType('array<int<1, max>>', $ints |>
90+
(fn ($x) => array_map(static fn ($i) => $i, $x)),
91+
);
92+
assertType('array<int<1, max>>', $ints |>
93+
(fn ($x) => array_map(static fn (int $i) => $i, $x)),
94+
);
95+
assertType('array<int<1, max>>', $ints |>
96+
(fn (array $x) => array_map(static fn ($i) => $i, $x)),
97+
);
98+
assertType('array<int<1, max>>', $ints |>
99+
(fn (array $x) => array_map(static fn (int $i) => $i, $x)),
100+
);
101+
102+
assertType('array<\'foo\'>', $ints |>
103+
(fn (array $x) => array_map(static fn (int $i) => 'foo', $x)),
104+
);
105+
106+
assertType('array<int<1, max>>', $ints |> function ($x) {
107+
assertType('array<int<1, max>>', $x);
108+
return array_map(function ($i) {
109+
assertType('int<1, max>', $i);
110+
111+
return $i;
112+
}, $x);
113+
});
114+
115+
assertType('array<int<1, max>>', $ints |> function (array $x) {
116+
assertType('array<int<1, max>>', $x);
117+
return array_map(function (int $i) {
118+
assertType('int<1, max>', $i);
119+
120+
return $i;
121+
}, $x);
122+
});
123+
}
124+
125+
/**
126+
* @param array<positive-int> $ints
127+
*/
128+
public function doArrayFilter(array $ints): void
129+
{
130+
assertType('array<int<1, max>>', $ints |> function ($x) {
131+
assertType('array<int<1, max>>', $x);
132+
return array_filter($x, function ($i) {
133+
assertType('int<1, max>', $i);
134+
135+
return true;
136+
});
137+
});
138+
assertType('array<int<1, max>>', $ints |> function (array $x) {
139+
assertType('array<int<1, max>>', $x);
140+
return array_filter($x, function (int $i) {
141+
assertType('int<1, max>', $i);
142+
143+
return true;
144+
});
145+
});
146+
assertType('array<int<1, max>>', $ints |> fn ($x) => array_filter($x, function ($i) {
147+
assertType('int<1, max>', $i);
148+
149+
return true;
150+
}));
151+
assertType('array<int<1, max>>', $ints |> fn (array $x) => array_filter($x, function (int $i) {
152+
assertType('int<1, max>', $i);
153+
154+
return true;
155+
}));
156+
assertType('array<0|1|2, 1|2|3>', (function (array $x) {
157+
assertType('array{1, 2, 3}', $x);
158+
return array_filter($x, function (int $i) {
159+
assertType('1|2|3', $i);
160+
161+
return true;
162+
});
163+
})([1, 2, 3]));
164+
assertType('array<0|1|2, 1|2|3>', (function ($x) {
165+
assertType('array{1, 2, 3}', $x);
166+
return array_filter($x, function ($i) {
167+
assertType('1|2|3', $i);
168+
169+
return true;
170+
});
171+
})([1, 2, 3]));
172+
assertType('array<0|1|2, 1|2|3>', (function (array $x) {
173+
assertType('array{1, 2, 3}', $x);
174+
return array_filter($x, function (int $i) {
175+
assertType('1|2|3', $i);
176+
177+
return true;
178+
});
179+
})([1, 2, 3]));
180+
assertType('array<0|1|2, 1|2|3>', (function ($x) {
181+
assertType('array{1, 2, 3}', $x);
182+
return array_filter($x, function ($i) {
183+
assertType('1|2|3', $i);
184+
185+
return true;
186+
});
187+
})([1, 2, 3]));
188+
assertType('array<0|1|2, 1|2|3>', (fn (array $x) => array_filter($x, function (int $i) {
189+
assertType('1|2|3', $i);
190+
191+
return true;
192+
}))([1, 2, 3]));
193+
assertType('array<0|1|2, 1|2|3>', (fn ($x) => array_filter($x, function ($i) {
194+
assertType('1|2|3', $i);
195+
196+
return true;
197+
}))([1, 2, 3]));
198+
assertType('1', (fn ($x) => $x)(1));
199+
assertType('1', (function ($x) {
200+
assertType('1', $x);
201+
return $x;
202+
})(1));
203+
}
204+
84205
}

0 commit comments

Comments
 (0)