Skip to content

Commit bcdbfd8

Browse files
committed
Fix closure type inference
1 parent 76b7f5a commit bcdbfd8

File tree

3 files changed

+84
-12
lines changed

3 files changed

+84
-12
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,32 @@ private function processStmtNodesInternal(
415415
callable $nodeCallback,
416416
StatementContext $context,
417417
): InternalStatementResult
418+
{
419+
$statementResult = $this->processStmtNodesInternalWithoutFlushingPendingFibers(
420+
$parentNode,
421+
$stmts,
422+
$scope,
423+
$storage,
424+
$nodeCallback,
425+
$context,
426+
);
427+
$this->processPendingFibers($storage);
428+
429+
return $statementResult;
430+
}
431+
432+
/**
433+
* @param Node\Stmt[] $stmts
434+
* @param callable(Node $node, Scope $scope): void $nodeCallback
435+
*/
436+
private function processStmtNodesInternalWithoutFlushingPendingFibers(
437+
Node $parentNode,
438+
array $stmts,
439+
MutatingScope $scope,
440+
ExpressionResultStorage $storage,
441+
callable $nodeCallback,
442+
StatementContext $context,
443+
): InternalStatementResult
418444
{
419445
$exitPoints = [];
420446
$throwPoints = [];
@@ -502,8 +528,6 @@ private function processStmtNodesInternal(
502528
), $scope, $storage);
503529
}
504530

505-
$this->processPendingFibers($storage);
506-
507531
return $statementResult;
508532
}
509533

@@ -5057,7 +5081,7 @@ private function processClosureNode(
50575081
};
50585082

