Skip to content

Commit

Permalink
Improve loose comparison on constant types
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Jan 2, 2025
1 parent 45a0160 commit 34d8229
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 4 deletions.
22 changes: 19 additions & 3 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
45 changes: 44 additions & 1 deletion tests/PHPStan/Analyser/nsrt/loose-comparisons.php
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,8 @@ public function sayInt(
array $array,
int $int,
int $intRange,
string $emptyStr,
string $phpStr,
): void
{
assertType('bool', $int == $true);
Expand All @@ -747,19 +749,37 @@ 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);
}

/**
* @param true|1|"1" $looseOne
* @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);
Expand Down Expand Up @@ -802,13 +822,22 @@ 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);
}

/**
* @param uppercase-string $upper
* @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(
Expand All @@ -818,6 +847,7 @@ public function sayIntersection(
array $emptyArr,
array $nonEmptyArr,
array $arr,
array $arrShape,
int $i,
int $intRange,
): void
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 34d8229

Please sign in to comment.