Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 18 additions & 4 deletions src/Rule/AllowComparingOnlyComparableTypesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\VerbosityLevel;
use function count;
use const PHP_VERSION_ID;

/**
* @implements Rule<BinaryOp>
Expand Down Expand Up @@ -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.")
Comment thread
whataboutpereira marked this conversation as resolved.
->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];
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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]);
Expand Down
13 changes: 13 additions & 0 deletions tests/Rule/data/AllowComparingOnlyComparableTypesRule/code.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace AllowComparingOnlyComparableTypesRule;

use BcMath\Number;
use DateTime;
use DateTimeImmutable;

Expand All @@ -19,6 +20,7 @@ interface Bar {}
Foo&Bar $fooAndBar,
DateTime $dateTime,
DateTimeImmutable $dateTimeImmutable,
Number $number,
Comment thread
whataboutpereira marked this conversation as resolved.
string $string,
int $int,
?int $nullableInt,
Expand All @@ -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;
Comment thread
whataboutpereira marked this conversation as resolved.
$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}.
Expand Down
Loading