Skip to content

Commit 80e6de9

Browse files
Firehedclaude
andcommitted
Call operator type specifying extensions for bitwise and arithmetic operators
- Add extension calls to getBitwiseAndType, getBitwiseOrType, getBitwiseXorType - Move extension call to top of resolveCommonMath (before integer range optimization) - Remove duplicate extension call later in resolveCommonMath - Add TestBitwiseOperatorTypeSpecifyingExtension for testing bitwise extension calls - Add OperatorTypeSpecifyingExtensionTypeInferenceTest with tests for both bitwise (TestBitwiseOperand) and arithmetic (TestDecimal) operators This ensures operator type specifying extensions are called consistently for all supported operators, allowing custom types to specify operator return types. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 66617b9 commit 80e6de9

File tree

6 files changed

+163
-6
lines changed

6 files changed

+163
-6
lines changed

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,12 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall
986986
$leftType = $getTypeCallback($left);
987987
$rightType = $getTypeCallback($right);
988988

989+
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
990+
->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseAnd($left, $right), $leftType, $rightType);
991+
if ($specifiedTypes !== null) {
992+
return $specifiedTypes;
993+
}
994+
989995
return $this->getBitwiseAndTypeFromTypes($leftType, $rightType);
990996
}
991997

@@ -1044,6 +1050,12 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb
10441050
$leftType = $getTypeCallback($left);
10451051
$rightType = $getTypeCallback($right);
10461052

1053+
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1054+
->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseOr($left, $right), $leftType, $rightType);
1055+
if ($specifiedTypes !== null) {
1056+
return $specifiedTypes;
1057+
}
1058+
10471059
return $this->getBitwiseOrTypeFromTypes($leftType, $rightType);
10481060
}
10491061

@@ -1092,6 +1104,12 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall
10921104
$leftType = $getTypeCallback($left);
10931105
$rightType = $getTypeCallback($right);
10941106

1107+
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1108+
->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseXor($left, $right), $leftType, $rightType);
1109+
if ($specifiedTypes !== null) {
1110+
return $specifiedTypes;
1111+
}
1112+
10951113
return $this->getBitwiseXorTypeFromTypes($leftType, $rightType);
10961114
}
10971115

@@ -2034,6 +2052,12 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType,
20342052
*/
20352053
private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
20362054
{
2055+
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2056+
->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
2057+
if ($specifiedTypes !== null) {
2058+
return $specifiedTypes;
2059+
}
2060+
20372061
$types = TypeCombinator::union($leftType, $rightType);
20382062
$leftNumberType = $leftType->toNumber();
20392063
$rightNumberType = $rightType->toNumber();
@@ -2073,12 +2097,6 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
20732097
}
20742098
}
20752099

2076-
$specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2077-
->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
2078-
if ($specifiedTypes !== null) {
2079-
return $specifiedTypes;
2080-
}
2081-
20822100
if (
20832101
$leftType->isArray()->yes()
20842102
|| $rightType->isArray()->yes()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
8+
class OperatorTypeSpecifyingExtensionTypeInferenceTest extends TypeInferenceTestCase
9+
{
10+
11+
public static function dataAsserts(): iterable
12+
{
13+
yield from self::gatherAssertTypes(__DIR__ . '/data/operator-type-specifying-extension.php');
14+
}
15+
16+
/**
17+
* @param mixed ...$args
18+
*/
19+
#[DataProvider('dataAsserts')]
20+
public function testAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args,
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
public static function getAdditionalConfigFiles(): array
30+
{
31+
return [
32+
__DIR__ . '/operator-type-specifying-extension.neon',
33+
];
34+
}
35+
36+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace OperatorExtensionTest;
4+
5+
use PHPStan\Fixture\TestBitwiseOperand;
6+
use PHPStan\Fixture\TestDecimal;
7+
use function PHPStan\Testing\assertType;
8+
9+
// =============================================================================
10+
// Bitwise operator extension tests
11+
// =============================================================================
12+
13+
function testBitwiseAnd(TestBitwiseOperand $a, TestBitwiseOperand $b): void
14+
{
15+
assertType('PHPStan\Fixture\TestBitwiseOperand', $a & $b);
16+
}
17+
18+
function testBitwiseOr(TestBitwiseOperand $a, TestBitwiseOperand $b): void
19+
{
20+
assertType('PHPStan\Fixture\TestBitwiseOperand', $a | $b);
21+
}
22+
23+
function testBitwiseXor(TestBitwiseOperand $a, TestBitwiseOperand $b): void
24+
{
25+
assertType('PHPStan\Fixture\TestBitwiseOperand', $a ^ $b);
26+
}
27+
28+
// =============================================================================
29+
// Arithmetic operator extension tests (via TestDecimal)
30+
// =============================================================================
31+
32+
function testArithmeticAdd(TestDecimal $a, TestDecimal $b): void
33+
{
34+
assertType('PHPStan\Fixture\TestDecimal', $a + $b);
35+
}
36+
37+
function testArithmeticSub(TestDecimal $a, TestDecimal $b): void
38+
{
39+
assertType('PHPStan\Fixture\TestDecimal', $a - $b);
40+
}
41+
42+
function testArithmeticMul(TestDecimal $a, TestDecimal $b): void
43+
{
44+
assertType('PHPStan\Fixture\TestDecimal', $a * $b);
45+
}
46+
47+
function testArithmeticDiv(TestDecimal $a, TestDecimal $b): void
48+
{
49+
assertType('PHPStan\Fixture\TestDecimal', $a / $b);
50+
}
51+
52+
function testArithmeticPow(TestDecimal $a, TestDecimal $b): void
53+
{
54+
assertType('PHPStan\Fixture\TestDecimal', $a ** $b);
55+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
-
3+
class: PHPStan\Type\TestBitwiseOperatorTypeSpecifyingExtension
4+
tags:
5+
- phpstan.broker.operatorTypeSpecifyingExtension
6+
-
7+
class: PHPStan\Type\TestDecimalOperatorTypeSpecifyingExtension
8+
tags:
9+
- phpstan.broker.operatorTypeSpecifyingExtension
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Fixture;
4+
5+
/**
6+
* Test fixture for verifying bitwise operator type specifying extensions.
7+
*/
8+
final class TestBitwiseOperand
9+
{
10+
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type;
4+
5+
use PHPStan\Fixture\TestBitwiseOperand;
6+
use function in_array;
7+
8+
/**
9+
* Test extension for verifying that bitwise operators call type specifying extensions.
10+
*/
11+
final class TestBitwiseOperatorTypeSpecifyingExtension implements OperatorTypeSpecifyingExtension
12+
{
13+
14+
public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type $rightSide): bool
15+
{
16+
$testType = new ObjectType(TestBitwiseOperand::class);
17+
18+
return in_array($operatorSigil, ['&', '|', '^'], true)
19+
&& $testType->isSuperTypeOf($leftSide)->yes()
20+
&& $testType->isSuperTypeOf($rightSide)->yes();
21+
}
22+
23+
public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type
24+
{
25+
return new ObjectType(TestBitwiseOperand::class);
26+
}
27+
28+
}

0 commit comments

Comments
 (0)