Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Prev Previous commit
Next Next commit
Refactor GmpOperatorTypeSpecifyingExtension to match BcMath pattern
- Simplify isOperatorSupported to only check if one side is GMP
- Move compatibility checking to specifyType, returning ErrorType for
  incompatible operands (like stdClass)
- Add unit tests for the extension
- Update pow.php test to expect ErrorType for stdClass ** GMP

Co-Authored-By: Claude Opus 4.5 <[email protected]>
  • Loading branch information
Firehed and claude committed Mar 25, 2026
commit 73b16d4f583579964f2e2357d0d4855518672432
48 changes: 18 additions & 30 deletions src/Type/Php/GmpOperatorTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
Expand All @@ -21,41 +22,21 @@ public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type
return false;
}

if (!in_array($operatorSigil, ['+', '-', '*', '/', '**', '%', '&', '|', '^', '<<', '>>', '<', '<=', '>', '>=', '==', '!=', '<=>'], true)) {
return false;
}

$gmpType = new ObjectType('GMP');
$leftIsGmp = $gmpType->isSuperTypeOf($leftSide)->yes();
$rightIsGmp = $gmpType->isSuperTypeOf($rightSide)->yes();

// At least one side must be GMP
if (!$leftIsGmp && !$rightIsGmp) {
return false;
}

// The other side must be GMP-compatible (GMP, int, or numeric-string)
// GMP operations with incompatible types (like stdClass) will error at runtime
return $this->isGmpCompatible($leftSide, $gmpType) && $this->isGmpCompatible($rightSide, $gmpType);
}

private function isGmpCompatible(Type $type, ObjectType $gmpType): bool
{
if ($gmpType->isSuperTypeOf($type)->yes()) {
return true;
}
if ($type->isInteger()->yes()) {
return true;
}
if ($type->isNumericString()->yes()) {
return true;
}
return false;
return in_array($operatorSigil, ['+', '-', '*', '/', '**', '%', '&', '|', '^', '<<', '>>', '<', '<=', '>', '>=', '==', '!=', '<=>'], true)
&& (
$gmpType->isSuperTypeOf($leftSide)->yes()
|| $gmpType->isSuperTypeOf($rightSide)->yes()
);
}

public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type
{
$gmpType = new ObjectType('GMP');
$otherSide = $gmpType->isSuperTypeOf($leftSide)->yes()
? $rightSide
: $leftSide;

// Comparison operators return bool or int (for spaceship)
if (in_array($operatorSigil, ['<', '<=', '>', '>=', '==', '!='], true)) {
Expand All @@ -66,9 +47,16 @@ public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSi
return IntegerRangeType::fromInterval(-1, 1);
}

// All arithmetic and bitwise operations on GMP return GMP
// GMP can operate with: GMP, int, or numeric-string
return $gmpType;
if (
$otherSide->isInteger()->yes()
|| $otherSide->isNumericString()->yes()
|| $gmpType->isSuperTypeOf($otherSide)->yes()
) {
return $gmpType;
}

return new ErrorType();
}

}
13 changes: 13 additions & 0 deletions tests/PHPStan/Analyser/nsrt/gmp-operators.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,16 @@ function gmpWithNumericString(\GMP $a, string $s): void
assertType('GMP', gmp_add($a, '123'));
assertType('GMP', gmp_mul($a, '456'));
}

/**
* @param object $obj
*/
function nonGmpObjectsDoNotGetGmpTreatment($obj, int $i): void
{
// Generic object should NOT be treated as GMP - the extension should not activate
// (object is a supertype of GMP, but GMP is not a supertype of object)
/** @phpstan-ignore binaryOp.invalid */
assertType('*ERROR*', $obj + $i);
/** @phpstan-ignore binaryOp.invalid */
assertType('*ERROR*', $i + $obj);
}
9 changes: 5 additions & 4 deletions tests/PHPStan/Analyser/nsrt/pow.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ function (\GMP $a, \GMP $b): void {
};

function (\stdClass $a, \GMP $b): void {
assertType('GMP|stdClass', pow($a, $b));
assertType('GMP|stdClass', $a ** $b);
// stdClass is not a valid GMP operand, these should error
assertType('*ERROR*', pow($a, $b));
assertType('*ERROR*', $a ** $b);

assertType('GMP|stdClass', pow($b, $a));
assertType('GMP|stdClass', $b ** $a);
assertType('*ERROR*', pow($b, $a));
assertType('*ERROR*', $b ** $a);
};

function (): void {
Expand Down
127 changes: 127 additions & 0 deletions tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use InvalidArgumentException;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPUnit\Framework\Attributes\DataProvider;
use function sprintf;

class GmpOperatorTypeSpecifyingExtensionTest extends PHPStanTestCase
{

private GmpOperatorTypeSpecifyingExtension $extension;

protected function setUp(): void

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.4)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.3)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.5)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, ubuntu-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, ubuntu-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, ubuntu-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, windows-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, windows-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.

Check failure on line 21 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, windows-latest)

