diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7422156cb6..d7d295cdb3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -42,12 +42,6 @@ parameters: count: 1 path: src/Analyser/MutatingScope.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Analyser/NodeScopeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c6420d1960..36bf3938c0 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -171,7 +171,6 @@ use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; @@ -1079,7 +1078,7 @@ private function processStmtNode( } } elseif ($stmt instanceof If_) { $conditionType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); - $ifAlwaysTrue = $conditionType->isTrue()->yes(); + $ifAlwaysTrue = $conditionType->isTrue(); $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep(), $context); $exitPoints = []; $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); @@ -1089,31 +1088,38 @@ private function processStmtNode( $alwaysTerminating = true; $hasYield = $condResult->hasYield(); - $branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); - - if (!$conditionType instanceof ConstantBooleanType || $conditionType->getValue()) { - $exitPoints = $branchScopeStatementResult->getExitPoints(); - $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); - $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); - $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; - $alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); - if (count($branchScopeStatementResult->getEndStatements()) > 0) { - $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); - } elseif (count($stmt->stmts) > 0) { - $endStatements[] = new EndStatementResult($stmt->stmts[count($stmt->stmts) - 1], $branchScopeStatementResult); - } else { - $endStatements[] = new EndStatementResult($stmt, $branchScopeStatementResult); + if ($context->isTopLevel() || !$conditionType->isTrue()->no()) { + $branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); + + if (!$conditionType->isTrue()->no()) { + $exitPoints = $branchScopeStatementResult->getExitPoints(); + $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); + $branchScope = $branchScopeStatementResult->getScope(); + $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; + $alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); + if (count($branchScopeStatementResult->getEndStatements()) > 0) { + $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); + } elseif (count($stmt->stmts) > 0) { + $endStatements[] = new EndStatementResult($stmt->stmts[count($stmt->stmts) - 1], $branchScopeStatementResult); + } else { + $endStatements[] = new EndStatementResult($stmt, $branchScopeStatementResult); + } + $hasYield = $branchScopeStatementResult->hasYield() || $hasYield; } - $hasYield = $branchScopeStatementResult->hasYield() || $hasYield; } $scope = $condResult->getFalseyScope(); - $lastElseIfConditionIsTrue = false; + $lastElseIfConditionIsTrue = TrinaryLogic::createNo(); $condScope = $scope; foreach ($stmt->elseifs as $elseif) { $nodeCallback($elseif, $scope); + if (!$context->isTopLevel()) { + if ($ifAlwaysTrue->yes() || $lastElseIfConditionIsTrue->yes()) { + continue; + } + } $elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condScope->getType($elseif->cond) : $scope->getNativeType($elseif->cond))->toBoolean(); $condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep(), $context); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); @@ -1121,15 +1127,15 @@ private function processStmtNode( $condScope = $condResult->getScope(); $branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); + if (!$context->isTopLevel() && $elseIfConditionType->isTrue()->no()) { + $scope = $condScope->filterByFalseyValue($elseif->cond); + continue; + } + if ( - !$ifAlwaysTrue - && ( - !$lastElseIfConditionIsTrue - && ( - !$elseIfConditionType instanceof ConstantBooleanType - || $elseIfConditionType->getValue() - ) - ) + !$ifAlwaysTrue->yes() + && !$lastElseIfConditionIsTrue->yes() + && !$elseIfConditionType->isTrue()->no() ) { $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); @@ -1147,40 +1153,40 @@ private function processStmtNode( $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); } - if ( - $elseIfConditionType->isTrue()->yes() - ) { - $lastElseIfConditionIsTrue = true; + if ($elseIfConditionType->isTrue()->yes()) { + $lastElseIfConditionIsTrue = $elseIfConditionType->isTrue(); } - $condScope = $condScope->filterByFalseyValue($elseif->cond); $scope = $condScope; } if ($stmt->else === null) { - if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { + if (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) { $finalScope = $scope->mergeWith($finalScope); $alwaysTerminating = false; } } else { $nodeCallback($stmt->else, $scope); - $branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context); - if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { - $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); - $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); - $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); - $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); - $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); - if (count($branchScopeStatementResult->getEndStatements()) > 0) { - $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); - } elseif (count($stmt->else->stmts) > 0) { - $endStatements[] = new EndStatementResult($stmt->else->stmts[count($stmt->else->stmts) - 1], $branchScopeStatementResult); - } else { - $endStatements[] = new EndStatementResult($stmt->else, $branchScopeStatementResult); + if ($context->isTopLevel() || (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes())) { + $branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context); + + if (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) { + $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); + $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); + $branchScope = $branchScopeStatementResult->getScope(); + $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); + $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); + if (count($branchScopeStatementResult->getEndStatements()) > 0) { + $endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); + } elseif (count($stmt->else->stmts) > 0) { + $endStatements[] = new EndStatementResult($stmt->else->stmts[count($stmt->else->stmts) - 1], $branchScopeStatementResult); + } else { + $endStatements[] = new EndStatementResult($stmt->else, $branchScopeStatementResult); + } + $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); } - $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); } } @@ -1188,7 +1194,7 @@ private function processStmtNode( $finalScope = $scope; } - if ($stmt->else === null && !$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { + if ($stmt->else === null && !$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) { $endStatements[] = new EndStatementResult($stmt, new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints, $impurePoints)); } @@ -1202,6 +1208,7 @@ private function processStmtNode( $condResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep(), $context); $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); + $exprType = $scope->getType($stmt->expr); $scope = $condResult->getScope(); $arrayComparisonExpr = new BinaryOp\NotIdentical( $stmt->expr, @@ -1214,6 +1221,18 @@ private function processStmtNode( $originalScope = $scope; $bodyScope = $scope; + $isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce(); + if (!$context->isTopLevel() && $isIterableAtLeastOnce->no()) { + return new StatementResult( + $scope, + $condResult->hasYield(), + $condResult->isAlwaysTerminating(), + [], + $throwPoints, + $impurePoints, + ); + } + if ($context->isTopLevel()) { $originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope; $bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt); @@ -1250,8 +1269,6 @@ private function processStmtNode( $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); } - $exprType = $scope->getType($stmt->expr); - $isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce(); if ($exprType->isIterable()->no() || $isIterableAtLeastOnce->maybe()) { $finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr( new BinaryOp\Identical( @@ -1494,6 +1511,25 @@ private function processStmtNode( $bodyScope = $condResult->getTruthyScope(); } + if (!$context->isTopLevel() && $isIterableAtLeastOnce->no()) { + if (!isset($condResult)) { + throw new ShouldNotHappenException(); + } + if ($this->polluteScopeWithLoopInitialAssignments) { + $finalScope = $condResult->getFalseyScope()->mergeWith($initScope); + } else { + $finalScope = $condResult->getFalseyScope()->mergeWith($scope); + } + return new StatementResult( + $finalScope, + $hasYield, + false, + [], + $throwPoints, + $impurePoints, + ); + } + if ($context->isTopLevel()) { $count = 0; do {