|
19 | 19 | use PhpParser\Node\Expr\StaticCall; |
20 | 20 | use PhpParser\Node\Expr\StaticPropertyFetch; |
21 | 21 | use PhpParser\Node\Name; |
| 22 | +use PHPStan\Analyser\ExprHandler\BooleanAndHandler; |
22 | 23 | use PHPStan\DependencyInjection\AutowiredService; |
23 | 24 | use PHPStan\Node\Expr\AlwaysRememberedExpr; |
24 | 25 | use PHPStan\Node\Expr\TypeExpr; |
@@ -99,6 +100,8 @@ final class TypeSpecifier |
99 | 100 |
|
100 | 101 | private const MAX_ACCESSORIES_LIMIT = 8; |
101 | 102 |
|
| 103 | + private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; |
| 104 | + |
102 | 105 | /** @var MethodTypeSpecifyingExtension[][]|null */ |
103 | 106 | private ?array $methodTypeSpecifyingExtensionsByClass = null; |
104 | 107 |
|
@@ -731,6 +734,13 @@ public function specifyTypesInCondition( |
731 | 734 | if (!$scope instanceof MutatingScope) { |
732 | 735 | throw new ShouldNotHappenException(); |
733 | 736 | } |
| 737 | + |
| 738 | + // For deep BooleanOr chains, flatten and process all arms at once |
| 739 | + // to avoid O(n^2) recursive filterByFalseyValue calls |
| 740 | + if (BooleanAndHandler::getBooleanExpressionDepth($expr) > self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { |
| 741 | + return $this->specifyTypesForFlattenedBooleanOr($scope, $expr, $context); |
| 742 | + } |
| 743 | + |
734 | 744 | $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); |
735 | 745 | $rightScope = $scope->filterByFalseyValue($expr->left); |
736 | 746 | $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); |
@@ -1967,6 +1977,60 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes |
1967 | 1977 | return []; |
1968 | 1978 | } |
1969 | 1979 |
|
| 1980 | + /** |
| 1981 | + * Flatten a deep BooleanOr chain into leaf expressions and process them |
| 1982 | + * without recursive filterByFalseyValue calls. This reduces O(n^2) to O(n) |
| 1983 | + * for chains with many arms (e.g., 80+ === comparisons in ||). |
| 1984 | + */ |
| 1985 | + private function specifyTypesForFlattenedBooleanOr( |
| 1986 | + MutatingScope $scope, |
| 1987 | + BooleanOr|LogicalOr $expr, |
| 1988 | + TypeSpecifierContext $context, |
| 1989 | + ): SpecifiedTypes |
| 1990 | + { |
| 1991 | + // Collect all leaf expressions from the chain |
| 1992 | + $arms = []; |
| 1993 | + $current = $expr; |
| 1994 | + while ($current instanceof BooleanOr || $current instanceof LogicalOr) { |
| 1995 | + $arms[] = $current->right; |
| 1996 | + $current = $current->left; |
| 1997 | + } |
| 1998 | + $arms[] = $current; // leftmost leaf |
| 1999 | + $arms = array_reverse($arms); |
| 2000 | + |
| 2001 | + if ($context->false() || $context->falsey()) { |
| 2002 | + // Falsey: all arms are false → union all SpecifiedTypes |
| 2003 | + $result = new SpecifiedTypes([], []); |
| 2004 | + foreach ($arms as $arm) { |
| 2005 | + $armTypes = $this->specifyTypesInCondition($scope, $arm, $context); |
| 2006 | + $result = $result->unionWith($armTypes); |
| 2007 | + } |
| 2008 | + return $result->setRootExpr($expr); |
| 2009 | + } |
| 2010 | + |
| 2011 | + // Truthy: at least one arm is true → intersect all normalized SpecifiedTypes |
| 2012 | + $armSpecifiedTypes = []; |
| 2013 | + foreach ($arms as $arm) { |
| 2014 | + $armTypes = $this->specifyTypesInCondition($scope, $arm, $context); |
| 2015 | + $armSpecifiedTypes[] = $armTypes->normalize($scope); |
| 2016 | + } |
| 2017 | + |
| 2018 | + $types = $armSpecifiedTypes[0]; |
| 2019 | + for ($i = 1; $i < count($armSpecifiedTypes); $i++) { |
| 2020 | + $types = $types->intersectWith($armSpecifiedTypes[$i]); |
| 2021 | + } |
| 2022 | + |
| 2023 | + $result = new SpecifiedTypes( |
| 2024 | + $types->getSureTypes(), |
| 2025 | + $types->getSureNotTypes(), |
| 2026 | + ); |
| 2027 | + if ($types->shouldOverwrite()) { |
| 2028 | + $result = $result->setAlwaysOverwriteTypes(); |
| 2029 | + } |
| 2030 | + |
| 2031 | + return $result->setRootExpr($expr); |
| 2032 | + } |
| 2033 | + |
1970 | 2034 | /** |
1971 | 2035 | * @return array<string, ConditionalExpressionHolder[]> |
1972 | 2036 | */ |
|
0 commit comments