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: 5 additions & 1 deletion src/Analyser/ExprHandler/ArrayHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$isAlwaysTerminating = false;
foreach ($expr->items as $arrayItem) {
$itemNodes[] = new LiteralArrayItem($scope, $arrayItem);
$nodeScopeResolver->callNodeCallback($nodeCallback, $arrayItem, $scope, $storage);
$itemCallbackScope = $scope;
if ($arrayItem->key !== null) {
$keyResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep());
$hasYield = $hasYield || $keyResult->hasYield();
Expand All @@ -98,6 +98,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints());
$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating();
$scope = $valueResult->getScope();
// the item's callback fires after its key and value were processed,
// with the item's entry scope - callback-side asks answer from the
// storage instead of re-walking the yet-unstored sub-expressions
$nodeScopeResolver->callNodeCallback($nodeCallback, $arrayItem, $itemCallbackScope, $storage);
}
$nodeScopeResolver->callNodeCallback($nodeCallback, new LiteralArrayNode($expr, $itemNodes), $scope, $storage);

Expand Down
16 changes: 11 additions & 5 deletions src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,7 @@ private function doPrepareTarget(
$lastDimKey = array_key_last($dimFetchStack);
foreach ($dimFetchStack as $key => $dimFetch) {
$dimExpr = $dimFetch->dim;

// Callback was already called for last dim at the beginning of the method.
if ($key !== $lastDimKey) {
$nodeScopeResolver->callNodeCallback($nodeCallback, $dimFetch, $enterExpressionAssign ? $scope->enterExpressionAssign($dimFetch) : $scope, $storage);
}
$callbackScope = $scope;

if ($dimExpr === null) {
$dimResults[$key] = null;
Expand Down Expand Up @@ -623,6 +619,16 @@ private function doPrepareTarget(
$scope = $scope->exitExpressionAssign($dimExpr);
}
}

// The whole target's callback fires in prepareTarget() after the
// walk; an intermediate link's fires here, after its dimension was
// processed, so callback-side asks about the dimension answer from
// the storage with the link's entry scope.
if ($key === $lastDimKey) {
continue;
}

$nodeScopeResolver->callNodeCallback($nodeCallback, $dimFetch, $enterExpressionAssign ? $callbackScope->enterExpressionAssign($dimFetch) : $callbackScope, $storage);
}

if ($mode->issetSemanticsForRead()) {
Expand Down
14 changes: 14 additions & 0 deletions src/Analyser/ExprHandler/AssignOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
);
}

