Skip to content

Commit

Permalink
Improve loose comparison on IntegerRange containing zero
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Jan 2, 2025
1 parent a742e51 commit fef2c93
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 5 deletions.
55 changes: 54 additions & 1 deletion src/Type/IntegerRangeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,16 @@ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryL
$maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType, $phpVersion);
}

// 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
$zeroInt = new ConstantIntegerType(0);
if (!$zeroInt->isSuperTypeOf($this)->no()) {
return TrinaryLogic::extremeIdentity(
$zeroInt->isSmallerThan($otherType, $phpVersion),
$minIsSmaller,
$maxIsSmaller,
);
}

return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
}

Expand All @@ -332,6 +342,16 @@ public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): T
$maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType, $phpVersion);
}

// 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
$zeroInt = new ConstantIntegerType(0);
if (!$zeroInt->isSuperTypeOf($this)->no()) {
return TrinaryLogic::extremeIdentity(
$zeroInt->isSmallerThanOrEqual($otherType, $phpVersion),
$minIsSmaller,
$maxIsSmaller,
);
}

return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
}

Expand All @@ -349,6 +369,16 @@ public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryL
$maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max)), $phpVersion);
}

// 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
$zeroInt = new ConstantIntegerType(0);
if (!$zeroInt->isSuperTypeOf($this)->no()) {
return TrinaryLogic::extremeIdentity(
$otherType->isSmallerThan($zeroInt, $phpVersion),
$minIsSmaller,
$maxIsSmaller,
);
}

return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
}

Expand All @@ -366,6 +396,16 @@ public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): T
$maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max)), $phpVersion);
}

// 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
$zeroInt = new ConstantIntegerType(0);
if (!$zeroInt->isSuperTypeOf($this)->no()) {
return TrinaryLogic::extremeIdentity(
$otherType->isSmallerThanOrEqual($zeroInt, $phpVersion),
$minIsSmaller,
$maxIsSmaller,
);
}

return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
}

Expand Down Expand Up @@ -694,7 +734,20 @@ public function toPhpDocNode(): TypeNode

public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
{
if ($this->isSmallerThan($type, $phpVersion)->yes() || $this->isGreaterThan($type, $phpVersion)->yes()) {
$zeroInt = new ConstantIntegerType(0);
if ($zeroInt->isSuperTypeOf($this)->no()) {
if ($type->isTrue()->yes()) {
return new ConstantBooleanType(true);
}
if ($type->isFalse()->yes()) {
return new ConstantBooleanType(false);
}
}

if (
$this->isSmallerThan($type, $phpVersion)->yes()
|| $this->isGreaterThan($type, $phpVersion)->yes()
) {
return new ConstantBooleanType(false);
}

Expand Down
61 changes: 57 additions & 4 deletions tests/PHPStan/Analyser/nsrt/loose-comparisons.php
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,9 @@ public function sayEmptyStr(
* @param array{} $emptyArr
* @param 'php' $phpStr
* @param '' $emptyStr
* @param int<10, 20> $intRange
* @param int<10, 20> $positiveIntRange
* @param int<-20, -10> $negativeIntRange
* @param int<-10, 10> $minusTenToTen
*/
public function sayInt(
$true,
Expand All @@ -728,7 +730,9 @@ public function sayInt(
$emptyArr,
array $array,
int $int,
int $intRange,
int $positiveIntRange,
int $negativeIntRange,
int $minusTenToTen,
): void
{
assertType('bool', $int == $true);
Expand All @@ -744,8 +748,57 @@ public function sayInt(
assertType('false', $int == $emptyArr);
assertType('false', $int == $array);

assertType('false', $intRange == $emptyArr);
assertType('false', $intRange == $array);
assertType('true', $positiveIntRange == $true);
assertType('false', $positiveIntRange == $false);
assertType('false', $positiveIntRange == $one);
assertType('false', $positiveIntRange == $zero);
assertType('false', $positiveIntRange == $minusOne);
assertType('false', $positiveIntRange == $oneStr);
assertType('false', $positiveIntRange == $zeroStr);
assertType('false', $positiveIntRange == $minusOneStr);
assertType('false', $positiveIntRange == $plusOneStr);
assertType('false', $positiveIntRange == $null);
assertType('false', $positiveIntRange == $emptyArr);
assertType('false', $positiveIntRange == $array);

assertType('true', $negativeIntRange == $true);
assertType('false', $negativeIntRange == $false);
assertType('false', $negativeIntRange == $one);
assertType('false', $negativeIntRange == $zero);
assertType('false', $negativeIntRange == $minusOne);
assertType('false', $negativeIntRange == $oneStr);
assertType('false', $negativeIntRange == $zeroStr);
assertType('false', $negativeIntRange == $minusOneStr);
assertType('false', $negativeIntRange == $plusOneStr);
assertType('false', $negativeIntRange == $null);
assertType('false', $negativeIntRange == $emptyArr);
assertType('false', $negativeIntRange == $array);

// see https://3v4l.org/VudDK
assertType('bool', $minusTenToTen == $true);
assertType('bool', $minusTenToTen == $false);
assertType('bool', $minusTenToTen == $one);
assertType('bool', $minusTenToTen == $zero);
assertType('bool', $minusTenToTen == $minusOne);
assertType('bool', $minusTenToTen == $oneStr);
assertType('bool', $minusTenToTen == $zeroStr);
assertType('bool', $minusTenToTen == $minusOneStr);
assertType('bool', $minusTenToTen == $plusOneStr);
assertType('bool', $minusTenToTen == $null);
assertType('false', $minusTenToTen == $emptyArr);
assertType('false', $minusTenToTen == $array);

// see https://3v4l.org/oJl3K
assertType('false', $minusTenToTen < $null);
assertType('bool', $minusTenToTen > $null);
assertType('bool', $minusTenToTen <= $null);
assertType('true', $minusTenToTen >= $null);

// see https://3v4l.org/oRSgU
assertType('bool', $null < $minusTenToTen);
assertType('false', $null > $minusTenToTen);
assertType('true', $null <= $minusTenToTen);
assertType('bool', $null >= $minusTenToTen);

}

Expand Down

0 comments on commit fef2c93

Please sign in to comment.