From 8e42457688698ebb4eb63ad991b0df57bf5aa88b Mon Sep 17 00:00:00 2001 From: Reio Remma Date: Wed, 13 May 2026 17:59:33 +0300 Subject: [PATCH 1/2] allowComparingOnlyComparableTypes: add support for `BcMath\Number` --- README.md | 4 ++-- .../AllowComparingOnlyComparableTypesRule.php | 22 +++++++++++++++---- .../code.php | 13 +++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) 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..2ab44a1 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,15 @@ private function isComparableTogether( $floatType = new FloatType(); $stringType = new StringType(); $dateTimeType = new ObjectType(DateTimeInterface::class); + $bcMathNumberType = new ObjectType('BcMath\Number'); + + if ($this->containsOnlyTypes($leftType, [$bcMathNumberType])) { + return $this->containsOnlyTypes($rightType, [$bcMathNumberType, $intType]); + } + + if ($this->containsOnlyTypes($rightType, [$bcMathNumberType])) { + return $this->containsOnlyTypes($leftType, [$bcMathNumberType, $intType]); + } 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..81d797a 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,6 +20,7 @@ interface Bar {} Foo&Bar $fooAndBar, DateTime $dateTime, DateTimeImmutable $dateTimeImmutable, + Number $number, string $string, int $int, ?int $nullableInt, @@ -45,10 +47,21 @@ 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. [$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}. From 7f7cf8d82b8114545c3684beeaf868f4d6818e32 Mon Sep 17 00:00:00 2001 From: Reio Remma Date: Thu, 14 May 2026 16:51:40 +0300 Subject: [PATCH 2/2] allowComparingOnlyComparableTypes: fix and add tests for int|BcMath\Number unions. --- src/Rule/AllowComparingOnlyComparableTypesRule.php | 9 +++------ .../data/AllowComparingOnlyComparableTypesRule/code.php | 4 ++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Rule/AllowComparingOnlyComparableTypesRule.php b/src/Rule/AllowComparingOnlyComparableTypesRule.php index 2ab44a1..ffe8821 100644 --- a/src/Rule/AllowComparingOnlyComparableTypesRule.php +++ b/src/Rule/AllowComparingOnlyComparableTypesRule.php @@ -118,12 +118,9 @@ private function isComparableTogether( $dateTimeType = new ObjectType(DateTimeInterface::class); $bcMathNumberType = new ObjectType('BcMath\Number'); - if ($this->containsOnlyTypes($leftType, [$bcMathNumberType])) { - return $this->containsOnlyTypes($rightType, [$bcMathNumberType, $intType]); - } - - if ($this->containsOnlyTypes($rightType, [$bcMathNumberType])) { - return $this->containsOnlyTypes($leftType, [$bcMathNumberType, $intType]); + if ($this->containsOnlyTypes($leftType, [$bcMathNumberType, $intType]) + && $this->containsOnlyTypes($rightType, [$bcMathNumberType, $intType])) { + return true; } if ($this->containsOnlyTypes($leftType, [$intType, $floatType])) { diff --git a/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php b/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php index 81d797a..c4f5db8 100644 --- a/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php +++ b/tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php @@ -25,6 +25,7 @@ interface Bar {} int $int, ?int $nullableInt, int|float $intOrFloat, + int|Number $intOrNumber, float $float, bool $bool, $mixed, @@ -54,6 +55,9 @@ interface Bar {} $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]];