Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -2034,6 +2052,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();
Expand Down Expand Up @@ -2073,12 +2097,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()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Testing\TypeInferenceTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class OperatorTypeSpecifyingExtensionTypeInferenceTest extends TypeInferenceTestCase
{

public static function dataAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/operator-type-specifying-extension.php');
}

/**
* @param mixed ...$args
*/
#[DataProvider('dataAsserts')]
public function testAsserts(
string $assertType,
string $file,
...$args,
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/operator-type-specifying-extension.neon',
];
}

}
55 changes: 55 additions & 0 deletions tests/PHPStan/Analyser/data/operator-type-specifying-extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace OperatorExtensionTest;

use PHPStan\Fixture\TestBitwiseOperand;
use PHPStan\Fixture\TestDecimal;
use function PHPStan\Testing\assertType;

// =============================================================================
// Bitwise operator extension tests
// =============================================================================

function testBitwiseAnd(TestBitwiseOperand $a, TestBitwiseOperand $b): void
{
assertType('PHPStan\Fixture\TestBitwiseOperand', $a & $b);
}

function testBitwiseOr(TestBitwiseOperand $a, TestBitwiseOperand $b): void
{
assertType('PHPStan\Fixture\TestBitwiseOperand', $a | $b);
}

function testBitwiseXor(TestBitwiseOperand $a, TestBitwiseOperand $b): void
{
assertType('PHPStan\Fixture\TestBitwiseOperand', $a ^ $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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
-
class: PHPStan\Type\TestBitwiseOperatorTypeSpecifyingExtension
tags:
- phpstan.broker.operatorTypeSpecifyingExtension
-
class: PHPStan\Type\TestDecimalOperatorTypeSpecifyingExtension
tags:
- phpstan.broker.operatorTypeSpecifyingExtension
11 changes: 11 additions & 0 deletions tests/PHPStan/Fixture/TestBitwiseOperand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace PHPStan\Fixture;

/**
* Test fixture for verifying bitwise operator type specifying extensions.
*/
final class TestBitwiseOperand
{

}
28 changes: 28 additions & 0 deletions tests/PHPStan/Type/TestBitwiseOperatorTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PHPStan\Fixture\TestBitwiseOperand;
use function in_array;

/**
* Test extension for verifying that bitwise operators call type specifying extensions.
*/
final class TestBitwiseOperatorTypeSpecifyingExtension implements OperatorTypeSpecifyingExtension
{

public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type $rightSide): bool
{
$testType = new ObjectType(TestBitwiseOperand::class);

return in_array($operatorSigil, ['&', '|', '^'], true)
&& $testType->isSuperTypeOf($leftSide)->yes()
&& $testType->isSuperTypeOf($rightSide)->yes();
}

public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type
{
return new ObjectType(TestBitwiseOperand::class);
}

}
Loading