Skip to content

Commit fad35b7

Browse files
staabmphpstan-bot
authored andcommitted
Fix by-ref variable type not propagated between closure arguments
- When multiple closures sharing a by-ref variable are passed as arguments to the same function call, subsequent closures now see by-ref modifications from earlier closures - Applied deferred by-ref closure results to the scope used for closure body analysis without leaking to non-closure argument evaluation - New regression test in tests/PHPStan/Analyser/nsrt/bug-14096.php Closes phpstan/phpstan#14096
1 parent 801f28f commit fad35b7

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3330,7 +3330,11 @@ public function processArgs(
33303330
}
33313331

33323332
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
3333-
$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context, $parameterType ?? null);
3333+
$closureScopeToPass = $scopeToPass;
3334+
foreach ($deferredByRefClosureResults as $deferredClosureResult) {
3335+
$closureScopeToPass = $deferredClosureResult->applyByRefUseScope($closureScopeToPass);
3336+
}
3337+
$closureResult = $this->processClosureNode($stmt, $arg->value, $closureScopeToPass, $storage, $nodeCallback, $context, $parameterType ?? null);
33343338
if ($this->callCallbackImmediately($parameter, $parameterType, $calleeReflection)) {
33353339
$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()));
33363340
$impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints());
@@ -3348,7 +3352,7 @@ public function processArgs(
33483352
$uses[] = $use->var->name;
33493353
}
33503354

3351-
$scope = $closureResult->getScope();
3355+
$scope = $scopeToPass;
33523356
$deferredByRefClosureResults[] = $closureResult;
33533357
$invalidateExpressions = $closureResult->getInvalidateExpressions();
33543358
if ($restoreThisScope !== null) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14096;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class AbstractView {}
8+
class App {}
9+
interface ServerRequestInterface {}
10+
11+
class Test
12+
{
13+
/**
14+
* @template T of App
15+
*
16+
* @param \Closure(ServerRequestInterface): T $createAppFx
17+
* @param \Closure(T): ServerRequestInterface $simulateRequestFx
18+
*
19+
* @return T
20+
*/
21+
protected function simulateAppCallback(\Closure $createAppFx, \Closure $simulateRequestFx): App
22+
{
23+
$appBase = $createAppFx(new class() implements ServerRequestInterface {});
24+
$request = $simulateRequestFx($appBase);
25+
26+
$app = $createAppFx($request);
27+
28+
return $app;
29+
}
30+
31+
/**
32+
* @template T of AbstractView
33+
*
34+
* @param \Closure(ServerRequestInterface): T $createViewFx
35+
* @param \Closure(T): ServerRequestInterface $simulateRequestFx
36+
*
37+
* @return T
38+
*/
39+
protected function simulateViewCallback(\Closure $createViewFx, \Closure $simulateRequestFx): AbstractView
40+
{
41+
$view = null;
42+
$this->simulateAppCallback(static function (ServerRequestInterface $request) use ($createViewFx, &$view) {
43+
$view = $createViewFx($request);
44+
45+
return new App();
46+
}, static function () use ($simulateRequestFx, &$view) {
47+
assertType('T of Bug14096\AbstractView (method Bug14096\Test::simulateViewCallback(), argument)|null', $view);
48+
49+
return $simulateRequestFx($view);
50+
});
51+
52+
assertType('T of Bug14096\AbstractView (method Bug14096\Test::simulateViewCallback(), argument)|null', $view);
53+
54+
return $view;
55+
}
56+
}

0 commit comments

Comments
 (0)