Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 15 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,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]);
Expand Down
17 changes: 17 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,10 +20,12 @@ 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,
int|float $intOrFloat,
int|Number $intOrNumber,
float $float,
bool $bool,
$mixed,
Expand All @@ -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;
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.
$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}.
Expand Down
Loading