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
42 changes: 36 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()
Copy link
Contributor

Choose a reason for hiding this comment

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

this change only makes sense when getBitwiseAndTypeFromTypes is no longer public because otherwise you can by-pass extensions.

but it seems like getBitwiseAndTypeFromTypes is not called from anywhere outside this class and since InitializerExprTypeResolver is not @api-annotated, I think we can reduce visibility for such methods to private.

affects multiple methods in this class

Copy link
Member

Choose a reason for hiding this comment

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

Please don’t change this, I’m pretty sure I use these in my refactoring at #5224

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

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

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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()
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',
];
}

}
65 changes: 65 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,65 @@
<?php declare(strict_types = 1);

namespace OperatorExtensionTest;

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

// =============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

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

misses tests for shift left/right

// 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);
}

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

function testShiftRight(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