From 34d8229d173aae028f60343b55e1810e65f9c84b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 25 Dec 2024 11:39:10 +0100 Subject: [PATCH] Improve loose comparison on constant types --- src/Type/Constant/ConstantArrayType.php | 22 +++++++-- .../Analyser/nsrt/loose-comparisons.php | 45 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 1514781d0f..114843f993 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -418,9 +418,25 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - if ($this->isIterableAtLeastOnce()->no() && count($type->getConstantScalarValues()) === 1) { - // @phpstan-ignore equal.invalid, equal.notAllowed - return new ConstantBooleanType($type->getConstantScalarValues()[0] == []); // phpcs:ignore + if ($type->isInteger()->yes()) { + return new ConstantBooleanType(false); + } + + if ($this->isIterableAtLeastOnce()->no()) { + if ($type->isIterableAtLeastOnce()->yes()) { + return new ConstantBooleanType(false); + } + + $constantScalarValues = $type->getConstantScalarValues(); + if (count($constantScalarValues) > 0) { + $results = []; + foreach ($constantScalarValues as $constantScalarValue) { + // @phpstan-ignore equal.invalid, equal.notAllowed + $results[] = TrinaryLogic::createFromBoolean($constantScalarValue == []); // phpcs:ignore + } + + return TrinaryLogic::extremeIdentity(...$results)->toBooleanType(); + } } return new BooleanType(); diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index 243b7672fd..415cc07a73 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -729,6 +729,8 @@ public function sayInt( array $array, int $int, int $intRange, + string $emptyStr, + string $phpStr, ): void { assertType('bool', $int == $true); @@ -747,6 +749,20 @@ public function sayInt( assertType('false', $intRange == $emptyArr); assertType('false', $intRange == $array); + assertType('false', 5 == $emptyArr); + assertType('false', $emptyArr == 5); + assertType('false', 5 == $array); + assertType('false', $array == 5); + assertType('false', [] == 5); + assertType('false', 5 == []); + + assertType('false', 5 == $emptyStr); + assertType('false', 5 == $phpStr); + assertType('false', 5 == 'a'); + + assertType('false', $emptyStr == 5); + assertType('false', $phpStr == 5); + assertType('false', 'a' == 5); } /** @@ -754,12 +770,16 @@ public function sayInt( * @param false|0|"0" $looseZero * @param false|1 $constMix * @param "abc"|"def" $constNonFalsy + * @param array{abc: string, num?: int, nullable: ?string} $arrShape + * @param array{} $emptyArr */ public function sayConstUnion( $looseOne, $looseZero, $constMix, $constNonFalsy, + array $arrShape, + array $emptyArr ): void { assertType('true', $looseOne == 1); @@ -802,6 +822,14 @@ public function sayConstUnion( assertType('false', $constNonFalsy == "1"); assertType('false', $constNonFalsy == "0"); assertType('false', $constNonFalsy == []); + + assertType('false', $emptyArr == $looseOne); + assertType('bool', $emptyArr == $constMix); + assertType('bool', $emptyArr == $looseZero); + + assertType('bool', $arrShape == $looseOne); + assertType('bool', $arrShape == $constMix); + assertType('bool', $arrShape == $looseZero); } /** @@ -809,6 +837,7 @@ public function sayConstUnion( * @param lowercase-string $lower * @param array{} $emptyArr * @param non-empty-array $nonEmptyArr + * @param array{abc: string, num?: int, nullable: ?string} $arrShape * @param int<10, 20> $intRange */ public function sayIntersection( @@ -818,6 +847,7 @@ public function sayIntersection( array $emptyArr, array $nonEmptyArr, array $arr, + array $arrShape, int $i, int $intRange, ): void @@ -849,11 +879,24 @@ public function sayIntersection( assertType('false', $nonEmptyArr == $i); assertType('false', $arr == $intRange); assertType('false', $nonEmptyArr == $intRange); - assertType('bool', $emptyArr == $nonEmptyArr); // should be false + assertType('false', $emptyArr == $nonEmptyArr); assertType('false', $nonEmptyArr == $emptyArr); assertType('bool', $arr == $nonEmptyArr); assertType('bool', $nonEmptyArr == $arr); + assertType('false', 5 == $arr); + assertType('false', $arr == 5); + assertType('false', 5 == $emptyArr); + assertType('false', $emptyArr == 5); + assertType('false', 5 == $nonEmptyArr); + assertType('false', $nonEmptyArr == 5); + assertType('false', 5 == $arrShape); + assertType('false', $arrShape == 5); + if (count($arr) > 0) { + assertType('false', 5 == $arr); + assertType('false', $arr == 5); + } + assertType('bool', '' == $lower); if ($lower != '') { assertType('false', '' == $lower);