Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,10 @@ services:
arguments:
reportMagicProperties: %reportMagicProperties%
checkDynamicProperties: %checkDynamicProperties%
-
class: PHPStan\Type\Php\BcMathNumberOperatorTypeSpecifyingExtension
tags:
- phpstan.broker.operatorTypeSpecifyingExtension

-
class: PHPStan\Rules\Properties\UninitializedPropertyRule
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,4 +400,9 @@ public function substrReturnFalseInsteadOfEmptyString(): bool
return $this->versionId < 80000;
}

public function supportsBcMathNumberOperatorOverloading(): bool
{
return $this->versionId >= 80400;
}

}
15 changes: 10 additions & 5 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,11 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback):
return $this->getNeverType($leftType, $rightType);
}

$extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType);
if ($extensionSpecified !== null) {
return $extensionSpecified;
}

if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
return new ErrorType();
}
Expand Down Expand Up @@ -1234,16 +1239,16 @@ public function getPowType(Expr $left, Expr $right, callable $getTypeCallback):
$leftType = $getTypeCallback($left);
$rightType = $getTypeCallback($right);

$exponentiatedTyped = $leftType->exponentiate($rightType);
if (!$exponentiatedTyped instanceof ErrorType) {
return $exponentiatedTyped;
}

$extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType);
if ($extensionSpecified !== null) {
return $extensionSpecified;
}

$exponentiatedTyped = $leftType->exponentiate($rightType);
if (!$exponentiatedTyped instanceof ErrorType) {
return $exponentiatedTyped;
}

return new ErrorType();
}

Expand Down
15 changes: 14 additions & 1 deletion src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
Expand Down Expand Up @@ -593,6 +595,14 @@ public function toFloat(): Type

public function toString(): Type
{
if ($this->isInstanceOf('BcMath\Number')->yes()) {
return new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
new AccessoryNonEmptyStringType(),
]);
}

$classReflection = $this->getClassReflection();
if ($classReflection === null) {
return new ErrorType();
Expand Down Expand Up @@ -678,7 +688,10 @@ public function toCoercedArgumentType(bool $strictTypes): Type

public function toBoolean(): BooleanType
{
if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
if (
$this->isInstanceOf('SimpleXMLElement')->yes()
|| $this->isInstanceOf('BcMath\Number')->yes()
) {
return new BooleanType();
}

Expand Down
53 changes: 53 additions & 0 deletions src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PHPStan\Php\PhpVersion;
use PHPStan\Type\ErrorType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\OperatorTypeSpecifyingExtension;
use PHPStan\Type\Type;
use function in_array;

final class BcMathNumberOperatorTypeSpecifyingExtension implements OperatorTypeSpecifyingExtension
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type $rightSide): bool
{
if (!$this->phpVersion->supportsBcMathNumberOperatorOverloading() || $leftSide instanceof NeverType || $rightSide instanceof NeverType) {
return false;
}

$bcMathNumberType = new ObjectType('BcMath\Number');

return in_array($operatorSigil, ['-', '+', '*', '/', '**', '%'], true)
&& (
$bcMathNumberType->isSuperTypeOf($leftSide)->yes()
|| $bcMathNumberType->isSuperTypeOf($rightSide)->yes()
);
}

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

if (
$otherSide->isInteger()->yes()
|| $otherSide->isNumericString()->yes()
|| $bcMathNumberType->isSuperTypeOf($otherSide)->yes()
) {
return $bcMathNumberType;
}

return new ErrorType();
}

}
4 changes: 4 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ private static function findTestFiles(): iterable
yield __DIR__ . '/../Rules/Comparison/data/bug-9499.php';
}

if (PHP_VERSION_ID >= 80400) {
yield __DIR__ . '/../Rules/Operators/data/bcmath-number.php';
}

yield __DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php';
yield __DIR__ . '/../Rules/Comparison/data/bug-5365.php';
yield __DIR__ . '/../Rules/Comparison/data/bug-6551.php';
Expand Down
Loading
Loading