diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6698a8a800..a05715c28e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2242,13 +2242,40 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - // Normalize to: fn() === expr $leftExpr = $expr->left; $rightExpr = $expr->right; + + // Normalize to: fn() === expr if ($rightExpr instanceof FuncCall && !$leftExpr instanceof FuncCall) { - [$leftExpr, $rightExpr] = [$rightExpr, $leftExpr]; + $specifiedTypes = $this->resolveNormalizedIdentical(new Expr\BinaryOp\Identical( + $rightExpr, + $leftExpr, + ), $scope, $context); + } else { + $specifiedTypes = $this->resolveNormalizedIdentical(new Expr\BinaryOp\Identical( + $leftExpr, + $rightExpr, + ), $scope, $context); + } + + // merge result of fn1() === fn2() and fn2() === fn1() + if ($rightExpr instanceof FuncCall && $leftExpr instanceof FuncCall) { + return $specifiedTypes->unionWith( + $this->resolveNormalizedIdentical(new Expr\BinaryOp\Identical( + $rightExpr, + $leftExpr, + ), $scope, $context), + ); } + return $specifiedTypes; + } + + private function resolveNormalizedIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $leftExpr = $expr->left; + $rightExpr = $expr->right; + $unwrappedLeftExpr = $leftExpr; if ($leftExpr instanceof AlwaysRememberedExpr) { $unwrappedLeftExpr = $leftExpr->getExpr(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-13749.php b/tests/PHPStan/Analyser/nsrt/bug-13749.php new file mode 100644 index 0000000000..25344ba0dd --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13749.php @@ -0,0 +1,69 @@ += strlen($s)) { + assertType('string', $s); // could be non-empty-string + } + + if (strlen($s) === strlen($nonES)) { + assertType('non-empty-string', $s); + } + if (strlen($s) >= strlen($nonES)) { + assertType('non-empty-string', $s); + } + } + + /** + * @param non-falsy-string $nonFalsy + */ + public function sayNonFalsy(string $s, string $nonFalsy): void + { + if (strlen($nonFalsy) === strlen($s)) { + assertType('non-empty-string', $s); + } + if (strlen($nonFalsy) >= strlen($s)) { + assertType('string', $s); // could be non-empty-string + } + + if (strlen($s) === strlen($nonFalsy)) { + assertType('non-empty-string', $s); + } + if (strlen($s) >= strlen($nonFalsy)) { + assertType('non-empty-string', $s); + } + } + + /** + * @param non-empty-array $nonEmptyArr + */ + public function sayCount(array $arr, array $nonEmptyArr): void + { + if (count($arr) === count($nonEmptyArr)) { + assertType('non-empty-array', $arr); + assertType('non-empty-array', $nonEmptyArr); + } + assertType('array', $arr); + assertType('non-empty-array', $nonEmptyArr); + + if (count($nonEmptyArr) === count($arr)) { + assertType('non-empty-array', $arr); + assertType('non-empty-array', $nonEmptyArr); + } + assertType('array', $arr); + assertType('non-empty-array', $nonEmptyArr); + } + +}