Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 0 additions & 6 deletions build/fiber.neon

This file was deleted.

4 changes: 0 additions & 4 deletions build/ignore-by-php-version.neon.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@
$includes[] = __DIR__ . '/php-85.neon';
}

if (PHP_VERSION_ID < 80100) {
$includes[] = __DIR__ . '/fiber.neon';
}

$config = [];
$config['includes'] = $includes;

Expand Down
11 changes: 5 additions & 6 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace PHPStan\Analyser;

use PhpParser\Node;
use PHPStan\Analyser\Fiber\FiberScope;
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\ExtensionsCollection;
use PHPStan\Node\Printer\ExprPrinter;
Expand Down Expand Up @@ -39,7 +38,7 @@ public function __construct(
private int|array|null $configPhpVersion,
private $nodeCallback,
private ConstantResolver $constantResolver,
private bool $fiber = false,
private bool $createsNodeCallbackScopes = false,
)
{
}
Expand All @@ -64,8 +63,8 @@ public function create(
): MutatingScope
{
$className = MutatingScope::class;
if ($this->fiber) {
$className = FiberScope::class;
if ($this->createsNodeCallbackScopes) {
$className = NodeCallbackScope::class;
}

return new $className(
Expand Down Expand Up @@ -102,7 +101,7 @@ public function create(
);
}

public function toFiberFactory(): InternalScopeFactory
public function toNodeCallbackScopeFactory(): InternalScopeFactory
{
return new self(
$this->container,
Expand All @@ -122,7 +121,7 @@ public function toFiberFactory(): InternalScopeFactory
);
}

public function toMutatingFactory(): InternalScopeFactory
public function toWalkScopeFactory(): InternalScopeFactory
{
return new self(
$this->container,
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/ArrowFunctionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function supports(Expr $expr): bool
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$arrowFunctionResult = $nodeScopeResolver->processArrowFunctionNode($stmt, $expr, $scope, $storage, $nodeCallback, null);
$this->closureTypeResolver->seedCacheFromArrowFunctionWalk($scope, $expr, $arrowFunctionResult, $storage);
$this->closureTypeResolver->seedCacheFromArrowFunctionWalk($scope, $expr, $arrowFunctionResult);
$result = $arrowFunctionResult->getExpressionResult();

return $this->expressionResultFactory->create(
Expand Down
27 changes: 26 additions & 1 deletion src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,32 @@ public function prepareTarget(
ExpressionContext $context,
AssignTargetWalkMode $mode,
): PreparedAssignTarget
{
// The raw target's node callback fires after the walk below composed and
// stored the target's read result, with the scope captured at entry -
// a synchronously invoked rule (the plain resolver, PHP < 8.1) then
// answers its asks from the storage instead of re-walking on demand,
// same as NodeScopeResolver::processExprNodeInternal().
$prepared = $this->doPrepareTarget($nodeScopeResolver, $scope, $storage, $stmt, $var, $assignedExpr, $nodeCallback, $context, $mode);
$nodeScopeResolver->callNodeCallback($nodeCallback, $var, $mode->enterExpressionAssign() ? $scope->enterExpressionAssign($var) : $scope, $storage);

return $prepared;
}

/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
private function doPrepareTarget(
NodeScopeResolver $nodeScopeResolver,
MutatingScope $scope,
ExpressionResultStorage $storage,
Node\Stmt $stmt,
Expr $var,
Expr $assignedExpr,
callable $nodeCallback,
ExpressionContext $context,
AssignTargetWalkMode $mode,
): PreparedAssignTarget
{
$enterExpressionAssign = $mode->enterExpressionAssign();
$targetReadResult = null;
Expand All @@ -447,7 +473,6 @@ public function prepareTarget(
throwPoints: [],
impurePoints: [],
));
$nodeScopeResolver->callNodeCallback($nodeCallback, $var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope, $storage);
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
Expand Down
12 changes: 9 additions & 3 deletions src/Analyser/ExprHandler/BooleanAndHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope());
}

$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftTruthyScope), $scope, $storage, $context);

return $this->expressionResultFactory->create(
$result = $this->expressionResultFactory->create(
$leftMergedWithRightScope,
beforeScope: $scope,
expr: $expr,
Expand All @@ -288,6 +286,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
truthyScopeCallback: static fn (): MutatingScope => $rightResult->getScope()->filterByTruthyValue($expr->right),
falseyScopeCallback: static fn (): MutatingScope => $leftMergedWithRightScope->filterByFalseyValue($expr),
);
// store before emitting the virtual node: its rules ask about the raw
// expression, and a synchronously invoked rule (the plain resolver,
// PHP < 8.1) must find the result in the storage instead of re-walking
// it on demand; processExprNodeInternal()'s later store is a no-op
$nodeScopeResolver->storeExpressionResult($storage, $expr, $result);
$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftTruthyScope), $scope, $storage, $context);

