diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c0a9489e3f..183a2307af 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4107,7 +4107,7 @@ private function mergeVariableHolders(array $listOfExpressionTypes): array $holder = $exprTypeHolders[0]->and(...array_slice($exprTypeHolders, 1)); } - if (!$inAllScopes) { + if (!$inAllScopes && $holder->getCertainty()->yes()) { $holder = new ExpressionTypeHolder($holder->getExpr(), $holder->getType(), TrinaryLogic::createMaybe()); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e3b525a6bb..1ee6170841 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1135,7 +1135,7 @@ private function processStmtNode( $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $endStatements = []; - $finalScope = null; + $scopesToMerge = []; $alwaysTerminating = true; $hasYield = $condResult->hasYield(); @@ -1146,7 +1146,10 @@ private function processStmtNode( $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; + + if (!$branchScopeStatementResult->isAlwaysTerminating()) { + $scopesToMerge[] = $branchScope; + } $alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); if (count($branchScopeStatementResult->getEndStatements()) > 0) { $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); @@ -1180,7 +1183,9 @@ private function processStmtNode( $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); + if (!$branchScopeStatementResult->isAlwaysTerminating()) { + $scopesToMerge[] = $branchScope; + } $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); if (count($branchScopeStatementResult->getEndStatements()) > 0) { $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); @@ -1204,7 +1209,7 @@ private function processStmtNode( if ($stmt->else === null) { if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { - $finalScope = $scope->mergeWith($finalScope); + $scopesToMerge[] = $scope; $alwaysTerminating = false; } } else { @@ -1216,7 +1221,9 @@ private function processStmtNode( $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); + if (!$branchScopeStatementResult->isAlwaysTerminating()) { + $scopesToMerge[] = $branchScope; + } $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); if (count($branchScopeStatementResult->getEndStatements()) > 0) { $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); @@ -1229,8 +1236,10 @@ private function processStmtNode( } } - if ($finalScope === null) { + if (count($scopesToMerge) === 0) { $finalScope = $scope; + } else { + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); } if ($stmt->else === null && !$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { @@ -1284,10 +1293,11 @@ private function processStmtNode( $storage = $originalStorage->duplicate(); $bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $nodeCallback); $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); - $bodyScope = $bodyScopeResult->getScope(); + $scopesToMerge = [$bodyScopeResult->getScope()]; foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } + $bodyScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); if ($bodyScope->equals($prevScope)) { break; } @@ -1317,9 +1327,10 @@ private function processStmtNode( } } + $scopesToMerge = [$finalScope]; foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $continueScope = $continueExitPoint->getScope(); - $finalScope = $continueScope->mergeWith($finalScope); + $scopesToMerge[] = $continueScope; if ($originalKeyVarExpr === null || !$continueScope->hasExpressionType($originalKeyVarExpr)->yes()) { $continueExitPointHasUnoriginalKeyType = true; continue; @@ -1328,9 +1339,11 @@ private function processStmtNode( } $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); foreach ($breakExitPoints as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $breakExitPoint->getScope(); } + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + $exprType = $scope->getType($stmt->expr); $hasExpr = $scope->hasExpressionType($stmt->expr); if ( @@ -1465,10 +1478,11 @@ private function processStmtNode( $storage = $originalStorage->duplicate(); $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); - $bodyScope = $bodyScopeResult->getScope(); + $scopesToMerge = [$bodyScopeResult->getScope()]; foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } + $bodyScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); if ($bodyScope->equals($prevScope)) { break; } @@ -1494,15 +1508,16 @@ private function processStmtNode( $alwaysIterates = $condBooleanType->isTrue()->yes(); $neverIterates = $condBooleanType->isFalse()->yes(); } + $scopesToMerge = [$finalScope]; if (!$alwaysIterates) { foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } } $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); foreach ($breakExitPoints as $breakExitPoint) { - $finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); + $scopesToMerge[] = $breakExitPoint->getScope(); } $isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes(); @@ -1519,9 +1534,11 @@ private function processStmtNode( if (!$this->polluteScopeWithLoopInitialAssignments) { $condScope = $condScope->mergeWith($scope); } - $finalScope = $finalScope->mergeWith($condScope); + $scopesToMerge[] = $condScope; } + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); if (!$neverIterates) { @@ -1538,7 +1555,7 @@ private function processStmtNode( $impurePoints, ); } elseif ($stmt instanceof Do_) { - $finalScope = null; + $finalScopesToMerge = []; $bodyScope = $scope; $count = 0; $hasYield = false; @@ -1552,15 +1569,15 @@ private function processStmtNode( $bodyScope = $bodyScope->mergeWith($scope); $storage = $originalStorage->duplicate(); $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - $bodyScope = $bodyScopeResult->getScope(); + //$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + $scopesToMerge = [$bodyScopeResult->getScope()]; foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } - $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $breakExitPoint->getScope(); } + $bodyScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); if ($bodyScope->equals($prevScope)) { break; @@ -1577,11 +1594,13 @@ private function processStmtNode( $storage = $originalStorage; $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); - $bodyScope = $bodyScopeResult->getScope(); + $scopesToMerge = [$bodyScopeResult->getScope()]; foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } + $bodyScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + $alwaysIterates = false; if ($context->isTopLevel()) { $condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScope->getType($stmt->cond) : $bodyScope->getNativeType($stmt->cond))->toBoolean(); @@ -1595,25 +1614,25 @@ private function processStmtNode( } else { $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); } - $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); - if ($finalScope === null) { - $finalScope = $scope; + if (!$alwaysTerminating) { + $finalScopesToMerge[] = $bodyScope; } if (!$alwaysTerminating) { $condResult = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep()); $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); - $finalScope = $condResult->getFalseyScope(); + $finalScopesToMerge[] = $condResult->getFalseyScope(); } else { $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep()); } + foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $finalScopesToMerge[] = $breakExitPoint->getScope(); } return new InternalStatementResult( - $finalScope, + $finalScopesToMerge !== [] ? $finalScopesToMerge[0]->mergeWith(...array_slice($finalScopesToMerge, 1)) : $scope, $bodyScopeResult->hasYield() || $hasYield, $alwaysTerminating, $bodyScopeResult->getExitPointsForOuterLoop(), @@ -1670,11 +1689,13 @@ private function processStmtNode( $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); } $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); - $bodyScope = $bodyScopeResult->getScope(); + $scopesToMerge = [$bodyScopeResult->getScope()]; foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + $scopesToMerge[] = $continueExitPoint->getScope(); } + $bodyScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + foreach ($stmt->loop as $loopExpr) { $exprResult = $this->processExprNode($stmt, $loopExpr, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createTopLevel()); $bodyScope = $exprResult->getScope(); @@ -1705,11 +1726,13 @@ private function processStmtNode( } $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); - $finalScope = $finalScopeResult->getScope(); + $scopesToMerge = [$finalScopeResult->getScope()]; foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $continueExitPoint->getScope(); } + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + $loopScope = $finalScope; foreach ($stmt->loop as $loopExpr) { $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $storage, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); @@ -1720,10 +1743,13 @@ private function processStmtNode( $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } + $scopesToMerge = [$finalScope]; foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $breakExitPoint->getScope(); } + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { if ($this->polluteScopeWithLoopInitialAssignments) { $finalScope = $initScope; @@ -1763,7 +1789,6 @@ private function processStmtNode( $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $condResult->getScope(); $scopeForBranches = $scope; - $finalScope = null; $prevScope = null; $hasDefaultCase = false; $alwaysTerminating = true; @@ -1772,6 +1797,7 @@ private function processStmtNode( $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $fullCondExpr = null; + $scopesToMerge = []; foreach ($stmt->cases as $caseNode) { if ($caseNode->cond !== null) { $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); @@ -1788,17 +1814,20 @@ private function processStmtNode( $branchScope = $scopeForBranches; } - $branchScope = $branchScope->mergeWith($prevScope); + if ($prevScope !== null) { + $branchScope = $branchScope->mergeWith($prevScope); + } $branchScopeResult = $this->processStmtNodesInternal($caseNode, $caseNode->stmts, $branchScope, $storage, $nodeCallback, $context); $branchScope = $branchScopeResult->getScope(); $branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); $hasYield = $hasYield || $branchFinalScopeResult->hasYield(); + foreach ($branchScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { $alwaysTerminating = false; - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $breakExitPoint->getScope(); } foreach ($branchScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); + $scopesToMerge[] = $continueExitPoint->getScope(); } $exitPointsForOuterLoop = array_merge($exitPointsForOuterLoop, $branchFinalScopeResult->getExitPointsForOuterLoop()); $throwPoints = array_merge($throwPoints, $branchFinalScopeResult->getThrowPoints()); @@ -1811,7 +1840,7 @@ private function processStmtNode( $fullCondExpr = null; } if (!$branchFinalScopeResult->isAlwaysTerminating()) { - $finalScope = $branchScope->mergeWith($finalScope); + $scopesToMerge[] = $branchScope; } } else { $prevScope = $branchScope; @@ -1825,37 +1854,39 @@ private function processStmtNode( } if ($prevScope !== null && isset($branchFinalScopeResult)) { - $finalScope = $prevScope->mergeWith($finalScope); + $scopesToMerge[] = $prevScope; $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); } - if ((!$hasDefaultCase && !$exhaustive) || $finalScope === null) { - $finalScope = $scope->mergeWith($finalScope); + if (!$hasDefaultCase && !$exhaustive) { + $scopesToMerge[] = $scope; + } + + if ($scopesToMerge === []) { + $finalScope = $scope; + } else { + $finalScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); } return new InternalStatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints, $impurePoints); } elseif ($stmt instanceof TryCatch) { $branchScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context); $branchScope = $branchScopeResult->getScope(); - $finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; + $finalScopesToMerge = $branchScopeResult->isAlwaysTerminating() ? [] : [$branchScope]; $exitPoints = []; $finallyExitPoints = []; $alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); $hasYield = $branchScopeResult->hasYield(); - if ($stmt->finally !== null) { - $finallyScope = $branchScope; - } else { - $finallyScope = null; - } + $finallyScopesToMerge = [$branchScope]; foreach ($branchScopeResult->getExitPoints() as $exitPoint) { $finallyExitPoints[] = $exitPoint->toPublic(); if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { continue; } - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); + if ($stmt->finally !== null) { + $finallyScopesToMerge[] = $exitPoint->getScope(); } $exitPoints[] = $exitPoint; } @@ -1971,13 +2002,9 @@ private function processStmtNode( } $throwPoints = $newThrowPoints; - $catchScope = null; + $catchScopesToMerge = []; foreach ($matchingThrowPoints as $matchingThrowPoint) { - if ($catchScope === null) { - $catchScope = $matchingThrowPoint->getScope(); - } else { - $catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); - } + $catchScopesToMerge[] = $matchingThrowPoint->getScope(); } $variableName = null; @@ -1989,52 +2016,57 @@ private function processStmtNode( $variableName = $catchNode->var->name; } + $catchScope = $catchScopesToMerge[0]->mergeWith(...array_slice($catchScopesToMerge, 1)); $catchScopeResult = $this->processStmtNodesInternal($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $storage, $nodeCallback, $context); $catchScopeForFinally = $catchScopeResult->getScope(); - $finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); + if (!$catchScopeResult->isAlwaysTerminating()) { + $finalScopesToMerge[] = $catchScopeResult->getScope(); + } $alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); $hasYield = $hasYield || $catchScopeResult->hasYield(); $catchThrowPoints = $catchScopeResult->getThrowPoints(); $impurePoints = array_merge($impurePoints, $catchScopeResult->getImpurePoints()); $throwPointsForLater = array_merge($throwPointsForLater, $catchThrowPoints); - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($catchScopeForFinally); + if ($stmt->finally !== null) { + $finallyScopesToMerge[] = $catchScopeForFinally; } foreach ($catchScopeResult->getExitPoints() as $exitPoint) { $finallyExitPoints[] = $exitPoint->toPublic(); if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { continue; } - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); + if ($stmt->finally !== null) { + $finallyScopesToMerge[] = $exitPoint->getScope(); } $exitPoints[] = $exitPoint; } foreach ($catchThrowPoints as $catchThrowPoint) { - if ($finallyScope === null) { + if ($stmt->finally === null) { continue; } - $finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()); + $finallyScopesToMerge[] = $catchThrowPoint->getScope(); } } - if ($finalScope === null) { + if ($finalScopesToMerge === []) { $finalScope = $scope; + } else { + $finalScope = $finalScopesToMerge[0]->mergeWith(...array_slice($finalScopesToMerge, 1)); } foreach ($throwPoints as $throwPoint) { - if ($finallyScope === null) { + if ($stmt->finally === null) { continue; } - $finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); + $finallyScopesToMerge[] = $throwPoint->getScope(); } - if ($finallyScope !== null) { - $originalFinallyScope = $finallyScope; - $finallyResult = $this->processStmtNodesInternal($stmt->finally, $stmt->finally->stmts, $finallyScope, $storage, $nodeCallback, $context); + if ($stmt->finally !== null) { + $originalFinallyScope = $finallyScopesToMerge[0]->mergeWith(...array_slice($finallyScopesToMerge, 1)); + $finallyResult = $this->processStmtNodesInternal($stmt->finally, $stmt->finally->stmts, $originalFinallyScope, $storage, $nodeCallback, $context); $alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); $hasYield = $hasYield || $finallyResult->hasYield(); $throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); @@ -4134,6 +4166,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $condType = $scope->getType($expr->cond); $condResult = $this->processExprNode($stmt, $expr->cond, $scope, $storage, $nodeCallback, $deepContext); $scope = $condResult->getScope(); + $scopesToMerge = []; $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); @@ -4252,8 +4285,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $nodeCallback, ExpressionContext::createTopLevel(), ); - $armScope = $armResult->getScope(); - $scope = $scope->mergeWith($armScope); + $scopesToMerge[] = $armResult->getScope(); $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); @@ -4286,11 +4318,10 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $matchArmBody = new MatchExpressionArmBody($matchScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, [], $arm->getStartLine()); $armResult = $this->processExprNode($stmt, $arm->body, $matchScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); - $matchScope = $armResult->getScope(); + $scopesToMerge[] = $armResult->getScope(); $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); - $scope = $scope->mergeWith($matchScope); continue; } @@ -4333,14 +4364,17 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $nodeCallback, ExpressionContext::createTopLevel(), ); - $armScope = $armResult->getScope(); - $scope = $scope->mergeWith($armScope); + $scopesToMerge[] = $armResult->getScope(); $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); $matchScope = $matchScope->filterByFalseyValue($filteringExpr); } + if ($scopesToMerge !== []) { + $scope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + } + if (!$hasDefaultCond && !$hasAlwaysTrueCond) { $remainingType = $matchScope->getType($expr->cond); if (!$remainingType instanceof NeverType) { @@ -5092,11 +5126,13 @@ private function processClosureNode( $storage = $originalStorage->duplicate(); $intermediaryClosureScopeResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, new NoopNodeCallback(), StatementContext::createTopLevel()); - $intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); + $intermediaryClosureScopesToMerge = [$intermediaryClosureScopeResult->getScope()]; foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { - $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); + $intermediaryClosureScopesToMerge[] = $exitPoint->getScope(); } + $intermediaryClosureScope = $intermediaryClosureScopesToMerge[0]->mergeWith(...array_slice($intermediaryClosureScopesToMerge, 1)); + if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) === true) { $closureResultScope = $intermediaryClosureScope; break; @@ -7142,7 +7178,7 @@ private function processCalledMethod(MethodReflection $methodReflection): ?Mutat $returnStatement = $node; }); - $calledMethodEndScope = null; + $scopesToMerge = []; if ($returnStatement !== null) { foreach ($returnStatement->getExecutionEnds() as $executionEnd) { $statementResult = $executionEnd->getStatementResult(); @@ -7153,23 +7189,19 @@ private function processCalledMethod(MethodReflection $methodReflection): ?Mutat continue; } } - if ($calledMethodEndScope === null) { - $calledMethodEndScope = $statementResult->getScope(); - continue; - } - - $calledMethodEndScope = $calledMethodEndScope->mergeWith($statementResult->getScope()); + $scopesToMerge[] = $statementResult->getScope(); } foreach ($returnStatement->getReturnStatements() as $statement) { - if ($calledMethodEndScope === null) { - $calledMethodEndScope = $statement->getScope(); - continue; - } - - $calledMethodEndScope = $calledMethodEndScope->mergeWith($statement->getScope()); + $scopesToMerge[] = $statement->getScope(); } } + if ($scopesToMerge === []) { + $calledMethodEndScope = null; + } else { + $calledMethodEndScope = $scopesToMerge[0]->mergeWith(...array_slice($scopesToMerge, 1)); + } + unset($this->calledMethodStack[$stackName]); $this->calledMethodResults[$stackName] = $calledMethodEndScope; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index b5db780c34..d157836be4 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -22,6 +22,7 @@ use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateArrayType; use PHPStan\Type\Generic\TemplateBenevolentUnionType; +use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateUnionType; @@ -155,6 +156,19 @@ public static function union(Type ...$types): Type $benevolentUnionObject = null; // transform A | (B | C) to A | B | C for ($i = 0; $i < $typesCount; $i++) { + if ( + $types[$i] instanceof MixedType + && !$types[$i]->isExplicitMixed() + && !$types[$i] instanceof TemplateMixedType + && $types[$i]->getSubtractedType() === null + ) { + return $types[$i]; + } + if ($types[$i] instanceof NeverType) { + array_splice($types, $i--, 1); + $typesCount--; + continue; + } if ($types[$i] instanceof BenevolentUnionType) { if ($types[$i] instanceof TemplateBenevolentUnionType && $benevolentUnionObject === null) { $benevolentUnionObject = $types[$i]; @@ -183,10 +197,25 @@ public static function union(Type ...$types): Type $typesCount += count($typesInner) - 1; } + if ($typesCount === 0) { + return new NeverType(); + } + if ($typesCount === 1) { return $types[0]; } + for ($i = 0; $i < $typesCount; $i++) { + for ($j = $i + 1; $j < $typesCount; $j++) { + if (!$types[$i]->equals($types[$j])) { + continue; + } + + array_splice($types, $j--, 1); + $typesCount--; + } + } + $arrayTypes = []; $scalarTypes = []; $hasGenericScalarTypes = []; @@ -1116,6 +1145,12 @@ public static function intersect(Type ...$types): Type return $types[0]; } + foreach ($types as $type) { + if ($type instanceof NeverType) { + return $type; + } + } + $sortTypes = static function (Type $a, Type $b): int { if (!$a instanceof UnionType || !$b instanceof UnionType) { return 0;