diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9f7a86eb4a..06eb10dbfb 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -734,7 +734,13 @@ public function specifyTypesInCondition( $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); $rightScope = $scope->filterByTruthyValue($expr->left); $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); - $types = $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($rightScope)); + if ($context->true()) { + $types = $leftTypes->unionWith($rightTypes); + } else { + $leftNormalized = $this->propagateArrayDimFetchToParentArray($leftTypes->normalize($scope), $scope); + $rightNormalized = $this->propagateArrayDimFetchToParentArray($rightTypes->normalize($rightScope), $rightScope); + $types = $leftNormalized->intersectWith($rightNormalized); + } if ($context->false()) { $leftTypesForHolders = $leftTypes; $rightTypesForHolders = $rightTypes; @@ -788,7 +794,9 @@ public function specifyTypesInCondition( ) { $types = $leftTypes->normalize($scope); } else { - $types = $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($rightScope)); + $leftNormalized = $this->propagateArrayDimFetchToParentArray($leftTypes->normalize($scope), $scope); + $rightNormalized = $this->propagateArrayDimFetchToParentArray($rightTypes->normalize($rightScope), $rightScope); + $types = $leftNormalized->intersectWith($rightNormalized); $types = $this->augmentBooleanOrTruthyWithConditionalHolders($scope, $rightScope, $expr, $types); } } else { @@ -2076,6 +2084,56 @@ private function augmentBooleanOrTruthyWithConditionalHolders(MutatingScope $sco return $types; } + private function propagateArrayDimFetchToParentArray(SpecifiedTypes $normalizedTypes, Scope $scope): SpecifiedTypes + { + $additionalSureTypes = []; + $sureTypes = $normalizedTypes->getSureTypes(); + foreach ($sureTypes as $exprString => [$expr, $type]) { + if ( + !$expr instanceof Expr\ArrayDimFetch + || $expr->dim === null + || !$expr->var instanceof Expr\Variable + ) { + continue; + } + $dimType = $scope->getType($expr->dim)->toArrayKey(); + $constantDimType = null; + if ($dimType instanceof ConstantIntegerType) { + $constantDimType = $dimType; + } else { + $constantStrings = $dimType->getConstantStrings(); + if (count($constantStrings) === 1) { + $constantDimType = $constantStrings[0]; + } + } + if ($constantDimType === null) { + continue; + } + $varExprString = $this->exprPrinter->printExpr($expr->var); + if (isset($sureTypes[$varExprString]) || isset($additionalSureTypes[$varExprString])) { + continue; + } + $varType = $scope->getType($expr->var); + if ($varType instanceof MixedType || $varType->isArray()->no()) { + continue; + } + $narrowedVarType = TypeCombinator::intersect($varType, new HasOffsetValueType($constantDimType, $type)); + if ($narrowedVarType instanceof NeverType) { + continue; + } + $additionalSureTypes[$varExprString] = [$expr->var, $narrowedVarType]; + } + + if ($additionalSureTypes === []) { + return $normalizedTypes; + } + + return new SpecifiedTypes( + $sureTypes + $additionalSureTypes, + $normalizedTypes->getSureNotTypes(), + ); + } + /** * @return array */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-14566.php b/tests/PHPStan/Analyser/nsrt/bug-14566.php new file mode 100644 index 0000000000..d4202a29e7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14566.php @@ -0,0 +1,80 @@ +