// applyWrite() emits nodes (PropertyAssignNode) whose rules ask about
// this whole `$lvalue OP= value` expression - store a before-scope
// anchored result first so those asks answer from the storage;
// processExprNode() overwrites it with the final result after this
// handler returns
$nodeScopeResolver->storeExpressionResult($storage, $expr, $this->expressionResultFactory->create(
$valueResult->getScope(),
$beforeScope,
$expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
));
$assignResult = $this->assignHandler->applyWrite(
$nodeScopeResolver,
$target,
Expand Down
14 changes: 14 additions & 0 deletions src/Analyser/ExprHandler/PostDecHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
{
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());

// processVirtualAssign() emits nodes (PropertyAssignNode) whose rules ask
// about this whole expression - store a before-scope anchored result
// first so those asks answer from the storage; processExprNode()
// overwrites it with the final result after this handler returns
$nodeScopeResolver->storeExpressionResult($storage, $expr, $this->expressionResultFactory->create(
$varResult->getScope(),
$scope,
$expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
));

return $this->expressionResultFactory->create(
$nodeScopeResolver->processVirtualAssign(
$varResult->getScope(),
Expand Down
14 changes: 14 additions & 0 deletions src/Analyser/ExprHandler/PostIncHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
{
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());

// processVirtualAssign() emits nodes (PropertyAssignNode) whose rules ask
// about this whole expression - store a before-scope anchored result
// first so those asks answer from the storage; processExprNode()
// overwrites it with the final result after this handler returns
$nodeScopeResolver->storeExpressionResult($storage, $expr, $this->expressionResultFactory->create(
$varResult->getScope(),
$scope,
$expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
));

return $this->expressionResultFactory->create(
$nodeScopeResolver->processVirtualAssign(
$varResult->getScope(),
Expand Down
14 changes: 14 additions & 0 deletions src/Analyser/ExprHandler/PreDecHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
{
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());

// processVirtualAssign() emits nodes (PropertyAssignNode) whose rules ask
// about this whole expression - store a before-scope anchored result
// first so those asks answer from the storage; processExprNode()
// overwrites it with the final result after this handler returns
$nodeScopeResolver->storeExpressionResult($storage, $expr, $this->expressionResultFactory->create(
$varResult->getScope(),
$scope,
$expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
));

return $this->expressionResultFactory->create(
$nodeScopeResolver->processVirtualAssign(
$varResult->getScope(),
Expand Down
14 changes: 14 additions & 0 deletions src/Analyser/ExprHandler/PreIncHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
{
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());

// processVirtualAssign() emits nodes (PropertyAssignNode) whose rules ask
// about this whole expression - store a before-scope anchored result
// first so those asks answer from the storage; processExprNode()
// overwrites it with the final result after this handler returns
$nodeScopeResolver->storeExpressionResult($storage, $expr, $this->expressionResultFactory->create(
$varResult->getScope(),
$scope,
$expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
));

return $this->expressionResultFactory->create(
$nodeScopeResolver->processVirtualAssign(
$varResult->getScope(),
Expand Down
15 changes: 12 additions & 3 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,10 @@ public function processStmtNode(
// PHP < 8.1) then finds the expressions' results in the storage instead
// of re-walking them on demand, mirroring processExprNodeInternal().
$deferredStmtCallback = $stmt instanceof Return_ || $stmt instanceof Node\Stmt\Expression || $stmt instanceof Echo_
|| $stmt instanceof If_ || $stmt instanceof Switch_ || $stmt instanceof Foreach_;
|| $stmt instanceof If_ || $stmt instanceof Switch_ || $stmt instanceof Foreach_
|| $stmt instanceof Node\Stmt\Unset_ || $stmt instanceof Node\Stmt\ClassConst
|| $stmt instanceof Node\Stmt\Const_ || $stmt instanceof Node\Stmt\While_
|| $stmt instanceof Node\Stmt\Do_;
if (!$deferredStmtCallback) {
$this->callNodeCallback($nodeCallback, $stmt, $stmtScope, $storage);
}
Expand Down Expand Up @@ -2054,7 +2057,6 @@ public function processArgs(
}
}

$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context, $parameterType, $parameterNativeType);
// the preferred ClosureType read below now answers from this seed
// instead of walking the body again (unless a parked fiber may
Expand All @@ -2074,6 +2076,10 @@ public function processArgs(
throwPoints: [],
impurePoints: [],
));
// the closure node's own callback fires after its result is
// stored, mirroring processExprNodeInternal() - callback-side
// getType() answers from the stored result
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);

$uses = [];
foreach ($arg->value->uses as $use) {
Expand Down Expand Up @@ -2142,7 +2148,6 @@ public function processArgs(
}
}

$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
$processArrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $parameterType, $parameterNativeType);
// the invalidation read below now answers from this seed instead
// of walking the body again (unless a parked fiber may still
Expand All @@ -2160,6 +2165,10 @@ public function processArgs(
}
}
$this->storeExpressionResult($storage, $arg->value, $arrowFunctionResult);
// the arrow function node's own callback fires after its result
// is stored, mirroring processExprNodeInternal() - callback-side
// getType() answers from the stored result
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
} else {
$exprType = $scope->getType($arg->value);
$enterExpressionAssignForByRef = $assignByReference && $arg->value instanceof ArrayDimFetch && $arg->value->dim === null;
Expand Down
8 changes: 7 additions & 1 deletion src/Analyser/StmtHandler/ClassConstHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ public function processStmt(
StatementContext $context,
): InternalStatementResult
{
$entryScope = $scope;
$impurePoints = [];
$nodeScopeResolver->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback);
foreach ($stmt->consts as $const) {
$nodeScopeResolver->callNodeCallback($nodeCallback, $const, $scope, $storage);
$constResult = $nodeScopeResolver->processExprNode($stmt, $const->value, $scope, $storage, $nodeCallback, ExpressionContext::createDeep());
// the constant's callback fires after its value was processed, so
// rule-side asks about the value answer from the storage
$nodeScopeResolver->callNodeCallback($nodeCallback, $const, $scope, $storage);
$impurePoints = array_merge($impurePoints, $constResult->getImpurePoints());
if ($scope->getClassReflection() === null) {
throw new ShouldNotHappenException();
Expand All @@ -54,6 +57,9 @@ public function processStmt(
);
}

// deferred from processStmtNode() - fires after the values were processed
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);

return new InternalStatementResult($scope, hasYield: false, isAlwaysTerminating: false, exitPoints: [], throwPoints: [], impurePoints: $impurePoints);
}

Expand Down
6 changes: 4 additions & 2 deletions src/Analyser/StmtHandler/ClassLikeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ public function processStmt(
if (isset($stmt->namespacedName)) {
$classReflection = $this->getCurrentClassReflection($nodeScopeResolver, $stmt, $stmt->namespacedName->toString(), $scope);
$classScope = $scope->enterClass($classReflection);
$nodeScopeResolver->callNodeCallback($nodeCallback, new InClassNode($stmt, $classReflection), $classScope, $storage);
} elseif ($stmt instanceof Class_) {
if ($stmt->name === null) {
throw new ShouldNotHappenException();
Expand All @@ -97,13 +96,16 @@ public function processStmt(
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope);
}
$classScope = $scope->enterClass($classReflection);
$nodeScopeResolver->callNodeCallback($nodeCallback, new InClassNode($stmt, $classReflection), $classScope, $storage);
} else {
throw new ShouldNotHappenException();
}

$classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback);
// the class attributes are processed before the InClassNode emission, so
// rules firing on it (ClassAttributesRule) read the attribute arguments
// from the storage
$nodeScopeResolver->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $storage, $classStatementsGatherer);
$nodeScopeResolver->callNodeCallback($nodeCallback, new InClassNode($stmt, $classReflection), $classScope, $storage);

$classLikeStatements = $stmt->stmts;
// analyze static methods first; constructor next; instance methods and property hooks last so we can carry over the scope
Expand Down
8 changes: 7 additions & 1 deletion src/Analyser/StmtHandler/ConstHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ public function processStmt(
StatementContext $context,
): InternalStatementResult
{
$entryScope = $scope;
$impurePoints = [];
foreach ($stmt->consts as $const) {
$nodeScopeResolver->callNodeCallback($nodeCallback, $const, $scope, $storage);
$constResult = $nodeScopeResolver->processExprNode($stmt, $const->value, $scope, $storage, $nodeCallback, ExpressionContext::createDeep());
// the constant's callback fires after its value was processed, so
// rule-side asks about the value answer from the storage
$nodeScopeResolver->callNodeCallback($nodeCallback, $const, $scope, $storage);
$impurePoints = array_merge($impurePoints, $constResult->getImpurePoints());
if ($const->namespacedName !== null) {
$constantName = new Name\FullyQualified($const->namespacedName->toString());
Expand All @@ -50,6 +53,9 @@ public function processStmt(
$scope = $scope->assignExpression(new ConstFetch($constantName), $constResult->getType(), $constResult->getNativeType());
}

// deferred from processStmtNode() - fires after the values were processed
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);

return new InternalStatementResult($scope, hasYield: false, isAlwaysTerminating: false, exitPoints: [], throwPoints: [], impurePoints: $impurePoints);
}

Expand Down
8 changes: 6 additions & 2 deletions src/Analyser/StmtHandler/DoWhileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ public function processStmt(
$alwaysIterates = $condBooleanType->isTrue()->yes();
}

$nodeScopeResolver->callNodeCallback($nodeCallback, new DoWhileLoopConditionNode($stmt->cond, $bodyScopeResult->toPublic()->getExitPoints(), $bodyScopeResult->hasYield()), $bodyScope, $storage);

if ($alwaysIterates) {
$alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0;
} else {
Expand All @@ -146,6 +144,12 @@ public function processStmt(
$nodeScopeResolver->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep());
}

// both emissions fire after the condition's final walk stored its
// results, so rule-side asks about the condition answer from the
// storage; the Do_ callback is deferred from processStmtNode()
$nodeScopeResolver->callNodeCallback($nodeCallback, new DoWhileLoopConditionNode($stmt->cond, $bodyScopeResult->toPublic()->getExitPoints(), $bodyScopeResult->hasYield()), $bodyScope, $storage);
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $scope, $storage);

$breakExitPoints = $bodyScopeResult->getExitPointsByType(Break_::class);
if (count($breakExitPoints) > 0) {
$breakScope = $alwaysIterates ? null : $finalScope;
Expand Down
6 changes: 5 additions & 1 deletion src/Analyser/StmtHandler/SwitchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public function processStmt(
{
$entryScope = $scope;
$condResult = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $scope, $storage, $nodeCallback, ExpressionContext::createDeep());
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);
$scope = $condResult->getScope();
$scopeForBranches = $scope;
$finalScope = null;
Expand Down Expand Up @@ -117,6 +116,11 @@ public function processStmt(
}
}

// the Switch_ callback is deferred from processStmtNode(): it fires
// after every case condition's walk stored its result, with the entry
// scope, so rules pricing the case conditions answer from the storage
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);

if ($switchConditionArms !== []) {
$nodeScopeResolver->callNodeCallback($nodeCallback, new SwitchConditionNode($stmt->cond, $switchConditionArms, $stmt), $scope, $storage);
}
Expand Down
6 changes: 6 additions & 0 deletions src/Analyser/StmtHandler/UnsetHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function processStmt(
StatementContext $context,
): InternalStatementResult
{
$entryScope = $scope;
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
Expand Down Expand Up @@ -104,6 +105,11 @@ public function processStmt(
$scope = $scope->invalidateExpression(new ForeachValueByRefExpr($var));
}

// the Unset_ callback is deferred from processStmtNode(): it fires after
// the unset targets were processed, with the entry scope, so rule-side
// asks about them answer from the storage
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);

return new InternalStatementResult($scope, hasYield: $hasYield, isAlwaysTerminating: false, exitPoints: [], throwPoints: $throwPoints, impurePoints: $impurePoints);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Analyser/StmtHandler/WhileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,16 @@ public function processStmt(
// and replay its emissions through the real callback instead
$originalStorage->mergeResults($replayPassStorage);
$nodeScopeResolver->replayRecording($replayCondRecording, $nodeCallback, $originalStorage);
// the While_ callback is deferred from processStmtNode(): it fires
// after the condition's results are in the storage, with the entry scope
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $scope, $originalStorage);
$nodeScopeResolver->replayRecording($replayBodyRecording, $nodeCallback, $originalStorage);
$finalScopeResult = $replayPassResult;
} else {
$bodyScope = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope();
// the While_ callback is deferred from processStmtNode(): it fires
// after the condition's real walk stored its result, with the entry scope
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $scope, $storage);
$finalScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints();
}
$finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond);
Expand Down
Loading