50595083
if (count($byRefUses) === 0) {
5060-
$statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel());
5084+
$statementResult = $this->processStmtNodesInternalWithoutFlushingPendingFibers($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel());
50615085
$publicStatementResult = $statementResult->toPublic();
50625086
$this->callNodeCallback($nodeCallback, new ClosureReturnStatementsNode(
50635087
$expr,
@@ -5079,7 +5103,7 @@ private function processClosureNode(
50795103
$prevScope = $closureScope;
50805104

50815105
$storage = $originalStorage->duplicate();
5082-
$intermediaryClosureScopeResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, new NoopNodeCallback(), StatementContext::createTopLevel());
5106+
$intermediaryClosureScopeResult = $this->processStmtNodesInternalWithoutFlushingPendingFibers($expr, $expr->stmts, $closureScope, $storage, new NoopNodeCallback(), StatementContext::createTopLevel());
50835107
$intermediaryClosureScope = $intermediaryClosureScopeResult->getScope();
50845108
foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) {
50855109
$intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope());
@@ -5107,7 +5131,7 @@ private function processClosureNode(
51075131
}
51085132

51095133
$storage = $originalStorage;
5110-
$statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel());
5134+
$statementResult = $this->processStmtNodesInternalWithoutFlushingPendingFibers($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel());
51115135
$publicStatementResult = $statementResult->toPublic();
51125136
$this->callNodeCallback($nodeCallback, new ClosureReturnStatementsNode(
51135137
$expr,

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,12 +605,12 @@ public function testArrayReduceCallback(): void
605605
5,
606606
],
607607
[
608-
sprintf('Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, 1|2|3): (non-empty-string|null), Closure(string, int): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'non-empty-string' : 'non-falsy-string'),
608+
sprintf('Parameter #2 $callback of function array_reduce expects callable(%1$s|null, 1|2|3): (%1$s|null), Closure(string, int): non-falsy-string given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'non-falsy-string' : 'non-empty-string'),
609609
13,
610610
'Type string of parameter #1 $foo of passed callable needs to be same or wider than parameter type string|null of accepting callable.',
611611
],
612612
[
613-
sprintf('Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, 1|2|3): (non-empty-string|null), Closure(string, int): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'non-empty-string' : 'non-falsy-string'),
613+
sprintf('Parameter #2 $callback of function array_reduce expects callable(%1$s|null, 1|2|3): (%1$s|null), Closure(string, int): non-falsy-string given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'non-falsy-string' : 'non-empty-string'),
614614
22,
615615
'Type string of parameter #1 $foo of passed callable needs to be same or wider than parameter type string|null of accepting callable.',
616616
],
@@ -683,7 +683,7 @@ public function testArrayUdiffCallback(): void
683683
6,
684684
],
685685
[
686-
sprintf('Parameter #3 $data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? '(lowercase-string&non-falsy-string&uppercase-string)' : "('11'|'12'|'13'|'14'|'15'|'16'|'21'|'22'|'23'|'24'|'25'|'26'|'31'|'32'|'33'|'34'|'35'|'36'|'41'|'42'|'43'|'44'|'45'|'46'|'51'|'52'|'53'|'54'|'55'|'56'|'61'|'62'|'63'|'64'|'65'|'66')"),
686+
"Parameter #3 \$data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): ('11'|'12'|'13'|'14'|'15'|'16'|'21'|'22'|'23'|'24'|'25'|'26'|'31'|'32'|'33'|'34'|'35'|'36'|'41'|'42'|'43'|'44'|'45'|'46'|'51'|'52'|'53'|'54'|'55'|'56'|'61'|'62'|'63'|'64'|'65'|'66') given.",
687687
14,
688688
],
689689
[
@@ -935,7 +935,7 @@ public function testArrayAllCallback(): void
935935
30,
936936
],
937937
[
938-
sprintf('Parameter #2 $callback of function array_all expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'string' : '(\'bar\'|\'foo\')'),
938+
'Parameter #2 $callback of function array_all expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.',
939939
36,
940940
],
941941
[
@@ -962,7 +962,7 @@ public function testArrayAnyCallback(): void
962962
30,
963963
],
964964
[
965-
sprintf('Parameter #2 $callback of function array_any expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'string' : '(\'bar\'|\'foo\')'),
965+
'Parameter #2 $callback of function array_any expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.',
966966
36,
967967
],
968968
[
@@ -988,7 +988,7 @@ public function testArrayFindCallback(): void
988988
30,
989989
],
990990
[
991-
sprintf('Parameter #2 $callback of function array_find expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'string' : '(\'bar\'|\'foo\')'),
991+
'Parameter #2 $callback of function array_find expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.',
992992
36,
993993
],
994994
[
@@ -1014,7 +1014,7 @@ public function testArrayFindKeyCallback(): void
10141014
30,
10151015
],
10161016
[
1017-
sprintf('Parameter #2 $callback of function array_find_key expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): %s given.', getenv('PHPSTAN_FNSR') !== '0' && PHP_VERSION_ID >= 80100 ? 'string' : '(\'bar\'|\'foo\')'),
1017+
'Parameter #2 $callback of function array_find_key expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.',
10181018
36,
10191019
],
10201020
[
@@ -2567,6 +2567,14 @@ public function testBug11079(): void
25672567
$this->analyse([__DIR__ . '/data/bug-11079.php'], []);
25682568
}
25692569

2570+
#[RequiresPhp('>= 8.1')]
2571+
public function testBug10612(): void
2572+
{
2573+
$this->checkExplicitMixed = true;
2574+
$this->checkImplicitMixed = false;
2575+
$this->analyse([__DIR__ . '/data/bug-10612.php'], []);
2576+
}
2577+
25702578
#[RequiresPhp('>= 8.1')]
25712579
public function testBug9652(): void
25722580
{
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10612;
4+
5+
/**
6+
* @template K of array-key
7+
* @template V
8+
* @template RK of array-key
9+
* @template RV
10+
* @param array<K, V> $source
11+
* @param callable(V, K): array{RK, RV} $transform
12+
* @return array<RK, RV>
13+
*/
14+
function associate(array $source, callable $transform): array
15+
{
16+
$result = [];
17+
18+
foreach ($source as $key => $value) {
19+
[$newKey, $newValue] = $transform($value, $key);
20+
$result[$newKey] = $newValue;
21+
}
22+
23+
return $result;
24+
}
25+
26+
function test(): void
27+
{
28+
$a = associate(
29+
[[1], [2], [3]],
30+
function (array $entry): array {
31+
\PHPStan\dumpType($entry);
32+
[$key] = $entry;
33+
34+
\PHPStan\dumpType($key);
35+
return [$key, $key];
36+
}
37+
);
38+
39+
\PHPStan\dumpType($a);
40+
}

0 commit comments

Comments
 (0)