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
56 changes: 30 additions & 26 deletions src/Analyser/ExprHandler/FuncCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use PHPStan\Analyser\ExprHandler\Helper\EarlyTerminatingCallHelper;
use PHPStan\Analyser\ExprHandler\Helper\OutputBufferHelper;
use PHPStan\Analyser\ExprHandler\Helper\VoidToNullTypeTransformer;
use PHPStan\Analyser\GatheringNodeCallback;
use PHPStan\Analyser\ImpurePoint;
use PHPStan\Analyser\InternalThrowPoint;
use PHPStan\Analyser\MutatingScope;
Expand Down Expand Up @@ -253,36 +254,39 @@
$arrayWalkOriginalArrayType = $scope->getType($arrayWalkArrayArg);
$arrayWalkOriginalArrayNativeType = $scope->getNativeType($arrayWalkArrayArg);

$nodeCallbackForArgs = static function (Node $node, Scope $scope) use ($nodeCallback, $callbackArg, $firstParamName, &$arrayWalkValueTypes): void {
if ($node instanceof ClosureReturnStatementsNode && $node->getClosureExpr() === $callbackArg) {
$types = [];
$nativeTypes = [];
$stmtResult = $node->getStatementResult();
foreach ($stmtResult->getExitPoints() as $exitPoint) {
$exitScope = $exitPoint->getScope();
if (!$exitScope->hasVariableType($firstParamName)->yes()) {
continue;
}
$nodeCallbackForArgs = new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($callbackArg, $firstParamName, &$arrayWalkValueTypes): void {
if (!($node instanceof ClosureReturnStatementsNode) || $node->getClosureExpr() !== $callbackArg) {
return;
}

$types[] = $exitScope->getVariableType($firstParamName);
$nativeTypes[] = $exitScope->getNativeType(new Variable($firstParamName));
}
if (!$stmtResult->isAlwaysTerminating()) {
$stmtScope = $stmtResult->getScope();
if ($stmtScope->hasVariableType($firstParamName)->yes()) {
$types[] = $stmtScope->getVariableType($firstParamName);
$nativeTypes[] = $stmtScope->getNativeType(new Variable($firstParamName));
}
$types = [];
$nativeTypes = [];
$stmtResult = $node->getStatementResult();
foreach ($stmtResult->getExitPoints() as $exitPoint) {
$exitScope = $exitPoint->getScope();
if (!$exitScope->hasVariableType($firstParamName)->yes()) {

Check warning on line 267 in src/Analyser/ExprHandler/FuncCallHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $stmtResult = $node->getStatementResult(); foreach ($stmtResult->getExitPoints() as $exitPoint) { $exitScope = $exitPoint->getScope(); - if (!$exitScope->hasVariableType($firstParamName)->yes()) { + if ($exitScope->hasVariableType($firstParamName)->no()) { continue; }

Check warning on line 267 in src/Analyser/ExprHandler/FuncCallHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $stmtResult = $node->getStatementResult(); foreach ($stmtResult->getExitPoints() as $exitPoint) { $exitScope = $exitPoint->getScope(); - if (!$exitScope->hasVariableType($firstParamName)->yes()) { + if ($exitScope->hasVariableType($firstParamName)->no()) { continue; }
continue;
}
if (count($types) > 0) {
$arrayWalkValueTypes = [
TypeCombinator::union(...$types),
TypeCombinator::union(...$nativeTypes),
];

$types[] = $exitScope->getVariableType($firstParamName);
$nativeTypes[] = $exitScope->getNativeType(new Variable($firstParamName));
}
if (!$stmtResult->isAlwaysTerminating()) {
$stmtScope = $stmtResult->getScope();
if ($stmtScope->hasVariableType($firstParamName)->yes()) {

Check warning on line 276 in src/Analyser/ExprHandler/FuncCallHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } if (!$stmtResult->isAlwaysTerminating()) { $stmtScope = $stmtResult->getScope(); - if ($stmtScope->hasVariableType($firstParamName)->yes()) { + if (!$stmtScope->hasVariableType($firstParamName)->no()) { $types[] = $stmtScope->getVariableType($firstParamName); $nativeTypes[] = $stmtScope->getNativeType(new Variable($firstParamName)); }

Check warning on line 276 in src/Analyser/ExprHandler/FuncCallHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } if (!$stmtResult->isAlwaysTerminating()) { $stmtScope = $stmtResult->getScope(); - if ($stmtScope->hasVariableType($firstParamName)->yes()) { + if (!$stmtScope->hasVariableType($firstParamName)->no()) { $types[] = $stmtScope->getVariableType($firstParamName); $nativeTypes[] = $stmtScope->getNativeType(new Variable($firstParamName)); }
$types[] = $stmtScope->getVariableType($firstParamName);
$nativeTypes[] = $stmtScope->getNativeType(new Variable($firstParamName));
}
}
$nodeCallback($node, $scope);
};
if (count($types) <= 0) {
return;
}

$arrayWalkValueTypes = [
TypeCombinator::union(...$types),
TypeCombinator::union(...$nativeTypes),
];
}, $nodeCallback);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\GatheringNodeCallback;
use PHPStan\Analyser\ImpurePoint;
use PHPStan\Analyser\InternalThrowPoint;
use PHPStan\Analyser\MutatingScope;
Expand Down Expand Up @@ -130,8 +131,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex

if ($constructorReflection->getDeclaringClass()->getName() === $classReflection->getName()) {
$constructorResult = null;
$nodeScopeResolver->processStmtNode($expr->class, $scope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void {
$nodeCallback($node, $scope);
$nodeScopeResolver->processStmtNode($expr->class, $scope, $storage, new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($classReflection, &$constructorResult): void {
if (!$node instanceof MethodReturnStatementsNode) {
return;
}
Expand All @@ -149,7 +149,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
return;
}
$constructorResult = $node;
}, StatementContext::createTopLevel());
}, $nodeCallback), StatementContext::createTopLevel());
if ($constructorResult !== null) {
$throwPoints = array_map(static fn (ThrowPoint $point): InternalThrowPoint => InternalThrowPoint::createFromPublic($point, $scope), $constructorResult->getStatementResult()->getThrowPoints());
$impurePoints = $constructorResult->getImpurePoints();
Expand Down
10 changes: 10 additions & 0 deletions src/Analyser/Fiber/FiberNodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpParser\Node\Expr;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\GatheringNodeCallback;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
Expand All @@ -31,6 +32,15 @@ public function callNodeCallback(
ExpressionResultStorage $storage,
): void
{
// Engine-feeding gatherers must observe the node at the emission
// position - their arrays are read as soon as the enclosing body walk
// returns. Only the rule-facing remainder may be deferred to a fiber;
// a rule parking on an unsettled expression must not delay gathering.
while ($nodeCallback instanceof GatheringNodeCallback) {
($nodeCallback->getGatherer())($node, $scope);
$nodeCallback = $nodeCallback->getInner();
}

if (Fiber::getCurrent() !== null) {
$nodeCallback($node, $scope->toFiberScope());
return;
Expand Down
52 changes: 52 additions & 0 deletions src/Analyser/GatheringNodeCallback.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PhpParser\Node;

/**
* Pairs an engine-feeding gatherer (impure points, execution ends, return
* statements, ...) with the rule-facing node callback. The gatherer's
* by-reference arrays are read as soon as the enclosing body walk returns,
* so FiberNodeScopeResolver runs the gatherer synchronously at the emission
* position and defers only the inner callback to a fiber - a rule parking
* on an unsettled expression must not delay the gathering past the read.
*/
final class GatheringNodeCallback
{

/** @var callable(Node, Scope): void */
private $gatherer;

/** @var callable(Node, Scope): void */
private $inner;

/**
* @param callable(Node, Scope): void $gatherer
* @param callable(Node, Scope): void $inner
*/
public function __construct(callable $gatherer, callable $inner)
{
$this->gatherer = $gatherer;
$this->inner = $inner;
}

public function __invoke(Node $node, Scope $scope): void
{
($this->inner)($node, $scope);
($this->gatherer)($node, $scope);
}

/** @return callable(Node, Scope): void */
public function getGatherer(): callable
{
return $this->gatherer;
}

/** @return callable(Node, Scope): void */
public function getInner(): callable
{
return $this->inner;
}

}
35 changes: 16 additions & 19 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -827,8 +827,7 @@ public function processStmtNode(
$gatheredYieldStatements = [];
$executionEnds = [];
$functionImpurePoints = [];
$statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $functionScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$functionImpurePoints): void {
$nodeCallback($node, $scope);
$statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $functionScope, $storage, new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($functionScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$functionImpurePoints): void {
if ($scope->getFunction() !== $functionScope->getFunction()) {
return;
}
Expand Down Expand Up @@ -857,7 +856,7 @@ public function processStmtNode(
}

$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
}, StatementContext::createTopLevel())->toPublic();
}, $nodeCallback), StatementContext::createTopLevel())->toPublic();

$this->callNodeCallback($nodeCallback, new FunctionReturnStatementsNode(
$stmt,
Expand Down Expand Up @@ -982,8 +981,7 @@ public function processStmtNode(
$gatheredYieldStatements = [];
$executionEnds = [];
$methodImpurePoints = [];
$statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $methodScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$methodImpurePoints): void {
$nodeCallback($node, $scope);
$statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $methodScope, $storage, new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($methodScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$methodImpurePoints): void {
if ($scope->getFunction() !== $methodScope->getFunction()) {
return;
}
Expand Down Expand Up @@ -1021,7 +1019,7 @@ public function processStmtNode(
}

$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
}, StatementContext::createTopLevel())->toPublic();
}, $nodeCallback), StatementContext::createTopLevel())->toPublic();

$methodReflection = $methodScope->getFunction();
if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) {
Expand Down Expand Up @@ -1145,16 +1143,17 @@ public function processStmtNode(
$earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope);
$hasAssign = false;
$currentScope = $scope;
$result = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $currentScope, &$hasAssign): void {
$result = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($currentScope, &$hasAssign): void {
if (
($node instanceof VariableAssignNode || $node instanceof PropertyAssignNode)
&& $scope->getAnonymousFunctionReflection() === $currentScope->getAnonymousFunctionReflection()
&& $scope->getFunction() === $currentScope->getFunction()
!($node instanceof VariableAssignNode) && !($node instanceof PropertyAssignNode)
|| $scope->getAnonymousFunctionReflection() !== $currentScope->getAnonymousFunctionReflection()
|| $scope->getFunction() !== $currentScope->getFunction()
) {
$hasAssign = true;
return;
}
$nodeCallback($node, $scope);
}, ExpressionContext::createTopLevel());

$hasAssign = true;
}, $nodeCallback), ExpressionContext::createTopLevel());
$throwPoints = array_filter($result->getThrowPoints(), static fn ($throwPoint) => $throwPoint->isExplicit());
if (
count($result->getImpurePoints()) === 0
Expand Down Expand Up @@ -3021,8 +3020,7 @@ public function processClosureNode(
$gatheredYieldStatements = [];
$closureImpurePoints = [];
$invalidateExpressions = [];
$closureStmtsCallback = static function (Node $node, Scope $scope) use ($nodeCallback, &$executionEnds, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope, &$closureImpurePoints, &$invalidateExpressions): void {
$nodeCallback($node, $scope);
$closureStmtsCallback = new GatheringNodeCallback(static function (Node $node, Scope $scope) use (&$executionEnds, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope, &$closureImpurePoints, &$invalidateExpressions): void {
if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) {
return;
}
Expand Down Expand Up @@ -3053,7 +3051,7 @@ public function processClosureNode(
}

$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
};
}, $nodeCallback);

if (count($byRefUses) === 0) {
$statementResult = $this->processStmtNodesInternalWithoutFlushingPendingFibers($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel());
Expand Down Expand Up @@ -3433,8 +3431,7 @@ private function processPropertyHooks(
$gatheredReturnStatements = [];
$executionEnds = [];
$methodImpurePoints = [];
$statementResult = $this->processStmtNodesInternal(new PropertyHookStatementNode($hook), $stmts, $hookScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void {
$nodeCallback($node, $scope);
$statementResult = $this->processStmtNodesInternal(new PropertyHookStatementNode($hook), $stmts, $hookScope, $storage, new GatheringNodeCallback(static function (Node $node, Scope $scope) use ($hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void {
if ($scope->getFunction() !== $hookScope->getFunction()) {
return;
}
Expand All @@ -3460,7 +3457,7 @@ private function processPropertyHooks(
}

$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
}, StatementContext::createTopLevel())->toPublic();
}, $nodeCallback), StatementContext::createTopLevel())->toPublic();

$this->callNodeCallback($nodeCallback, new PropertyHookReturnStatementsNode(
$hook,
Expand Down
Loading