return $result;
}

}
12 changes: 9 additions & 3 deletions src/Analyser/ExprHandler/BooleanOrHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope());
}

$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftFalseyScope), $scope, $storage, $context);

return $this->expressionResultFactory->create(
$result = $this->expressionResultFactory->create(
$leftMergedWithRightScope,
beforeScope: $scope,
expr: $expr,
Expand All @@ -279,6 +277,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
truthyScopeCallback: static fn (): MutatingScope => $leftMergedWithRightScope->filterByTruthyValue($expr),
falseyScopeCallback: static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr->right),
);
// store before emitting the virtual node: its rules ask about the raw
// expression, and a synchronously invoked rule (the plain resolver,
// PHP < 8.1) must find the result in the storage instead of re-walking
// it on demand; processExprNodeInternal()'s later store is a no-op
$nodeScopeResolver->storeExpressionResult($storage, $expr, $result);
$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftFalseyScope), $scope, $storage, $context);

return $result;
}

}
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/ClosureHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function supports(Expr $expr): bool
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$processClosureResult = $nodeScopeResolver->processClosureNode($stmt, $expr, $scope, $storage, $nodeCallback, $context, null);
$this->closureTypeResolver->seedCacheFromClosureWalk($scope, $expr, $processClosureResult, $storage);
$this->closureTypeResolver->seedCacheFromClosureWalk($scope, $expr, $processClosureResult);

