diff --git a/README.md b/README.md index b36b377..b38d0f3 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ Few rules are enabled, but do nothing unless configured, those are marked with ` ## Rules: ### allowComparingOnlyComparableTypes -- Denies using comparison operators `>,<,<=,>=,<=>` over anything other than `int|string|float|DateTimeInterface` or same size tuples containing comparable types. Null is not allowed. -- Mixing different types in those operators is also forbidden, only exception is comparing floats with integers +- Denies using comparison operators `>,<,<=,>=,<=>` over anything other than `int|string|float|DateTimeInterface|BcMath\Number` or same size tuples containing comparable types. Null is not allowed. +- Mixing different types in those operators is also forbidden, only exception is comparing floats with integers and integers with `BcMath\Number` - Mainly targets to accidental comparisons of objects, enums or arrays which is valid in PHP, but very tricky ```php diff --git a/src/Rule/AllowComparingOnlyComparableTypesRule.php b/src/Rule/AllowComparingOnlyComparableTypesRule.php index 430fff7..ffe8821 100644 --- a/src/Rule/AllowComparingOnlyComparableTypesRule.php +++ b/src/Rule/AllowComparingOnlyComparableTypesRule.php @@ -22,6 +22,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; use function count; +use const PHP_VERSION_ID; /** * @implements Rule @@ -60,9 +61,12 @@ public function processNode( $rightTypeDescribed = $rightType->describe($rightType->isArray()->no() ? VerbosityLevel::typeOnly() : VerbosityLevel::value()); if (!$this->isComparable($leftType) || !$this->isComparable($rightType)) { - $error = RuleErrorBuilder::message("Comparison {$leftTypeDescribed} {$node->getOperatorSigil()} {$rightTypeDescribed} contains non-comparable type, only int|float|string|DateTimeInterface or comparable tuple is allowed.") - ->identifier('shipmonk.comparingNonComparableTypes') - ->build(); + $builder = RuleErrorBuilder::message("Comparison {$leftTypeDescribed} {$node->getOperatorSigil()} {$rightTypeDescribed} contains non-comparable type, only int|float|string|DateTimeInterface or comparable tuple is allowed.") + ->identifier('shipmonk.comparingNonComparableTypes'); + if (PHP_VERSION_ID >= 80_400) { + $builder->addTip('Also BcMath\Number is allowed.'); + } + $error = $builder->build(); return [$error]; } @@ -82,8 +86,9 @@ private function isComparable(Type $type): bool $floatType = new FloatType(); $stringType = new StringType(); $dateTimeType = new ObjectType(DateTimeInterface::class); + $bcMathNumberType = new ObjectType('BcMath\Number'); - if ($this->containsOnlyTypes($type, [$intType, $floatType, $stringType, $dateTimeType])) { + if ($this->containsOnlyTypes($type, [$intType, $floatType, $stringType, $dateTimeType, $bcMathNumberType])) { return true; } @@ -111,6 +116,12 @@ private function isComparableTogether( $floatType = new FloatType(); $stringType = new StringType(); $dateTimeType = new ObjectType(DateTimeInterface::class); + $bcMathNumberType = new ObjectType('BcMath\Number'); + + if ($this->containsOnlyTypes($leftType, [$bcMathNumberType, $intType]) + && $this->containsOnlyTypes($rightType, [$bcMathNumberType, $intType])) { + return true; + } if ($this->containsOnlyTypes($leftType, [$intType, $floatType])) { return $this->containsOnlyTypes($rightType, [$intType, $floatType]); diff --git a/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php b/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php index 8a560b0..c4f5db8 100644 --- a/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php +++ b/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php @@ -2,6 +2,7 @@ namespace AllowComparingOnlyComparableTypesRule; +use BcMath\Number; use DateTime; use DateTimeImmutable; @@ -19,10 +20,12 @@ interface Bar {} Foo&Bar $fooAndBar, DateTime $dateTime, DateTimeImmutable $dateTimeImmutable, + Number $number, string $string, int $int, ?int $nullableInt, int|float $intOrFloat, + int|Number $intOrNumber, float $float, bool $bool, $mixed, @@ -45,10 +48,24 @@ interface Bar {} $string > $int; // error: Cannot compare different types in string > int. $float > $int; $dateTime > $string; // error: Cannot compare different types in DateTime > string. + $int > $number; + $number > $int; + $number > $float; // error: Cannot compare different types in BcMath\Number > float. + $number > $string; // error: Cannot compare different types in BcMath\Number > string. + $number > $intOrFloat; // error: Cannot compare different types in BcMath\Number > float|int. + $number > $foo; // error: Comparison BcMath\Number > AllowComparingOnlyComparableTypesRule\Foo contains non-comparable type, only int|float|string|DateTimeInterface or comparable tuple is allowed. + $number > $nullableInt; // error: Comparison BcMath\Number > int|null contains non-comparable type, only int|float|string|DateTimeInterface or comparable tuple is allowed. + $number > $intOrNumber; + $intOrNumber > $intOrNumber; + $intOrNumber > $int; [$int, $string] > [$int, $string]; [[$int]] > [[$int]]; [$int, $float, $intOrFloat, $intOrFloat] > [$int, $int, $int, $float]; + [$number] > [$number]; + [$number] > [$int]; + [$number, $int] > [$int, $number]; + [$number] > [$string]; // error: Cannot compare different types in array{BcMath\Number} > array{string}. [$int, $string] > $foos; // error: Comparison array{int, string} > array contains non-comparable type, only int|float|string|DateTimeInterface or comparable tuple is allowed. [$int] > [$int, $int]; // error: Cannot compare different types in array{int} > array{int, int}. [$int, $string] > [$int]; // error: Cannot compare different types in array{int, string} > array{int}.