Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
35cb4d0
ExpressionResultStorage
ondrejmirtes Dec 5, 2025
66e4f13
VirtualAssignNodeCallback + ShallowNodeCallback
ondrejmirtes Dec 10, 2025
3d9a776
Fix double processing of match with enum condition
ondrejmirtes Dec 11, 2025
0f03b30
First class callable name was not processed
ondrejmirtes Dec 11, 2025
edb1a6c
Prevent double processing of `StaticCall::$name`
ondrejmirtes Dec 11, 2025
3beb8c6
Fix match analysis
ondrejmirtes Dec 11, 2025
141c443
Add currentlyAssignedExpressions and currentlyAllowedUndefinedExpress…
ondrejmirtes Dec 11, 2025
7d51e73
Arg value inherited currentlyAllowedUndefinedExpressions because of a…
ondrejmirtes Dec 11, 2025
719a973
GNSR is dead, long live FNSR
ondrejmirtes Dec 3, 2025
040e9ce
FNSR preparation
ondrejmirtes Dec 11, 2025
2073668
FNSR
ondrejmirtes Dec 11, 2025
3d361fb
Do not change what `$expr` is so that it's added to the storage
ondrejmirtes Dec 15, 2025
283136a
Fix cache in LegacyNodeScopeResolverTest
ondrejmirtes Dec 15, 2025
d83934e
Issue bot - use FNSR
ondrejmirtes Dec 15, 2025
6a28cf0
Fix
ondrejmirtes Dec 16, 2025
707f363
Keep errors ordering even when nodeCallback executed out of order
ondrejmirtes Dec 16, 2025
088c69c
toFiberScope, toMutatingScope - take advantage of inheritance and pol…
ondrejmirtes Dec 17, 2025
80d12db
Introduce DeepNodeCloner
ondrejmirtes Dec 17, 2025
8708b07
Fix NullsafePropertyFetch and NullsafeMethodCall with FiberScope
ondrejmirtes Dec 17, 2025
2604680
Missing printer methods for BooleanOrNode and BooleanAndNode
ondrejmirtes Dec 17, 2025
7b4c7b9
Fix LegacyNodeScopeResolverTest with trait uses
ondrejmirtes Dec 17, 2025
f4e17d0
Fix Coalesce with FiberScope
ondrejmirtes Dec 17, 2025
0223f74
Fix Isset and Empty with FiberScope
ondrejmirtes Dec 17, 2025
dbf5200
Fix storing results of first class callable expressions
ondrejmirtes Dec 17, 2025
01efece
Fix FiberScope with dynamic variable names
ondrejmirtes Dec 17, 2025
6583bdd
Fix duplicate Assign result storing
ondrejmirtes Dec 18, 2025
4147d7e
Do not pile up too many fibers for synthetic nodes
ondrejmirtes Dec 18, 2025
7a04260
Autowire FiberNodeScopeResolver where NodeScopeResolver is expected (…
ondrejmirtes Dec 18, 2025
b0bc22b
Fix
ondrejmirtes Dec 18, 2025
239f07e
Add internal `toMutatingScope` on `Scope` interface
ondrejmirtes Dec 19, 2025
5a285c8
NodeScopeResolver and MutatingScope can be non-final
ondrejmirtes Dec 19, 2025
2226259
Revert "Issue bot - use FNSR"
ondrejmirtes Dec 19, 2025
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
4 changes: 4 additions & 0 deletions build/PHPStan/Build/FinalClassRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use PhpParser\Modifiers;
use PhpParser\Node;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\File\FileHelper;
use PHPStan\Node\InClassNode;
Expand Down Expand Up @@ -55,6 +57,8 @@ public function processNode(Node $node, Scope $scope): array
ExtendedFunctionVariant::class,
DummyParameter::class,
PhpFunctionFromParserNodeReflection::class,
NodeScopeResolver::class,
MutatingScope::class,
], true)) {
return [];
}
Expand Down
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ extensions:
autowiredAttributeServices: PHPStan\DependencyInjection\AutowiredAttributeServicesExtension
validateServiceTags: PHPStan\DependencyInjection\ValidateServiceTagsExtension
gnsr: PHPStan\DependencyInjection\GnsrExtension
fnsr: PHPStan\DependencyInjection\FnsrExtension

