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
64 changes: 64 additions & 0 deletions src/Analyser/AssignTargetWalkMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

/**
* How AssignHandler::prepareTarget() walks the assignment target.
*
* Besides whether target sub-expressions are walked inside
* enterExpressionAssign() scopes, the mode says whether the walk also prices
* the whole target as a read: `$lvalue OP= ...` reads the old value of
* `$lvalue`, and `$lvalue ??= ...` reads it with isset() semantics (no
* undefined-variable/uninitialized-property reports; the read carries the
* isset descriptor). The read happens inside the one target walk instead of
* callers re-processing the target with a noop callback.
*
* @internal
*/
final class AssignTargetWalkMode
{

private function __construct(
private bool $enterExpressionAssign,
private bool $producesTargetReadResult,
private bool $issetSemanticsForRead,
)
{
}

public static function assign(): self
{
return new self(true, false, false);
}

public static function virtualAssign(): self
{
return new self(false, false, false);
}

public static function readModifyWrite(): self
{
return new self(false, true, false);
}

public static function coalesceReadModifyWrite(): self
{
return new self(true, true, true);
}

public function enterExpressionAssign(): bool
{
return $this->enterExpressionAssign;
}

public function producesTargetReadResult(): bool
{
return $this->producesTargetReadResult;
}

public function issetSemanticsForRead(): bool
{
return $this->issetSemanticsForRead;
}

}
26 changes: 22 additions & 4 deletions src/Analyser/ExprHandler/ArrayDimFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
Expand Down Expand Up @@ -84,8 +85,27 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$beforeScope = $scope;
if ($expr->dim === null) {
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());
$scope = $varResult->getScope();

return $this->composeResult($nodeScopeResolver, $stmt, $expr, null, $varResult, $storage, $context, $beforeScope);
}

$dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->dim, $scope, $storage, $nodeCallback, $context->enterDeep());
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $dimResult->getScope(), $storage, $nodeCallback, $context->enterDeep());

return $this->composeResult($nodeScopeResolver, $stmt, $expr, $dimResult, $varResult, $storage, $context, $beforeScope);
}

/**
* Builds the offset read's ExpressionResult from the already-walked
* dimension and receiver results - the chain is not re-walked (only the
* ArrayAccess offsetGet simulation runs, over synthetic nodes).
* processExpr() routes through this; AssignHandler::prepareTarget() calls it
* to price a read-modify-write target from the write walk's child results.
*/
public function composeResult(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, ArrayDimFetch $expr, ?ExpressionResult $dimResult, ExpressionResult $varResult, ExpressionResultStorage $storage, ExpressionContext $context, MutatingScope $beforeScope): ExpressionResult
{
$scope = $varResult->getScope();
if ($expr->dim === null || $dimResult === null) {
return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
Expand All @@ -98,11 +118,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
);
}

$dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->dim, $scope, $storage, $nodeCallback, $context->enterDeep());
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $dimResult->getScope(), $storage, $nodeCallback, $context->enterDeep());
$throwPoints = array_merge($dimResult->getThrowPoints(), $varResult->getThrowPoints());
$impurePoints = array_merge($dimResult->getImpurePoints(), $varResult->getImpurePoints());
$scope = $varResult->getScope();

$varType = $varResult->getType();
if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) {
Expand All @@ -125,6 +142,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
throwPoints: $throwPoints,
impurePoints: $impurePoints,
containsNullsafe: $varResult->containsNullsafe(),
issetabilityDescriptor: IssetabilityDescriptor::offset($varResult, $dimResult),
);
}

Expand Down
Loading
Loading