Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion issue-bot/src/Console/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use function array_key_exists;
use function array_values;
use function count;
use function escapeshellarg;
use function explode;
Expand Down Expand Up @@ -143,7 +144,7 @@ static function ($result) use (&$results, &$pending, $currentIndex, $total, $run

if (count($results) === $total) {
ksort($results);
$deferred->resolve($results);
$deferred->resolve(array_values($results));
} else {
$runNext();
}
Expand Down
34 changes: 23 additions & 11 deletions src/Analyser/Generics/ClosureSignatureInference.php
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,23 @@ private static function byRefUseNames(Closure $expr): array
private static function bodyYieldsOrInvokes(array $stmts, array $names): bool
{
$stack = $stmts;
while (count($stack) > 0) {
$node = array_pop($stack);
if ($node instanceof Node\FunctionLike || $node instanceof Node\Stmt\ClassLike) {
// closures and arrow functions written in the body: their yields are
// their own, but they can invoke the variables on the body's behalf
$nestedStack = [];
while (count($stack) > 0 || count($nestedStack) > 0) {
$nested = count($stack) === 0;
$node = $nested ? array_pop($nestedStack) : array_pop($stack);
if (!$nested && ($node instanceof Closure || $node instanceof ArrowFunction)) {
$nestedStack[] = $node;
continue;
}
if (
($node instanceof Node\FunctionLike && !$node instanceof Closure && !$node instanceof ArrowFunction)
|| $node instanceof Node\Stmt\ClassLike
) {
continue;
}
if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) {
if (!$nested && ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom)) {
return true;
}
if (
Expand All @@ -564,13 +575,14 @@ private static function bodyYieldsOrInvokes(array $stmts, array $names): bool
}
foreach ($node->getSubNodeNames() as $subNodeName) {
$subNode = $node->$subNodeName;
if ($subNode instanceof Node) {
$stack[] = $subNode;
} elseif (is_array($subNode)) {
foreach ($subNode as $item) {
if (!$item instanceof Node) {
continue;
}
$subNodes = is_array($subNode) ? $subNode : [$subNode];
foreach ($subNodes as $item) {
if (!$item instanceof Node) {
continue;
}
if ($nested) {
$nestedStack[] = $item;
} else {
$stack[] = $item;
}
}
Expand Down
37 changes: 37 additions & 0 deletions tests/PHPStan/Analyser/nsrt/closure-byref-uses-at-invocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -504,4 +504,41 @@ public function referenceReboundByDestructuring(array $list): void
assertType('int', $x);
}

/**
* @param callable(callable(): void, callable(): void): void $then
*/
public function invokesItselfFromNestedClosure(callable $then): void
{
$rejected = false;
$runNext = function () use (&$runNext, &$rejected, $then): void {
assertType('bool', $rejected);
if ($rejected) {
return;
}
$then(
function () use ($runNext): void {
$runNext();
},
function () use (&$rejected): void {
$rejected = true;
},
);
};
$runNext();
}

public function nestedGeneratorKeepsInvocationsFollowed(): void
{
$x = 1;
$c = function () use (&$x): void {
assertType('1', $x);
$g = function () {
yield 1;
};
$x = 'a';
};
$c();
assertType("'a'", $x);
}

}
32 changes: 24 additions & 8 deletions turbo-ext/src/ClosureSignatureInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,20 +583,36 @@ zv::Val anonymousMarker(zval *site, zend_string *name, zend_long variance, zval
stack.push(zv::Val::copyOf(entry.value().deref()));
}
}
/* closures and arrow functions written in the body: their yields are
* their own, but they can invoke the variables on the body's behalf */
zv::Arr nestedStack = zv::Arr::create(0);
for (;;) {
zv::Val node = popNode(stack);
if (node.isUndef()) break;
bool nested = node.isUndef();
if (nested) {
node = popNode(nestedStack);
if (node.isUndef()) break;
}
zval *n = node.raw();
bool is;
bool isNestedFunction;
if (UNEXPECTED(!isA(n, PT_CLASS_CLOSURE_EXPR, isNestedFunction))) return false;
if (!isNestedFunction && UNEXPECTED(!isA(n, PT_CLASS_ARROW_FUNCTION, isNestedFunction))) return false;
if (!nested && isNestedFunction) {
nestedStack.push(std::move(node));
continue;
}
if (UNEXPECTED(!isA(n, PT_CLASS_FUNCTION_LIKE, is))) return false;
if (is) continue;
if (is && !isNestedFunction) continue;
if (UNEXPECTED(!isA(n, PT_CLASS_CLASS_LIKE_STMT, is))) return false;
if (is) continue;
if (UNEXPECTED(!isA(n, PT_CLASS_YIELD, is))) return false;
if (!is && UNEXPECTED(!isA(n, PT_CLASS_YIELD_FROM, is))) return false;
if (is) {
out = true;
return true;
if (!nested) {
if (UNEXPECTED(!isA(n, PT_CLASS_YIELD, is))) return false;
if (!is && UNEXPECTED(!isA(n, PT_CLASS_YIELD_FROM, is))) return false;
if (is) {
out = true;
return true;
}
}
if (UNEXPECTED(!isA(n, PT_CLASS_FUNC_CALL, is))) return false;
if (is) {
Expand All @@ -614,7 +630,7 @@ zv::Val anonymousMarker(zval *site, zend_string *name, zend_long variance, zval
}
}
}
if (UNEXPECTED(!pushSubNodes(n, stack))) return false;
if (UNEXPECTED(!pushSubNodes(n, nested ? nestedStack : stack))) return false;
}
return true;
}
Expand Down
Loading