autowiredAttributeServices:
level: null
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/AnalyserResultFinalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $
$linesToIgnore = $allLinesToIgnore[$file] ?? [];
$unmatchedLineIgnores = $allUnmatchedLineIgnores[$file] ?? [];
$localIgnoresProcessorResult = $this->localIgnoresProcessor->process(
[$tempCollectorError],
[[$tempCollectorError, 0]],
$linesToIgnore,
$unmatchedLineIgnores,
);
foreach ($localIgnoresProcessorResult->getFileErrors() as $error) {
foreach ($localIgnoresProcessorResult->getFileErrors() as [$error]) {
$errors[] = $error;
$collectorErrors[] = $error;
}
Expand Down
53 changes: 52 additions & 1 deletion src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Analyser;

use PhpParser\Node;
use PHPStan\Analyser\Fiber\FiberScope;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
use PHPStan\Node\Printer\ExprPrinter;
Expand Down Expand Up @@ -38,6 +39,7 @@ public function __construct(
private int|array|null $configPhpVersion,
private $nodeCallback,
private ConstantResolver $constantResolver,
private bool $fiber = false,
)
{
}
Expand All @@ -61,7 +63,12 @@ public function create(
bool $nativeTypesPromoted = false,
): MutatingScope
{
return new MutatingScope(
$className = MutatingScope::class;
if ($this->fiber) {
$className = FiberScope::class;
}

return new $className(
$this,
$this->reflectionProvider,
$this->initializerExprTypeResolver,
Expand Down Expand Up @@ -97,4 +104,48 @@ public function create(
);
}

public function toFiberFactory(): InternalScopeFactory
{
return new self(
$this->reflectionProvider,
$this->initializerExprTypeResolver,
$this->dynamicReturnTypeExtensionRegistryProvider,
$this->expressionTypeResolverExtensionRegistryProvider,
$this->exprPrinter,
$this->typeSpecifier,
$this->propertyReflectionFinder,
$this->parser,
$this->nodeScopeResolver,
$this->richerScopeGetTypeHelper,
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$this->nodeCallback,
$this->constantResolver,
true,
);
}

public function toMutatingFactory(): InternalScopeFactory
{
return new self(
$this->reflectionProvider,
$this->initializerExprTypeResolver,
$this->dynamicReturnTypeExtensionRegistryProvider,
$this->expressionTypeResolverExtensionRegistryProvider,
$this->exprPrinter,
$this->typeSpecifier,
$this->propertyReflectionFinder,
$this->parser,
$this->nodeScopeResolver,
$this->richerScopeGetTypeHelper,
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$this->nodeCallback,
$this->constantResolver,
false,
);
}

}
6 changes: 6 additions & 0 deletions src/Analyser/ExpressionResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ final class ExpressionResult
*/
public function __construct(
private MutatingScope $scope,
private MutatingScope $beforeScope,
private bool $hasYield,
private bool $isAlwaysTerminating,
private array $throwPoints,
Expand All @@ -40,6 +41,11 @@ public function getScope(): MutatingScope
return $this->scope;
}

public function getBeforeScope(): MutatingScope
{
return $this->beforeScope;
}

public function hasYield(): bool
{
return $this->hasYield;
Expand Down
47 changes: 47 additions & 0 deletions src/Analyser/ExpressionResultStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use Fiber;
use PhpParser\Node\Expr;
use PHPStan\Analyser\Fiber\ExpressionAnalysisRequest;
use PHPStan\ShouldNotHappenException;
use SplObjectStorage;
use function get_class;
use function sprintf;

final class ExpressionResultStorage
{

/** @var SplObjectStorage<Expr, ExpressionResult> */
private SplObjectStorage $results;

/** @var array<array{fiber: Fiber<mixed, ExpressionResult, null, ExpressionAnalysisRequest>, request: ExpressionAnalysisRequest}> */
public array $pendingFibers = [];

Check failure on line 20 in src/Analyser/ExpressionResultStorage.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Property PHPStan\Analyser\ExpressionResultStorage::$pendingFibers has unknown class Fiber as its type.

Check failure on line 20 in src/Analyser/ExpressionResultStorage.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Property PHPStan\Analyser\ExpressionResultStorage::$pendingFibers has unknown class Fiber as its type.

Check failure on line 20 in src/Analyser/ExpressionResultStorage.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Property PHPStan\Analyser\ExpressionResultStorage::$pendingFibers has unknown class Fiber as its type.

public function __construct()
{
$this->results = new SplObjectStorage();
}

public function duplicate(): self
{
$new = new self();
$new->results->addAll($this->results);
return $new;
}

public function storeResult(Expr $expr, ExpressionResult $result): void
{
if (isset($this->results[$expr])) {
throw new ShouldNotHappenException(sprintf('already stored %s on line %d', get_class($expr), $expr->getStartLine()));
}
$this->results[$expr] = $result;
}

public function findResult(Expr $expr): ?ExpressionResult
{
return $this->results[$expr] ?? null;
}

}
15 changes: 15 additions & 0 deletions src/Analyser/Fiber/ExpressionAnalysisRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Fiber;

use PhpParser\Node\Expr;
use PHPStan\Analyser\MutatingScope;

final class ExpressionAnalysisRequest
{

public function __construct(public readonly Expr $expr, public readonly MutatingScope $scope)
{
}

}
132 changes: 132 additions & 0 deletions src/Analyser/Fiber/FiberNodeScopeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Fiber;

use Fiber;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\ShouldNotHappenException;
use function get_class;
use function get_debug_type;
use function sprintf;

#[AutowiredService(as: FiberNodeScopeResolver::class)]
final class FiberNodeScopeResolver extends NodeScopeResolver
{

/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
protected function callNodeCallback(
callable $nodeCallback,
Node $node,

Check failure on line 30 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Instantiated class Fiber not found.
MutatingScope $scope,
ExpressionResultStorage $storage,
): void

Check failure on line 33 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Call to method start() on an unknown class Fiber.
{
$fiber = new Fiber(static function () use ($node, $scope, $nodeCallback) {

Check failure on line 35 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Instantiated class Fiber not found.

Check failure on line 35 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Instantiated class Fiber not found.
$nodeCallback($node, $scope->toFiberScope());
});
$request = $fiber->start();

Check failure on line 38 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Call to method start() on an unknown class Fiber.

Check failure on line 38 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Call to method start() on an unknown class Fiber.
$this->runFiberForNodeCallback($storage, $fiber, $request);
}

Check failure on line 40 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.

Check failure on line 40 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.

/**

Check failure on line 42 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Call to method isTerminated() on an unknown class Fiber.
* @param Fiber<mixed, ExpressionResult, null, ExpressionAnalysisRequest> $fiber
*/
private function runFiberForNodeCallback(
ExpressionResultStorage $storage,

Check failure on line 46 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Call to method resume() on an unknown class Fiber.
Fiber $fiber,

Check failure on line 47 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.

Check failure on line 47 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.

Check failure on line 47 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.

Check failure on line 47 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Parameter $fiber of method PHPStan\Analyser\Fiber\FiberNodeScopeResolver::runFiberForNodeCallback() has invalid type Fiber.
?ExpressionAnalysisRequest $request,
): void
{
while (!$fiber->isTerminated()) {

Check failure on line 51 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Call to method isTerminated() on an unknown class Fiber.

Check failure on line 51 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Call to method isTerminated() on an unknown class Fiber.
if ($request instanceof ExpressionAnalysisRequest) {
$result = $storage->findResult($request->expr);
if ($result !== null) {
$request = $fiber->resume($result);

Check failure on line 55 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Call to method resume() on an unknown class Fiber.

Check failure on line 55 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Call to method resume() on an unknown class Fiber.
continue;
}

$storage->pendingFibers[] = [
'fiber' => $fiber,
'request' => $request,
];
return;
}

throw new ShouldNotHappenException(
'Unknown fiber suspension: ' . get_debug_type($request),
);
}

if ($request !== null) {
throw new ShouldNotHappenException(
'Fiber terminated but we did not handle its request ' . get_debug_type($request),
);
}
}

protected function processPendingFibers(ExpressionResultStorage $storage): void
{
foreach ($storage->pendingFibers as $pending) {
$request = $pending['request'];
$result = $storage->findResult($request->expr);

if ($result !== null) {
throw new ShouldNotHappenException('Pending fibers at the end should be about synthetic nodes');
}

$this->processExprNode(
new Node\Stmt\Expression($request->expr),
$request->expr,
$request->scope->toMutatingScope(),
$storage,
new NoopNodeCallback(),
ExpressionContext::createTopLevel(),
);
if ($storage->findResult($request->expr) === null) {
throw new ShouldNotHappenException(sprintf('processExprNode should have stored the result of %s on line %s', get_class($request->expr), $request->expr->getStartLine()));
}
$this->processPendingFibers($storage);

// Break and restart the loop since the array may have been modified
return;
}
}

protected function processPendingFibersForRequestedExpr(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $result): void
{
$restartLoop = true;

while ($restartLoop) {
$restartLoop = false;

foreach ($storage->pendingFibers as $key => $pending) {

Check failure on line 113 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Call to method resume() on an unknown class Fiber.
$request = $pending['request'];
if ($request->expr !== $expr) {
continue;
}

unset($storage->pendingFibers[$key]);
$restartLoop = true;

$fiber = $pending['fiber'];
$request = $fiber->resume($result);

Check failure on line 123 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Call to method resume() on an unknown class Fiber.

Check failure on line 123 in src/Analyser/Fiber/FiberNodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Call to method resume() on an unknown class Fiber.
$this->runFiberForNodeCallback($storage, $fiber, $request);

// Break and restart the loop since the array may have been modified
break;
}
}
}

}
Loading
Loading