close
Skip to content

Allow GMP and BCMath\Number in arithmetic operations#311

Open
Firehed wants to merge 7 commits intophpstan:2.0.xfrom
Firehed:310/allow-bcmath-gmp-overloads
Open

Allow GMP and BCMath\Number in arithmetic operations#311
Firehed wants to merge 7 commits intophpstan:2.0.xfrom
Firehed:310/allow-bcmath-gmp-overloads

Conversation

@Firehed
Copy link
Copy Markdown

@Firehed Firehed commented Apr 20, 2026

Summary

  • Allow GMP and BcMath\Number objects in arithmetic operations (+, -, *, /, %, **, unary +/-)
  • These types support operator overloading (GMP since PHP 5.6, BCMath\Number since PHP 8.4)
  • Increment/decrement (++/--) unchanged since those operators aren't supported by these types

Fixes #310

Implementation

Modified OperatorRuleHelper::isSubtypeOfNumber() to accept an optional parameter for including operator-overloaded types. isValidForArithmeticOperation() passes true to include GMP and BCMath\Number.

Question for reviewers

The test fixture includes incompatibleOverloads() which tests GMP + BCMath\Number. These types are incompatible at runtime. The strict-rules correctly allow both as valid operand types (they are numeric-like), and PHPStan core is expected to catch the incompatibility via binaryOp.invalid.

However, RuleTestCase only captures errors from the specific rule being tested, not PHPStan core errors. So this test only verifies strict-rules doesn't flag it - it doesn't verify PHPStan core catches the incompatibility.

Is there a recommended way to add integration tests that verify PHPStan core behavior, or is this outside the scope of strict-rules' test suite?

Test plan

  • All existing tests pass
  • New tests for GMP and BCMath\Number arithmetic operations
  • PHPStan self-analysis passes

🤖 Generated with Claude Code

Firehed and others added 5 commits April 20, 2026 12:59
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GMP has supported operator overloading since PHP 5.6.
BCMath\Number has supported it since PHP 8.4.

Fixes phpstan#310

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Strict-rules allows both as valid operand types.
PHPStan core catches the type incompatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ondrejmirtes
Copy link
Copy Markdown
Member

Why isn't this taking advantage of the existing extensions but instead hardcodes this information?

@Firehed
Copy link
Copy Markdown
Author

Firehed commented Apr 20, 2026

Thanks for the feedback. To clarify - should the fix:

  1. Use InitializerExprTypeResolver::getPlusTypeFromTypes() to test if $type + int produces a valid result? This delegates to the extension registry internally.

  2. Inject OperatorTypeSpecifyingExtensionRegistryProvider directly and call callOperatorTypeSpecifyingExtensions() with a synthetic BinaryOp\Plus node?

  3. Something else entirely? e.g., should there be a new Type method like supportsArithmetic() added in phpstan-src?

The core issue is that $type->toNumber() returns ErrorType for GMP (correctly - GMP can't be cast to a primitive), but GMP is still valid for arithmetic via the operator extensions. We need a way to ask "does this type support arithmetic?" rather than "can this type be converted to a number?"


Just to clarify, I had something like this, but it felt weird:

diff --git a/src/Rules/Operators/OperatorRuleHelper.php b/src/Rules/Operators/OperatorRuleHelper.php
--- a/src/Rules/Operators/OperatorRuleHelper.php
+++ b/src/Rules/Operators/OperatorRuleHelper.php
@@ -3,7 +3,9 @@
 namespace PHPStan\Rules\Operators;

 use PhpParser\Node\Expr;
+use PhpParser\Node\Scalar\Int_;
 use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\InitializerExprTypeResolver;
 use PHPStan\Rules\RuleLevelHelper;
 use PHPStan\Type\Accessory\AccessoryNumericStringType;
 use PHPStan\Type\BenevolentUnionType;
@@ -21,9 +23,15 @@ class OperatorRuleHelper

      private RuleLevelHelper $ruleLevelHelper;

-     public function __construct(RuleLevelHelper $ruleLevelHelper)
+     private InitializerExprTypeResolver $initializerExprTypeResolver;
+
+     public function __construct(
+             RuleLevelHelper $ruleLevelHelper,
+             InitializerExprTypeResolver $initializerExprTypeResolver
+     )
      {
              $this->ruleLevelHelper = $ruleLevelHelper;
+             $this->initializerExprTypeResolver = $initializerExprTypeResolver;
      }

      public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool
@@ -33,13 +41,22 @@ class OperatorRuleHelper
                      return true;
              }

-             // already reported by PHPStan core
-             if ($type->toNumber() instanceof ErrorType) {
+             if ($this->isSubtypeOfNumber($scope, $expr)) {
                      return true;
              }

-             return $this->isSubtypeOfNumber($scope, $expr);
+             // Check if the type supports arithmetic via operator overloading extensions
+             $intExpr = new Int_(1);
+             $resultType = $this->initializerExprTypeResolver->getPlusTypeFromTypes(
+                     $expr,
+                     $intExpr,
+                     $type,
+                     new IntegerType(),
+             );
+
+             if (!$resultType instanceof ErrorType) {
+                     return true;
+             }
+
+             // Type doesn't support arithmetic - check if PHPStan core will report it
+             return $type->toNumber() instanceof ErrorType;
      }

@ondrejmirtes
Copy link
Copy Markdown
Member

What about $scope->getType() on the Plus operation?

Use $scope->getType() on a synthetic Plus operation to check if object
types support arithmetic via operator overloading extensions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Firehed Firehed marked this pull request as ready for review April 21, 2026 15:37
@Firehed
Copy link
Copy Markdown
Author

Firehed commented Apr 21, 2026

That seems to be quite a bit simpler, thanks for the tip!

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need some logic in isValidForIncrement and isValidForDecrement no ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow core numeric overloads in math and comparison operations

3 participants