Method PHPStan\Type\Php\GmpOperatorTypeSpecifyingExtensionTest::setUp() overrides 3rd party method PHPUnit\Framework\TestCase::setUp() but is missing the #[\Override] attribute.
{
$this->extension = new GmpOperatorTypeSpecifyingExtension();
}

#[DataProvider('dataSupportedOperations')]
public function testSupportsValidGmpOperations(string $sigil, string $leftType, string $rightType): void
{
$left = $this->createType($leftType);
$right = $this->createType($rightType);

self::assertTrue($this->extension->isOperatorSupported($sigil, $left, $right));
}

public static function dataSupportedOperations(): iterable
{
// GMP + GMP
yield 'GMP + GMP' => ['+', 'GMP', 'GMP'];
yield 'GMP - GMP' => ['-', 'GMP', 'GMP'];
yield 'GMP * GMP' => ['*', 'GMP', 'GMP'];

// GMP + int (activates, specifyType handles compatibility)
yield 'GMP + int' => ['+', 'GMP', 'int'];
yield 'int + GMP' => ['+', 'int', 'GMP'];

// GMP + incompatible (activates, specifyType returns ErrorType)
yield 'GMP + stdClass' => ['+', 'GMP', 'stdClass'];
yield 'stdClass + GMP' => ['+', 'stdClass', 'GMP'];

// Comparison
yield 'GMP < GMP' => ['<', 'GMP', 'GMP'];
yield 'GMP <=> int' => ['<=>', 'GMP', 'int'];
}

#[DataProvider('dataUnsupportedOperations')]
public function testDoesNotSupportInvalidOperations(string $sigil, string $leftType, string $rightType): void
{
$left = $this->createType($leftType);
$right = $this->createType($rightType);

self::assertFalse($this->extension->isOperatorSupported($sigil, $left, $right));
}

public static function dataUnsupportedOperations(): iterable
{
// Neither side is GMP
yield 'int + int' => ['+', 'int', 'int'];

// object is a supertype of GMP, but is not GMP itself
// This catches mutations that swap isSuperTypeOf callee/argument
yield 'object + int' => ['+', 'object', 'int'];
yield 'int + object' => ['+', 'int', 'object'];

// GMP|int union should not be treated as definitely GMP
// This catches mutations that change .yes() to !.no()
yield 'GMP|int + int' => ['+', 'GMP|int', 'int'];
yield 'int + GMP|int' => ['+', 'int', 'GMP|int'];
}

#[DataProvider('dataSpecifyTypeReturnsError')]
public function testSpecifyTypeReturnsErrorForIncompatibleTypes(string $sigil, string $leftType, string $rightType): void
{
$left = $this->createType($leftType);
$right = $this->createType($rightType);

self::assertInstanceOf(ErrorType::class, $this->extension->specifyType($sigil, $left, $right));
}

public static function dataSpecifyTypeReturnsError(): iterable
{
yield 'GMP + stdClass' => ['+', 'GMP', 'stdClass'];
yield 'stdClass + GMP' => ['+', 'stdClass', 'GMP'];
yield 'GMP + float' => ['+', 'GMP', 'float'];
}

#[DataProvider('dataSpecifyTypeReturnsGmp')]
public function testSpecifyTypeReturnsGmpForCompatibleTypes(string $sigil, string $leftType, string $rightType): void
{
$left = $this->createType($leftType);
$right = $this->createType($rightType);

$result = $this->extension->specifyType($sigil, $left, $right);
self::assertInstanceOf(ObjectType::class, $result);
self::assertSame('GMP', $result->getClassName());
}

public static function dataSpecifyTypeReturnsGmp(): iterable
{
yield 'GMP + GMP' => ['+', 'GMP', 'GMP'];
yield 'GMP + int' => ['+', 'GMP', 'int'];
yield 'int + GMP' => ['+', 'int', 'GMP'];
}

private function createType(string $type): Type
{
return match ($type) {
'GMP' => new ObjectType('GMP'),
'int' => new IntegerType(),
'float' => new FloatType(),
'object' => new ObjectType('object'),
'stdClass' => new ObjectType('stdClass'),

Check failure on line 121 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected T_DOUBLE_ARROW on line 121

Check failure on line 121 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 121

Check failure on line 121 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected T_DOUBLE_ARROW on line 121

Check failure on line 121 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 121
'GMP|int' => new UnionType([new ObjectType('GMP'), new IntegerType()]),

Check failure on line 122 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 122

Check failure on line 122 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 122
default => throw new InvalidArgumentException(sprintf('Unknown type: %s', $type)),

Check failure on line 123 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 123

Check failure on line 123 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 123
};

Check failure on line 124 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 124

Check failure on line 124 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 124
}

Check failure on line 125 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 125

Check failure on line 125 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 125

Check failure on line 126 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 126

Check failure on line 126 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 126
}

Check failure on line 127 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Syntax error, unexpected ',' on line 127

Check failure on line 127 in tests/PHPStan/Type/Php/GmpOperatorTypeSpecifyingExtensionTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Syntax error, unexpected ',' on line 127
Loading