diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 0891f487b7..f7a8f872e2 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -986,6 +986,12 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseAnd($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + return $this->getBitwiseAndTypeFromTypes($leftType, $rightType); } @@ -1044,6 +1050,12 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseOr($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + return $this->getBitwiseOrTypeFromTypes($leftType, $rightType); } @@ -1092,6 +1104,12 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseXor($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + return $this->getBitwiseXorTypeFromTypes($leftType, $rightType); } @@ -1756,6 +1774,12 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftLeft($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + return $this->getShiftLeftTypeFromTypes($left, $right, $leftType, $rightType); } @@ -1820,6 +1844,12 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftRight($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + return $this->getShiftRightTypeFromTypes($left, $right, $leftType, $rightType); } @@ -2034,6 +2064,12 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, */ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type { + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() + ->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + $types = TypeCombinator::union($leftType, $rightType); $leftNumberType = $leftType->toNumber(); $rightNumberType = $rightType->toNumber(); @@ -2073,12 +2109,6 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri } } - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry() - ->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - if ( $leftType->isArray()->yes() || $rightType->isArray()->yes() diff --git a/tests/PHPStan/Analyser/OperatorTypeSpecifyingExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/OperatorTypeSpecifyingExtensionTypeInferenceTest.php new file mode 100644 index 0000000000..bc2493201e --- /dev/null +++ b/tests/PHPStan/Analyser/OperatorTypeSpecifyingExtensionTypeInferenceTest.php @@ -0,0 +1,36 @@ +assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/operator-type-specifying-extension.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/operator-type-specifying-extension.php b/tests/PHPStan/Analyser/data/operator-type-specifying-extension.php new file mode 100644 index 0000000000..24a3e15a9c --- /dev/null +++ b/tests/PHPStan/Analyser/data/operator-type-specifying-extension.php @@ -0,0 +1,65 @@ +> $b); +} + +// ============================================================================= +// Arithmetic operator extension tests (via TestDecimal) +// ============================================================================= + +function testArithmeticAdd(TestDecimal $a, TestDecimal $b): void +{ + assertType('PHPStan\Fixture\TestDecimal', $a + $b); +} + +function testArithmeticSub(TestDecimal $a, TestDecimal $b): void +{ + assertType('PHPStan\Fixture\TestDecimal', $a - $b); +} + +function testArithmeticMul(TestDecimal $a, TestDecimal $b): void +{ + assertType('PHPStan\Fixture\TestDecimal', $a * $b); +} + +function testArithmeticDiv(TestDecimal $a, TestDecimal $b): void +{ + assertType('PHPStan\Fixture\TestDecimal', $a / $b); +} + +function testArithmeticPow(TestDecimal $a, TestDecimal $b): void +{ + assertType('PHPStan\Fixture\TestDecimal', $a ** $b); +} diff --git a/tests/PHPStan/Analyser/operator-type-specifying-extension.neon b/tests/PHPStan/Analyser/operator-type-specifying-extension.neon new file mode 100644 index 0000000000..c87a669bcb --- /dev/null +++ b/tests/PHPStan/Analyser/operator-type-specifying-extension.neon @@ -0,0 +1,9 @@ +services: + - + class: PHPStan\Type\TestBitwiseOperatorTypeSpecifyingExtension + tags: + - phpstan.broker.operatorTypeSpecifyingExtension + - + class: PHPStan\Type\TestDecimalOperatorTypeSpecifyingExtension + tags: + - phpstan.broker.operatorTypeSpecifyingExtension diff --git a/tests/PHPStan/Fixture/TestBitwiseOperand.php b/tests/PHPStan/Fixture/TestBitwiseOperand.php new file mode 100644 index 0000000000..70a90b64df --- /dev/null +++ b/tests/PHPStan/Fixture/TestBitwiseOperand.php @@ -0,0 +1,11 @@ +>'], true) + && $testType->isSuperTypeOf($leftSide)->yes() + && $testType->isSuperTypeOf($rightSide)->yes(); + } + + public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type + { + return new ObjectType(TestBitwiseOperand::class); + } + +}