Skip to content

Commit d37a38e

Browse files
committed
FNSR
2 parents 38ea1ab + 2226259 commit d37a38e

36 files changed

+2175
-472
lines changed

build/PHPStan/Build/FinalClassRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use PhpParser\Modifiers;
66
use PhpParser\Node;
7+
use PHPStan\Analyser\MutatingScope;
8+
use PHPStan\Analyser\NodeScopeResolver;
79
use PHPStan\Analyser\Scope;
810
use PHPStan\File\FileHelper;
911
use PHPStan\Node\InClassNode;
@@ -55,6 +57,8 @@ public function processNode(Node $node, Scope $scope): array
5557
ExtendedFunctionVariant::class,
5658
DummyParameter::class,
5759
PhpFunctionFromParserNodeReflection::class,
60+
NodeScopeResolver::class,
61+
MutatingScope::class,
5862
], true)) {
5963
return [];
6064
}

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/AnalyserResultFinalizer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $
118118
$linesToIgnore = $allLinesToIgnore[$file] ?? [];
119119
$unmatchedLineIgnores = $allUnmatchedLineIgnores[$file] ?? [];
120120
$localIgnoresProcessorResult = $this->localIgnoresProcessor->process(
121-
[$tempCollectorError],
121+
[[$tempCollectorError, 0]],
122122
$linesToIgnore,
123123
$unmatchedLineIgnores,
124124
);
125-
foreach ($localIgnoresProcessorResult->getFileErrors() as $error) {
125+
foreach ($localIgnoresProcessorResult->getFileErrors() as [$error]) {
126126
$errors[] = $error;
127127
$collectorErrors[] = $error;
128128
}

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

0 commit comments

Comments
 (0)