Skip to content

Commit 609686a

Browse files
committed
FNSR
1 parent 0f70a9f commit 609686a

13 files changed

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

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->getScope()->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->getScope()->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->getScope()->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)