diff --git a/src/Rules/Operators/OperatorRuleHelper.php b/src/Rules/Operators/OperatorRuleHelper.php index 6de54ca..fba073f 100644 --- a/src/Rules/Operators/OperatorRuleHelper.php +++ b/src/Rules/Operators/OperatorRuleHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Operators; use PhpParser\Node\Expr; +use PhpParser\Node\Scalar\Int_; use PHPStan\Analyser\Scope; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -38,7 +39,20 @@ public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool return true; } - return $this->isSubtypeOfNumber($scope, $expr); + if ($this->isSubtypeOfNumber($scope, $expr)) { + return true; + } + + // Check if the type supports arithmetic via operator overloading extensions + // (only applies to object types like GMP, BCMath\Number) + if ($type->isObject()->yes()) { + $resultType = $scope->getType(new Expr\BinaryOp\Plus($expr, new Int_(1))); + if (!$resultType instanceof ErrorType) { + return true; + } + } + + return false; } public function isValidForIncrement(Scope $scope, Expr $expr): bool @@ -53,7 +67,18 @@ public function isValidForIncrement(Scope $scope, Expr $expr): bool return true; } - return $this->isSubtypeOfNumber($scope, $expr); + if ($this->isSubtypeOfNumber($scope, $expr)) { + return true; + } + + if ($type->isObject()->yes()) { + $resultType = $scope->getType(new Expr\PostInc($expr)); + if (!$resultType instanceof ErrorType) { + return true; + } + } + + return false; } public function isValidForDecrement(Scope $scope, Expr $expr): bool @@ -63,7 +88,18 @@ public function isValidForDecrement(Scope $scope, Expr $expr): bool return true; } - return $this->isSubtypeOfNumber($scope, $expr); + if ($this->isSubtypeOfNumber($scope, $expr)) { + return true; + } + + if ($type->isObject()->yes()) { + $resultType = $scope->getType(new Expr\PostDec($expr)); + if (!$resultType instanceof ErrorType) { + return true; + } + } + + return false; } private function isSubtypeOfNumber(Scope $scope, Expr $expr): bool diff --git a/tests/Rules/Operators/OperandInArithmeticPostDecrementRuleTest.php b/tests/Rules/Operators/OperandInArithmeticPostDecrementRuleTest.php index c4ea156..cf6021f 100644 --- a/tests/Rules/Operators/OperandInArithmeticPostDecrementRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticPostDecrementRuleTest.php @@ -23,23 +23,23 @@ protected function getExpectedErrors(): array return [ [ 'Only numeric types are allowed in post-decrement, false given.', - 21, + 25, ], [ 'Only numeric types are allowed in post-decrement, string given.', - 22, + 26, ], [ 'Only numeric types are allowed in post-decrement, null given.', - 23, + 27, ], [ 'Only numeric types are allowed in post-decrement, stdClass given.', - 24, + 28, ], [ 'Only numeric types are allowed in post-decrement, int|stdClass|string given.', - 26, + 30, ], ]; } diff --git a/tests/Rules/Operators/OperandInArithmeticPostIncrementRuleTest.php b/tests/Rules/Operators/OperandInArithmeticPostIncrementRuleTest.php index 94357e6..acd4117 100644 --- a/tests/Rules/Operators/OperandInArithmeticPostIncrementRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticPostIncrementRuleTest.php @@ -23,19 +23,19 @@ protected function getExpectedErrors(): array return [ [ 'Only numeric types are allowed in post-increment, false given.', - 32, + 38, ], [ 'Only numeric types are allowed in post-increment, null given.', - 34, + 40, ], [ 'Only numeric types are allowed in post-increment, stdClass given.', - 35, + 41, ], [ 'Only numeric types are allowed in post-increment, int|stdClass|string given.', - 37, + 43, ], ]; } diff --git a/tests/Rules/Operators/OperandInArithmeticPreDecrementRuleTest.php b/tests/Rules/Operators/OperandInArithmeticPreDecrementRuleTest.php index 2bb021a..b433ae1 100644 --- a/tests/Rules/Operators/OperandInArithmeticPreDecrementRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticPreDecrementRuleTest.php @@ -23,23 +23,23 @@ protected function getExpectedErrors(): array return [ [ 'Only numeric types are allowed in pre-decrement, false given.', - 43, + 51, ], [ 'Only numeric types are allowed in pre-decrement, string given.', - 44, + 52, ], [ 'Only numeric types are allowed in pre-decrement, null given.', - 45, + 53, ], [ 'Only numeric types are allowed in pre-decrement, stdClass given.', - 46, + 54, ], [ 'Only numeric types are allowed in pre-decrement, int|stdClass|string given.', - 48, + 56, ], ]; } diff --git a/tests/Rules/Operators/OperandInArithmeticPreIncrementRuleTest.php b/tests/Rules/Operators/OperandInArithmeticPreIncrementRuleTest.php index 2ffe1bb..ea040d0 100644 --- a/tests/Rules/Operators/OperandInArithmeticPreIncrementRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticPreIncrementRuleTest.php @@ -23,19 +23,19 @@ protected function getExpectedErrors(): array return [ [ 'Only numeric types are allowed in pre-increment, false given.', - 54, + 64, ], [ 'Only numeric types are allowed in pre-increment, null given.', - 56, + 66, ], [ 'Only numeric types are allowed in pre-increment, stdClass given.', - 57, + 67, ], [ 'Only numeric types are allowed in pre-increment, int|stdClass|string given.', - 59, + 69, ], ]; } diff --git a/tests/Rules/Operators/OperandInArithmeticUnaryMinusRuleTest.php b/tests/Rules/Operators/OperandInArithmeticUnaryMinusRuleTest.php index d202bdb..441759a 100644 --- a/tests/Rules/Operators/OperandInArithmeticUnaryMinusRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticUnaryMinusRuleTest.php @@ -31,4 +31,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandInArithmeticUnaryPlusRuleTest.php b/tests/Rules/Operators/OperandInArithmeticUnaryPlusRuleTest.php index 6cc253a..be52a22 100644 --- a/tests/Rules/Operators/OperandInArithmeticUnaryPlusRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticUnaryPlusRuleTest.php @@ -31,4 +31,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php index cc53305..6464fb8 100644 --- a/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php @@ -60,4 +60,9 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/operators.php'], $messages); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php index 5714808..7aef68d 100644 --- a/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php @@ -43,4 +43,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php index 539a969..29a493c 100644 --- a/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php @@ -43,4 +43,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php index 54ccc48..a9c7904 100644 --- a/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php @@ -43,4 +43,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php index 4864120..8a27ae4 100644 --- a/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php @@ -43,4 +43,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php index 66dc3b0..230c4d5 100644 --- a/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php @@ -43,4 +43,9 @@ public function testRule(): void ]); } + public function testOperatorOverloads(): void + { + $this->analyse([__DIR__ . '/data/operator-overloads.php'], []); + } + } diff --git a/tests/Rules/Operators/data/increment-decrement.php b/tests/Rules/Operators/data/increment-decrement.php index bdb97c5..a49767f 100644 --- a/tests/Rules/Operators/data/increment-decrement.php +++ b/tests/Rules/Operators/data/increment-decrement.php @@ -2,6 +2,8 @@ namespace Operators; +use BcMath\Number; +use GMP; use stdClass; $int = 123; @@ -14,8 +16,10 @@ $mixed = foo(); /** @var int|string|stdClass $union */ $union = bar(); +$gmp = new GMP('1'); +$bcmath = new BCMath\Number('2'); -(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union): void { +(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union, $gmp, $bcmath): void { $int--; $float--; $bool--; @@ -24,9 +28,11 @@ $object--; $mixed--; $union--; + $gmp--; + $bcmath--; })(); -(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union): void { +(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union, $gmp, $bcmath): void { $int++; $float++; $bool++; @@ -35,9 +41,11 @@ $object++; $mixed++; $union++; + $gmp++; + $bcmath++; })(); -(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union): void { +(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union, $gmp, $bcmath): void { --$int; --$float; --$bool; @@ -46,9 +54,11 @@ --$object; --$mixed; --$union; + --$gmp; + --$bcmath; })(); -(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union): void { +(function () use ($int, $float, $bool, $string, $null, $object, $mixed, $union, $gmp, $bcmath): void { ++$int; ++$float; ++$bool; @@ -57,6 +67,8 @@ ++$object; ++$mixed; ++$union; + ++$gmp; + ++$bcmath; })(); diff --git a/tests/Rules/Operators/data/operator-overloads.php b/tests/Rules/Operators/data/operator-overloads.php new file mode 100644 index 0000000..5aeb9ed --- /dev/null +++ b/tests/Rules/Operators/data/operator-overloads.php @@ -0,0 +1,93 @@ +