diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 334b96f68e..e66e992c03 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -742,11 +742,15 @@ public function specifyTypesInCondition( if ($types->shouldOverwrite()) { $result = $result->setAlwaysOverwriteTypes(); } - return $result->setNewConditionalExpressionHolders(array_merge( + $leftNormalizedForHolders = $leftTypesForHolders->normalize($scope); + $rightNormalizedForHolders = $rightTypesForHolders->normalize($rightScope); + return $result->setNewConditionalExpressionHolders($this->mergeHolders( $this->processBooleanNotSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, $rightScope), $this->processBooleanNotSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, $scope), $this->processBooleanSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, $rightScope), $this->processBooleanSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, $scope), + $this->processBooleanSureConditionalTypes($scope, $leftNormalizedForHolders, $rightNormalizedForHolders, $rightScope), + $this->processBooleanSureConditionalTypes($scope, $rightNormalizedForHolders, $leftNormalizedForHolders, $scope), ))->setRootExpr($expr); } @@ -795,11 +799,15 @@ public function specifyTypesInCondition( if ($types->shouldOverwrite()) { $result = $result->setAlwaysOverwriteTypes(); } - return $result->setNewConditionalExpressionHolders(array_merge( + $leftNormalizedForHolders = $leftTypes->normalize($scope); + $rightNormalizedForHolders = $rightTypes->normalize($rightScope); + return $result->setNewConditionalExpressionHolders($this->mergeHolders( $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes, $rightScope), $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes, $scope), $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes, $rightScope), $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes, $scope), + $this->processBooleanSureConditionalTypes($scope, $leftNormalizedForHolders, $rightNormalizedForHolders, $rightScope), + $this->processBooleanSureConditionalTypes($scope, $rightNormalizedForHolders, $leftNormalizedForHolders, $scope), ))->setRootExpr($expr); } @@ -2123,6 +2131,26 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes return []; } + /** + * @param array ...$arrays + * @return array + */ + private function mergeHolders(array ...$arrays): array + { + $result = []; + foreach ($arrays as $array) { + foreach ($array as $exprString => $holders) { + if (!isset($result[$exprString])) { + $result[$exprString] = $holders; + } else { + $result[$exprString] = array_merge($result[$exprString], $holders); + } + } + } + + return $result; + } + private function isTrackableExpression(Expr $expr): bool { if ($expr instanceof Expr\Variable) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-3385.php b/tests/PHPStan/Analyser/nsrt/bug-3385.php new file mode 100644 index 0000000000..c3015451aa --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-3385.php @@ -0,0 +1,134 @@ +sayHello() === $otherGreeter->sayHello(); + } +} + +function threeGuards(?Greeter $a, ?Greeter $b): bool +{ + if ($a === null && $b !== null) { + return true; + } + + if ($a !== null && $b === null) { + return true; + } + + if ($a === null && $b === null) { + return false; + } + + assertType('Bug3385\Greeter', $a); + assertType('Bug3385\Greeter', $b); + + return $a->isEqualTo($b); +} + +function threeGuardsReversed(?Greeter $a, ?Greeter $b): bool +{ + if ($b !== null && $a === null) { + return true; + } + + if ($b === null && $a !== null) { + return true; + } + + if ($b === null && $a === null) { + return false; + } + + assertType('Bug3385\Greeter', $a); + assertType('Bug3385\Greeter', $b); + + return $a->isEqualTo($b); +} + +function orCombined(?Greeter $a, ?Greeter $b): bool +{ + if (($a === null && $b !== null) || ($a !== null && $b === null)) { + return true; + } + + if ($a === null && $b === null) { + return false; + } + + assertType('Bug3385\Greeter', $a); + assertType('Bug3385\Greeter', $b); + + return $a->isEqualTo($b); +} + +function twoGuardsSuffice(?Greeter $a, ?Greeter $b): bool +{ + if ($a === null && $b !== null) { + return true; + } + + if ($b === null) { + return false; + } + + assertType('Bug3385\Greeter', $a); + assertType('Bug3385\Greeter', $b); + + return $a->isEqualTo($b); +} + +function nestedAlreadyWorks(?Greeter $a, ?Greeter $b): bool +{ + if ($a === null) { + if ($b === null) { + return false; + } + return true; + } + + if ($b === null) { + return true; + } + + assertType('Bug3385\Greeter', $a); + assertType('Bug3385\Greeter', $b); + + return $a->isEqualTo($b); +} + +function orTruthyConditionalHolder(?Greeter $a, ?Greeter $b): bool +{ + if ($a === null || $b !== null) { + if ($b === null) { + assertType('null', $a); + } + } + + return true; +} + +function orTruthyConditionalHolderCross(?Greeter $a, ?Greeter $b): bool +{ + if ($a !== null || $b !== null) { + if ($a === null) { + assertType('Bug3385\Greeter', $b); + } + if ($b === null) { + assertType('Bug3385\Greeter', $a); + } + } + + return true; +}