Skip to content

Commit 162f630

Browse files
committed
FNSR
1 parent 2374d29 commit 162f630

14 files changed

+483
-12
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ extensions:
238238
autowiredAttributeServices: PHPStan\DependencyInjection\AutowiredAttributeServicesExtension
239239
validateServiceTags: PHPStan\DependencyInjection\ValidateServiceTagsExtension
240240
gnsr: PHPStan\DependencyInjection\GnsrExtension
241+
fnsr: PHPStan\DependencyInjection\FnsrExtension
241242

242243
autowiredAttributeServices:
243244
level: null

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Analyser;
44

55
use PhpParser\Node;
6+
use PHPStan\Analyser\Fiber\FiberScope;
67
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
78
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
89
use PHPStan\Node\Printer\ExprPrinter;
@@ -38,6 +39,7 @@ public function __construct(
3839
private int|array|null $configPhpVersion,
3940
private $nodeCallback,
4041
private ConstantResolver $constantResolver,
42+
private bool $fiber = false,
4143
)
4244
{
4345
}
@@ -61,7 +63,12 @@ public function create(
6163
bool $nativeTypesPromoted = false,
6264
): MutatingScope
6365
{
64-
return new MutatingScope(
66+
$className = MutatingScope::class;
67+
if ($this->fiber) {
68+
$className = FiberScope::class;
69+
}
70+
71+
return new $className(
6572
$this,
6673
$this->reflectionProvider,
6774
$this->initializerExprTypeResolver,
@@ -97,4 +104,48 @@ public function create(
97104
);
98105
}
99106

107+
public function toFiberFactory(): InternalScopeFactory
108+
{
109+
return new self(
110+
$this->reflectionProvider,
111+
$this->initializerExprTypeResolver,
112+
$this->dynamicReturnTypeExtensionRegistryProvider,
113+
$this->expressionTypeResolverExtensionRegistryProvider,
114+
$this->exprPrinter,
115+
$this->typeSpecifier,
116+
$this->propertyReflectionFinder,
117+
$this->parser,
118+
$this->nodeScopeResolver,
119+
$this->richerScopeGetTypeHelper,
120+
$this->phpVersion,
121+
$this->attributeReflectionFactory,
122+
$this->configPhpVersion,
123+
$this->nodeCallback,
124+
$this->constantResolver,
125+
true,
126+
);
127+
}
128+
129+
public function toMutatingFactory(): InternalScopeFactory
130+
{
131+
return new self(
132+
$this->reflectionProvider,
133+
$this->initializerExprTypeResolver,
134+
$this->dynamicReturnTypeExtensionRegistryProvider,
135+
$this->expressionTypeResolverExtensionRegistryProvider,
136+
$this->exprPrinter,
137+
$this->typeSpecifier,
138+
$this->propertyReflectionFinder,
139+
$this->parser,
140+
$this->nodeScopeResolver,
141+
$this->richerScopeGetTypeHelper,
142+
$this->phpVersion,
143+
$this->attributeReflectionFactory,
144+
$this->configPhpVersion,
145+
$this->nodeCallback,
146+
$this->constantResolver,
147+
false,
148+
);
149+
}
150+
100151
}

src/Analyser/ExpressionResult.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ final class ExpressionResult
2323
*/
2424
public function __construct(
2525
private MutatingScope $scope,
26+
private MutatingScope $beforeScope,
2627
private bool $hasYield,
2728
private bool $isAlwaysTerminating,
2829
private array $throwPoints,
@@ -40,6 +41,11 @@ public function getScope(): MutatingScope
4041
return $this->scope;
4142
}
4243

44+
public function getBeforeScope(): MutatingScope
45+
{
46+
return $this->beforeScope;
47+
}
48+
4349
public function hasYield(): bool
4450
{
4551
return $this->hasYield;

src/Analyser/ExpressionResultStorage.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Analyser;
44

55
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Fiber\ExpressionAnalysisRequest;
67
use SplObjectStorage;
78

89
final class ExpressionResultStorage
@@ -11,6 +12,9 @@ final class ExpressionResultStorage
1112
/** @var SplObjectStorage<Expr, ExpressionResult> */
1213
private SplObjectStorage $results;
1314

15+
/** @var array<array{fiber: Fiber<mixed, ExpressionResult|null, null, ExpressionAnalysisRequest|NodeCallbackRequest>, request: ExpressionAnalysisRequest}> */
16+
public array $pendingFibers = [];
17+
1418
public function __construct()
1519
{
1620
$this->results = new SplObjectStorage();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Fiber;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\MutatingScope;
7+
8+
final class ExpressionAnalysisRequest
9+
{
10+
11+
public function __construct(public readonly Expr $expr, public readonly MutatingScope $scope)
12+
{
13+
}
14+
15+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Fiber;
4+
5+
use Fiber;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr;
8+
use PHPStan\Analyser\ExpressionContext;
9+
use PHPStan\Analyser\ExpressionResult;
10+
use PHPStan\Analyser\ExpressionResultStorage;
11+
use PHPStan\Analyser\MutatingScope;
12+
use PHPStan\Analyser\NodeScopeResolver;
13+
use PHPStan\Analyser\NoopNodeCallback;
14+
use PHPStan\DependencyInjection\AutowiredService;
15+
use PHPStan\ShouldNotHappenException;
16+
use function get_class;
17+
use function get_debug_type;
18+
use function sprintf;
19+
20+
#[AutowiredService(as: FiberNodeScopeResolver::class)]
21+
final class FiberNodeScopeResolver extends NodeScopeResolver
22+
{
23+
24+
protected function callNodeCallback(
25+
callable $nodeCallback,
26+
Node $node,
27+
MutatingScope $scope,
28+
ExpressionResultStorage $storage,
29+
): void
30+
{
31+
$fiber = new Fiber(static function () use ($node, $scope, $nodeCallback) {
32+
$nodeCallback($node, $scope->toFiberScope());
33+
});
34+
$request = $fiber->start();
35+
$this->runFiberForNodeCallback($storage, $fiber, $request);
36+
}
37+
38+
private function runFiberForNodeCallback(
39+
ExpressionResultStorage $storage,
40+
Fiber $fiber,
41+
?ExpressionAnalysisRequest $request,
42+
): void
43+
{
44+
while (!$fiber->isTerminated()) {
45+
if ($request instanceof ExpressionAnalysisRequest) {
46+
$result = $storage->findResult($request->expr);
47+
if ($result !== null) {
48+
$request = $fiber->resume($result);
49+
continue;
50+
}
51+
52+
$storage->pendingFibers[] = [
53+
'fiber' => $fiber,
54+
'request' => $request,
55+
];
56+
return;
57+
}
58+
59+
throw new ShouldNotHappenException(
60+
'Unknown fiber suspension: ' . get_debug_type($request),
61+
);
62+
}
63+
64+
if ($request !== null) {
65+
throw new ShouldNotHappenException(
66+
'Fiber terminated but we did not handle its request ' . get_debug_type($request),
67+
);
68+
}
69+
}
70+
71+
protected function processPendingFibers(ExpressionResultStorage $storage): void
72+
{
73+
foreach ($storage->pendingFibers as $pending) {
74+
$request = $pending['request'];
75+
$result = $storage->findResult($request->expr);
76+
77+
if ($result !== null) {
78+
throw new ShouldNotHappenException('Pending fibers at the end should be about synthetic nodes');
79+
}
80+
81+
$this->processExprNode(
82+
new Node\Stmt\Expression($request->expr),
83+
$request->expr,
84+
$request->scope->toMutatingScope(),
85+
$storage,
86+
new NoopNodeCallback(),
87+
ExpressionContext::createTopLevel(),
88+
);
89+
if ($storage->findResult($request->expr) === null) {
90+
throw new ShouldNotHappenException(sprintf('processExprNode should have stored the result of %s on line %s', get_class($request->expr), $request->expr->getStartLine()));
91+
}
92+
$this->processPendingFibers($storage);
93+
94+
// Break and restart the loop since the array may have been modified
95+
return;
96+
}
97+
}
98+
99+
protected function processPendingFibersForRequestedExpr(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $result): void
100+
{
101+
$restartLoop = true;
102+
103+
while ($restartLoop) {
104+
$restartLoop = false;
105+
106+
foreach ($storage->pendingFibers as $key => $pending) {
107+
$request = $pending['request'];
108+
if ($request->expr !== $expr) {
109+
continue;
110+
}
111+
112+
unset($storage->pendingFibers[$key]);
113+
$restartLoop = true;
114+
115+
$fiber = $pending['fiber'];
116+
$request = $fiber->resume($result);
117+
$this->runFiberForNodeCallback($storage, $fiber, $request);
118+
119+
// Break and restart the loop since the array may have been modified
120+
break;
121+
}
122+
}
123+
}
124+
125+
}

src/Analyser/Fiber/FiberScope.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Fiber;
4+
5+
use Fiber;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\Analyser\ExpressionResult;
8+
use PHPStan\Analyser\MutatingScope;
9+
use PHPStan\Type\Type;
10+
11+
final class FiberScope extends MutatingScope
12+
{
13+
14+
/** @api */
15+
public function getType(Expr $node): Type
16+
{
17+
/** @var ExpressionResult $exprResult */
18+
$exprResult = Fiber::suspend(
19+
new ExpressionAnalysisRequest($node, $this),
20+
);
21+
22+
return $exprResult->getBeforeScope()->toMutatingScope()->getType($node);
23+
}
24+
25+
/** @api */
26+
public function getNativeType(Expr $expr): Type
27+
{
28+
/** @var ExpressionResult $exprResult */
29+
$exprResult = Fiber::suspend(
30+
new ExpressionAnalysisRequest($expr, $this),
31+
);
32+
33+
return $exprResult->getBeforeScope()->toMutatingScope()->getNativeType($expr);
34+
}
35+
36+
public function getKeepVoidType(Expr $node): Type
37+
{
38+
/** @var ExpressionResult $exprResult */
39+
$exprResult = Fiber::suspend(
40+
new ExpressionAnalysisRequest($node, $this),
41+
);
42+
43+
return $exprResult->getBeforeScope()->toMutatingScope()->getKeepVoidType($node);
44+
}
45+
46+
}

src/Analyser/InternalScopeFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ public function create(
3939
bool $nativeTypesPromoted = false,
4040
): MutatingScope;
4141

42+
public function toFiberFactory(): self;
43+
44+
public function toMutatingFactory(): self;
45+
4246
}

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Analyser;
44

55
use PhpParser\Node;
6+
use PHPStan\Analyser\Fiber\FiberScope;
67
use PHPStan\DependencyInjection\Container;
78
use PHPStan\DependencyInjection\GenerateFactory;
89
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
@@ -29,6 +30,7 @@ final class LazyInternalScopeFactory implements InternalScopeFactory
2930
public function __construct(
3031
private Container $container,
3132
private $nodeCallback,
33+
private bool $fiber = false,
3234
)
3335
{
3436
$this->phpVersion = $this->container->getParameter('phpVersion');
@@ -53,7 +55,12 @@ public function create(
5355
bool $nativeTypesPromoted = false,
5456
): MutatingScope
5557
{
56-
return new MutatingScope(
58+
$className = MutatingScope::class;
59+
if ($this->fiber) {
60+
$className = FiberScope::class;
61+
}
62+
63+
return new $className(
5764
$this,
5865
$this->container->getByType(ReflectionProvider::class),
5966
$this->container->getByType(InitializerExprTypeResolver::class),
@@ -89,4 +96,14 @@ public function create(
8996
);
9097
}
9198

99+
public function toFiberFactory(): InternalScopeFactory
100+
{
101+
return new self($this->container, $this->nodeCallback, true);
102+
}
103+
104+
public function toMutatingFactory(): InternalScopeFactory
105+
{
106+
return new self($this->container, $this->nodeCallback, false);
107+
}
108+
92109
}

0 commit comments

Comments
 (0)