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: 44 additions & 12 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2542,10 +2542,8 @@ public function enterMatch(Expr\Match_ $expr, Type $condType, Type $condNativeTy
return $this->assignExpression($condExpr, $type, $nativeType);
}

public function enterForeach(self $originalScope, Expr $iteratee, string $valueName, ?string $keyName, bool $valueByRef): self
public function enterForeach(self $originalScope, Expr $iteratee, Type $iterateeType, Type $nativeIterateeType, string $valueName, ?string $keyName, bool $valueByRef): self
{
$iterateeType = $originalScope->getType($iteratee);
$nativeIterateeType = $originalScope->getNativeType($iteratee);
$valueType = $originalScope->getIterableValueType($iterateeType);
$nativeValueType = $originalScope->getIterableValueType($nativeIterateeType);
$scope = $this->assignVariable(
Expand Down Expand Up @@ -2574,7 +2572,7 @@ public function enterForeach(self $originalScope, Expr $iteratee, string $valueN
);
}
if ($keyName !== null) {
$scope = $scope->enterForeachKey($originalScope, $iteratee, $keyName);
$scope = $scope->enterForeachKey($originalScope, $iteratee, $iterateeType, $nativeIterateeType, $keyName);

if ($valueByRef && $iterateeType->isArray()->yes() && $iterateeType->isConstantArray()->no()) {
$scope = $scope->assignExpression(
Expand All @@ -2588,11 +2586,8 @@ public function enterForeach(self $originalScope, Expr $iteratee, string $valueN
return $scope;
}

public function enterForeachKey(self $originalScope, Expr $iteratee, string $keyName): self
public function enterForeachKey(self $originalScope, Expr $iteratee, Type $iterateeType, Type $nativeIterateeType, string $keyName): self
{
$iterateeType = $originalScope->getType($iteratee);
$nativeIterateeType = $originalScope->getNativeType($iteratee);

$keyType = $originalScope->getIterableKeyType($iterateeType);
$nativeKeyType = $originalScope->getIterableKeyType($nativeIterateeType);

Expand Down Expand Up @@ -2847,8 +2842,13 @@ public function assignVariable(string $variableName, Type $type, Type $nativeTyp
}
}

$assignedType = $scope->getType($assignedExpr);
$assignedNativeType = $scope->getNativeType($assignedExpr);
// Resolve the byref slot's new value directly from the just-assigned
// root variable's type, instead of re-evaluating the (stale) $assignedExpr
// node via Scope::getType(): the stored ArrayDimFetch result captured the
// array variable before it existed, so re-reading it would only resolve
// through the asking scope. We already hold the authoritative value here.
$assignedType = $this->resolveIntertwinedAssignedType($scope, $type, $assignedExpr, $variableName, false);
$assignedNativeType = $this->resolveIntertwinedAssignedType($scope, $nativeType, $assignedExpr, $variableName, true);

$has = $scope->hasExpressionType($expressionType->getExpr()->getExpr());
if (
Expand All @@ -2862,14 +2862,15 @@ public function assignVariable(string $variableName, Type $type, Type $nativeTyp
}
if ($unionWithOld) {
$targetVarNode = new Variable($targetVarName);
$rootVarNode = new Variable($variableName);
$assignedType = TypeCombinator::union(
$assignedType,
$this->getType($assignedExpr),
$this->resolveIntertwinedAssignedType($this, $this->getType($rootVarNode), $assignedExpr, $variableName, false),
$scope->getType($targetVarNode),
);
$assignedNativeType = TypeCombinator::union(
$assignedNativeType,
$this->getNativeType($assignedExpr),
$this->resolveIntertwinedAssignedType($this, $this->getNativeType($rootVarNode), $assignedExpr, $variableName, true),
$scope->getNativeType($targetVarNode),
);
}
Expand All @@ -2896,6 +2897,37 @@ public function assignVariable(string $variableName, Type $type, Type $nativeTyp
return $scope;
}

/**
* Resolves the type of a byref slot expression (rooted at $rootVariableName)
* from $rootType - the type just assigned to that root variable - by walking
* the offsets, without re-evaluating the stored $assignedExpr node via
* Scope::getType().
*/
private function resolveIntertwinedAssignedType(self $scope, Type $rootType, Expr $assignedExpr, string $rootVariableName, bool $native): Type
{
if ($assignedExpr instanceof Variable && is_string($assignedExpr->name) && $assignedExpr->name === $rootVariableName) {
return $rootType;
}

if ($assignedExpr instanceof Expr\ArrayDimFetch && $assignedExpr->dim !== null) {
return $this->resolveIntertwinedAssignedType($scope, $rootType, $assignedExpr->var, $rootVariableName, $native)
->getOffsetValueType($scope->getType($assignedExpr->dim));
}

if ($assignedExpr instanceof SetExistingOffsetValueTypeExpr) {
// foreach-byref slot: the iteratee with its key offset set to the value
// variable's new type ($rootType is exactly that value - the expr's
// getValue()).
$iterateeType = $native
? $scope->getNativeType($assignedExpr->getVar())
: $scope->getType($assignedExpr->getVar());

return $iterateeType->setExistingOffsetValueType($scope->getType($assignedExpr->getDim()), $rootType);
}

throw new ShouldNotHappenException();
}

private function isDimFetchPathReachable(self $scope, Expr\ArrayDimFetch $dimFetch): bool
{
if ($dimFetch->dim === null) {
Expand Down
41 changes: 23 additions & 18 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1482,25 +1482,27 @@ public function processStmtNode(
$this->callNodeCallback($nodeCallback, new InForeachNode($stmt), $scope, $storage);
$originalScope = $scope;
$bodyScope = $scope;
$foreachIterateeType = $originalScope->getType($stmt->expr);
$foreachNativeIterateeType = $originalScope->getNativeType($stmt->expr);

if ($stmt->keyVar instanceof Variable) {
$keyTypeExpr = new NativeTypeExpr(
$originalScope->getIterableKeyType($originalScope->getType($stmt->expr)),
$originalScope->getIterableKeyType($originalScope->getNativeType($stmt->expr)),
$originalScope->getIterableKeyType($foreachIterateeType),
$originalScope->getIterableKeyType($foreachNativeIterateeType),
);
$this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->keyVar, $keyTypeExpr), $originalScope, $storage);
}

if ($stmt->valueVar instanceof Variable) {
$valueTypeExpr = new NativeTypeExpr(
$originalScope->getIterableValueType($originalScope->getType($stmt->expr)),
$originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)),
$originalScope->getIterableValueType($foreachIterateeType),
$originalScope->getIterableValueType($foreachNativeIterateeType),
);
$this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->valueVar, $valueTypeExpr), $originalScope, $storage);
} elseif ($stmt->valueVar instanceof List_) {
$virtualAssign = new Assign($stmt->valueVar, new NativeTypeExpr(
$originalScope->getIterableValueType($originalScope->getType($stmt->expr)),
$originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)),
$originalScope->getIterableValueType($foreachIterateeType),
$originalScope->getIterableValueType($foreachNativeIterateeType),
));
$virtualAssign->setAttributes($stmt->valueVar->getAttributes());
$this->callNodeCallback($nodeCallback, $virtualAssign, $scope, $storage);
Expand All @@ -1514,19 +1516,21 @@ public function processStmtNode(
$storage = $originalStorage->duplicate();

$originalScope = $iterateeScope;
$unrolledResult = $this->tryProcessUnrolledConstantArrayForeach($stmt, $originalScope, $originalStorage, $context);
$foreachIterateeType = $originalScope->getType($stmt->expr);
$foreachNativeIterateeType = $originalScope->getNativeType($stmt->expr);
$unrolledResult = $this->tryProcessUnrolledConstantArrayForeach($stmt, $originalScope, $originalStorage, $context, $foreachIterateeType, $foreachNativeIterateeType);
if ($unrolledResult !== null) {
$bodyScope = $unrolledResult['bodyScope'];
$unrolledEndScope = $unrolledResult['endScope'];
$unrolledTotalKeys = $unrolledResult['totalKeys'];
} else {
$bodyScope = $this->enterForeach($originalScope, $storage, $originalScope, $stmt, $nodeCallback);
$bodyScope = $this->enterForeach($originalScope, $storage, $originalScope, $stmt, $foreachIterateeType, $foreachNativeIterateeType, $nodeCallback);
$count = 0;
do {
$prevScope = $bodyScope;
$bodyScope = $bodyScope->mergeWith($iterateeScope);
$storage = $originalStorage->duplicate();
$bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $nodeCallback);
$bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $foreachIterateeType, $foreachNativeIterateeType, $nodeCallback);
$bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints();
$bodyScope = $bodyScopeResult->getScope();
foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
Expand All @@ -1546,7 +1550,7 @@ public function processStmtNode(

$bodyScope = $bodyScope->mergeWith($iterateeScope);
$storage = $originalStorage;
$bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $nodeCallback);
$bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $foreachIterateeType, $foreachNativeIterateeType, $nodeCallback);
$finalPassContext = $unrolledTotalKeys !== null ? $context->enterUnrolledForeach($unrolledTotalKeys) : $context;
$finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $finalPassContext)->filterOutLoopExitPoints();
$finalScope = $finalScopeResult->getScope();
Expand Down Expand Up @@ -4520,6 +4524,8 @@ private function tryProcessUnrolledConstantArrayForeach(
MutatingScope $originalScope,
ExpressionResultStorage $originalStorage,
StatementContext $context,
Type $iterateeType,
Type $nativeIterateeType,
): ?array
{
if ($stmt->byRef) {
Expand All @@ -4532,7 +4538,6 @@ private function tryProcessUnrolledConstantArrayForeach(
return null;
}

$iterateeType = $originalScope->getType($stmt->expr);
if (!$iterateeType->isConstantArray()->yes()) {
return null;
}
Expand All @@ -4558,7 +4563,6 @@ private function tryProcessUnrolledConstantArrayForeach(
return null;
}

$nativeIterateeType = $originalScope->getNativeType($stmt->expr);
$nativeConstantArrays = $nativeIterateeType->getConstantArrays();
$matchedNativeArrays = count($nativeConstantArrays) === count($constantArrays) ? $nativeConstantArrays : null;

Expand Down Expand Up @@ -4694,7 +4698,7 @@ private function tryProcessUnrolledConstantArrayForeach(
$prevLoopScope = $loopScope;
$iterStorage = $originalStorage->duplicate();
$iterBodyScope = $loopScope->mergeWith($endScope);
$iterBodyScope = $this->enterForeach($iterBodyScope, $iterStorage, $originalScope, $stmt, new NoopNodeCallback());
$iterBodyScope = $this->enterForeach($iterBodyScope, $iterStorage, $originalScope, $stmt, $iterateeType, $nativeIterateeType, new NoopNodeCallback());
$iterBodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $iterBodyScope, $iterStorage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints();
$loopScope = $iterBodyScopeResult->getScope();
foreach ($iterBodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
Expand Down Expand Up @@ -4753,13 +4757,12 @@ private function getTraversableForeachThrowPoint(MutatingScope $scope, Expr $ite
/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
private function enterForeach(MutatingScope $scope, ExpressionResultStorage $storage, MutatingScope $originalScope, Foreach_ $stmt, callable $nodeCallback): MutatingScope
private function enterForeach(MutatingScope $scope, ExpressionResultStorage $storage, MutatingScope $originalScope, Foreach_ $stmt, Type $iterateeType, Type $nativeIterateeType, callable $nodeCallback): MutatingScope
{
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
}

$iterateeType = $originalScope->getType($stmt->expr);
if (
($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name))
&& ($stmt->keyVar === null || ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)))
Expand All @@ -4768,6 +4771,8 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto
$scope = $scope->enterForeach(
$originalScope,
$stmt->expr,
$iterateeType,
$nativeIterateeType,
$stmt->valueVar->name,
$keyVarName,
$stmt->byRef,
Expand All @@ -4784,15 +4789,15 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto
$stmt->valueVar,
new NativeTypeExpr(
$originalScope->getIterableValueType($iterateeType),
$originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)),
$originalScope->getIterableValueType($nativeIterateeType),
),
$nodeCallback,
)->getScope();
$vars = $this->getAssignedVariables($stmt->valueVar);
if (
$stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)
) {
$scope = $scope->enterForeachKey($originalScope, $stmt->expr, $stmt->keyVar->name);
$scope = $scope->enterForeachKey($originalScope, $stmt->expr, $iterateeType, $nativeIterateeType, $stmt->keyVar->name);
$vars[] = $stmt->keyVar->name;
} elseif ($stmt->keyVar !== null) {
$scope = $this->processVirtualAssign(
Expand All @@ -4802,7 +4807,7 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto
$stmt->keyVar,
new NativeTypeExpr(
$originalScope->getIterableKeyType($iterateeType),
$originalScope->getIterableKeyType($originalScope->getNativeType($stmt->expr)),
$originalScope->getIterableKeyType($nativeIterateeType),
),
$nodeCallback,
)->getScope();
Expand Down
Loading