return $this->expressionResultFactory->create(
$processClosureResult->applyByRefUseScope($processClosureResult->getScope()),
Expand Down
23 changes: 4 additions & 19 deletions src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,18 +368,8 @@ public function buildClosureTypeForArrowFunction(
* slot is seeded with the same build, keyed exactly as the
* promoted-scope ask computes its key.
*/
public function seedCacheFromClosureWalk(MutatingScope $scope, Node\Expr\Closure $expr, ProcessClosureResult $processClosureResult, ExpressionResultStorage $storage): void
public function seedCacheFromClosureWalk(MutatingScope $scope, Node\Expr\Closure $expr, ProcessClosureResult $processClosureResult): void
{
// a parked fiber may still append to the walk's gathered data - the
// invalidate expressions of a write like $this->prop[] = ... arrive
// only when the fiber flushes (see the invalidate-expressions note in
// NodeScopeResolver::processArgs()). Seed only when nothing is parked,
// so a seeded entry is never incomplete; otherwise the lazy ask keeps
// re-walking with the fibers flushed, as before.
if ($storage->pendingFibers !== []) {
return;
}

// for array_map() callbacks and immediately invoked closures this
// delegates to a getClosureType() walk with the call-site parameter
// types; either way the phpdoc build lands in the cache under the
Expand Down Expand Up @@ -415,13 +405,8 @@ public function seedCacheFromClosureWalk(MutatingScope $scope, Node\Expr\Closure
* so the native-flavour slot is seeded with its own build reading the
* walked body's native types.
*/
public function seedCacheFromArrowFunctionWalk(MutatingScope $scope, ArrowFunction $expr, ProcessArrowFunctionResult $arrowFunctionResult, ExpressionResultStorage $storage): void
public function seedCacheFromArrowFunctionWalk(MutatingScope $scope, ArrowFunction $expr, ProcessArrowFunctionResult $arrowFunctionResult): void
{
// see the parked-fiber note in seedCacheFromClosureWalk()
if ($storage->pendingFibers !== []) {
return;
}

$this->buildClosureTypeForArrowFunction(
$scope,
$expr,
Expand Down Expand Up @@ -584,7 +569,7 @@ private function buildClosureTypeFromClosureWalk(
continue;
}

$readScope = $returnScope->toMutatingScope();
$readScope = $returnScope->toWalkScope();
if ($native) {
$readScope = $readScope->doNotTreatPhpDocTypesAsCertain();
}
Expand All @@ -611,7 +596,7 @@ private function buildClosureTypeFromClosureWalk(
$keyTypes = [];
$valueTypes = [];
foreach ($yieldStatements as [$yieldNode, $yieldScope]) {
$readScope = $yieldScope->toMutatingScope();
$readScope = $yieldScope->toWalkScope();
if ($native) {
$readScope = $readScope->doNotTreatPhpDocTypesAsCertain();
}
Expand Down
17 changes: 0 additions & 17 deletions src/Analyser/ExpressionResultStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

namespace PHPStan\Analyser;

use Fiber;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PHPStan\Analyser\Fiber\ExpressionResultRequest;
use PHPStan\Analyser\Fiber\ParkFiberRequest;
use PHPStan\Turbo\ShadowedByTurboExtension;
use function spl_object_id;

Expand All @@ -25,19 +21,6 @@ final class ExpressionResultStorage
/** @var array<int, Scope> */
private array $scopesById = [];

/**
* Keyed by spl_object_id() of the requested Expr, so resolving a stored
* before-scope touches only the fibers waiting for that expression.
* The request object keeps the Expr alive, so its id cannot be reused
* while the entry exists.
*
* @var array<int, non-empty-list<array{fiber: Fiber<mixed, ExpressionResult|array{callable(Node $node, Scope $scope): void, Node, MutatingScope}, null, ExpressionResultRequest|ParkFiberRequest>, request: ExpressionResultRequest}>>
*/
public array $pendingFibers = [];

/** @var list<Fiber<mixed, ExpressionResult|array{callable(Node $node, Scope $scope): void, Node, MutatingScope}, null, ExpressionResultRequest|ParkFiberRequest>> */
public array $parkedFibers = [];

public function duplicate(): self
{
$new = new self();
Expand Down
48 changes: 48 additions & 0 deletions src/Analyser/ExpressionResultStorageStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\AutowiredService;
use function array_key_last;
use function array_pop;
use function count;

/**
* The ExpressionResultStorage a node callback's type asks resolve against.
*
* NodeScopeResolver::callNodeCallback() pushes the emitting walk's
* storage for the duration of the callback and always pops it in a finally
* block - the same association a suspended fiber's request had with the
* frame that would resolve it. Scopes deliberately do not reference the
* storage directly - it would create a reference cycle (storage -> scopes ->
* storage) that never gets collected because the cycle collector is disabled
* in bin/phpstan. An ask outside any running callback simply misses here and
* resolves on demand.
*/
#[AutowiredService]
final class ExpressionResultStorageStack
{

/** @var list<ExpressionResultStorage> */
private array $stack = [];

public function push(ExpressionResultStorage $storage): void
{
$this->stack[] = $storage;
}

public function pop(): void
{
array_pop($this->stack);
}

public function getCurrent(): ?ExpressionResultStorage
{
if (count($this->stack) === 0) {
return null;
}

return $this->stack[array_key_last($this->stack)];
}

}
14 changes: 0 additions & 14 deletions src/Analyser/Fiber/ExpressionResultRequest.php

This file was deleted.

Loading
Loading