Skip to content

Implement GMP operator type specifying extension#5223

Open
Firehed wants to merge 3 commits intophpstan:2.1.xfrom
Firehed:gmp-operator-type-extension
Open

Implement GMP operator type specifying extension#5223
Firehed wants to merge 3 commits intophpstan:2.1.xfrom
Firehed:gmp-operator-type-extension

Conversation

@Firehed
Copy link
Contributor

@Firehed Firehed commented Mar 15, 2026

Summary

  • Add GmpOperatorTypeSpecifyingExtension to properly infer return types for GMP operator overloads
  • GMP supports arithmetic (+, -, *, /, %, **), bitwise (&, |, ^, ~, <<, >>), and comparison operators
  • Extension only claims support when both operands are GMP-compatible (GMP, int, or numeric-string)
  • Update InitializerExprTypeResolver to call operator extensions early for object types

Fixes phpstan/phpstan#14288

Test plan

  • Added comprehensive test coverage in tests/PHPStan/Analyser/nsrt/gmp-operators.php
  • Tests cover GMP on both left and right sides of operators
  • Tests cover both operator overloads and corresponding gmp_* functions
  • All existing tests pass (11627 tests, 78892 assertions)

🤖 Generated with Claude Code

@phpstan-bot
Copy link
Collaborator

You've opened the pull request against the latest branch 2.2.x. PHPStan 2.2 is not going to be released for months. If your code is relevant on 2.1.x and you want it to be released sooner, please rebase your pull request and change its target to 2.1.x.

Firehed and others added 2 commits March 15, 2026 12:10
This adds comprehensive type inference tests for GMP operations:

- Arithmetic operators (+, -, *, /, %, **) with GMP on left and right
- Bitwise operators (&, |, ^, ~, <<, >>) with GMP on left and right
- Comparison operators (<, <=, >, >=, ==, !=, <=>) with GMP on left and right
- Assignment operators (+=, -=, *=)
- Corresponding gmp_* functions (gmp_add, gmp_sub, gmp_mul, etc.)

These tests currently fail because PHPStan lacks a GmpOperatorTypeSpecifyingExtension
to specify that GMP operations return GMP rather than int|float.

Related: phpstan/phpstan#12123

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add GmpOperatorTypeSpecifyingExtension to properly infer return types
for GMP operator overloads. GMP supports arithmetic (+, -, *, /, %, **),
bitwise (&, |, ^, ~, <<, >>), and comparison (<, <=, >, >=, ==, !=, <=>)
operators.

The extension only claims support when both operands are GMP-compatible
(GMP, int, or numeric-string). Operations with incompatible types like
stdClass are left to the default type inference.

Also update InitializerExprTypeResolver to call operator extensions
early for object types in resolveCommonMath and bitwise methods, and
add explicit GMP handling for unary operators (-$a, ~$a).

Fixes phpstan/phpstan#14288

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Firehed Firehed force-pushed the gmp-operator-type-extension branch from a7b2630 to c75bac6 Compare March 15, 2026 19:10
@Firehed Firehed changed the base branch from 2.2.x to 2.1.x March 15, 2026 19:10
Comment on lines +2065 to +2069
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
if ($specifiedTypes !== null) {
return $specifiedTypes;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This become a duplicate with the check later

$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
			->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
		if ($specifiedTypes !== null) {
			return $specifiedTypes;
		}

I would either:

  • remove this and rely on the later call
  • or just move the existing call first with condition on object.

$leftType = $getTypeCallback($left);
$rightType = $getTypeCallback($right);

if ($leftType->isObject()->yes() || $rightType->isObject()->yes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't check for Object and be done unconditionally.

$leftType = $getTypeCallback($left);
$rightType = $getTypeCallback($right);

if ($leftType->isObject()->yes() || $rightType->isObject()->yes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't check for Object and be done unconditionally.

$leftType = $getTypeCallback($left);
$rightType = $getTypeCallback($right);

if ($leftType->isObject()->yes() || $rightType->isObject()->yes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't check for Object and be done unconditionally.

Comment on lines +2619 to +2622
// GMP supports unary minus and returns GMP
if ($type->isObject()->yes() && (new ObjectType('GMP'))->isSuperTypeOf($type)->yes()) {
return new ObjectType('GMP');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than an hardcoded check for this, I feel like it we should introduce an UnaryOperatorTypeSpecifyingExtension as explained here: #4980 (comment)

This is maybe better in a dedicated PR ?

$exprType = $getTypeCallback($expr);

// GMP supports bitwise not and returns GMP
if ($exprType->isObject()->yes() && (new ObjectType('GMP'))->isSuperTypeOf($exprType)->yes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same for UnaryOperatorTypeSpecifyingExtension


public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type $rightSide): bool
{
if ($leftSide instanceof NeverType || $rightSide instanceof NeverType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The BC math operator type specifying extension has a check

$this->phpVersion->supportsBcMathNumberOperatorOverloading() 

Do we need something similar here or does it work on any php version ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anything since 5.6, according to the RFC that added it (linked in issue). I'm not sure how far back PHPStan support goes and by extension whether that'd be necessary.

@Firehed
Copy link
Contributor Author

Firehed commented Mar 15, 2026

Updated to target 2.1 instead of 2.2 per the bot's suggestion. The two failing-as-of-writing tests appear to be failing on other PRs so I don't think they're specific to this change.

I'll check out the other feedback shortly. Thank you for the very quick review!

- Make extension calls unconditional in getBitwiseAndType, getBitwiseOrType,
  getBitwiseXorType per reviewer feedback
- Move extension call to top of resolveCommonMath and remove duplicate later call

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

Please introduce the changes to InitializerExprTypeResolver in a separate PR - this will force you to create synthetic extensions just for test purposes, and review these changes separately, which I want you to do.

Without this, if we once removed the GMP extensions then this feature would become untested.

After that is merged, then you can take advantage of it with new GMP extensions in a new PR.

@Firehed
Copy link
Contributor Author

Firehed commented Mar 15, 2026

Can do. Thanks for the feedback!

@Firehed
Copy link
Contributor Author

Firehed commented Mar 15, 2026

Split per feedback: #5226 contains the InitializerExprTypeResolver changes with synthetic test extensions. Once that's merged, I'll rebase this PR to only contain the GMP-specific extension (or new separate one if you prefer)

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.

GMP operator overloading false-positive errors

4 participants