Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for maths with constant numeric string #2209

Merged
merged 7 commits into from
Jun 22, 2023
Merged
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
50 changes: 28 additions & 22 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1446,45 +1446,50 @@ private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type
*/
private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
{
if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType)
) {
$types = TypeCombinator::union($leftType, $rightType);
$leftNumberType = $leftType->toNumber();
$rightNumberType = $rightType->toNumber();

if ($leftType instanceof ConstantIntegerType) {
if (
!$types instanceof MixedType
&& (
$rightNumberType instanceof IntegerRangeType
|| $rightNumberType instanceof ConstantIntegerType
|| $rightNumberType instanceof UnionType
)
) {
if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) {
return $this->integerRangeMath(
$leftType,
$leftNumberType,
$expr,
$rightType,
$rightNumberType,
);
} elseif ($leftType instanceof UnionType) {

} elseif ($leftNumberType instanceof UnionType) {
$unionParts = [];

foreach ($leftType->getTypes() as $type) {
if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
$unionParts[] = $this->integerRangeMath($type, $expr, $rightType);
foreach ($leftNumberType->getTypes() as $type) {
$numberType = $type->toNumber();
if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
VincentLanglet marked this conversation as resolved.
Show resolved Hide resolved
$unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType);
} else {
$unionParts[] = $type;
$unionParts[] = $numberType;
}
}

$union = TypeCombinator::union(...$unionParts);
if ($leftType instanceof BenevolentUnionType) {
if ($leftNumberType instanceof BenevolentUnionType) {
return TypeUtils::toBenevolentUnion($union)->toNumber();
}

return $union->toNumber();
}

return $this->integerRangeMath($leftType, $expr, $rightType);
}

$specifiedTypes = $this->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
if ($specifiedTypes !== null) {
return $specifiedTypes;
}

$types = TypeCombinator::union($leftType, $rightType);
if (
$leftType->isArray()->yes()
|| $rightType->isArray()->yes()
Expand All @@ -1493,8 +1498,6 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
return new ErrorType();
}

$leftNumberType = $leftType->toNumber();
$rightNumberType = $rightType->toNumber();
if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
return new ErrorType();
}
Expand Down Expand Up @@ -1531,7 +1534,6 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
/**
* @param ConstantIntegerType|IntegerRangeType $range
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
* @param IntegerRangeType|ConstantIntegerType|UnionType $operand
*/
private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
{
Expand All @@ -1548,8 +1550,9 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
$unionParts = [];

foreach ($operand->getTypes() as $type) {
if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
$unionParts[] = $this->integerRangeMath($range, $node, $type);
$numberType = $type->toNumber();
if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
$unionParts[] = $this->integerRangeMath($range, $node, $numberType);
} else {
$unionParts[] = $type->toNumber();
}
Expand All @@ -1563,12 +1566,15 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
return $union->toNumber();
}

$operand = $operand->toNumber();
if ($operand instanceof IntegerRangeType) {
$operandMin = $operand->getMin();
$operandMax = $operand->getMax();
} else {
} elseif ($operand instanceof ConstantIntegerType) {
$operandMin = $operand->getValue();
$operandMax = $operand->getValue();
} else {
return $operand;
}

if ($node instanceof BinaryOp\Plus) {
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8803.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8827.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8609.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php');
Expand Down
36 changes: 36 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8803.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Bug8803;

use function PHPStan\Testing\assertType;

class HelloWorld
VincentLanglet marked this conversation as resolved.
Show resolved Hide resolved
{
public function sayHello(): void
{
$from = new \DateTimeImmutable('2023-01-30');
for ($offset = 1; $offset <= 14; $offset++) {
$value = $from->format('N') + $offset;
if ($value > 7) {
}

$value2 = $offset + $from->format('N');
$value3 = '1e3' + $offset;
$value4 = $offset + '1e3';

assertType("'1'|'2'|'3'|'4'|'5'|'6'|'7'", $from->format('N'));
assertType('int<1, 14>', $offset);
assertType('int<2, 21>', $value);
assertType('int<2, 21>', $value2);
assertType('float', $value3);
assertType('float', $value4);
}
}

public function testWithMixed(mixed $a, mixed $b): void
{
assertType('(array|float|int)', $a + $b);
assertType('(float|int)', 3 + $b);
assertType('(float|int)', $a + 3);
}
}
27 changes: 27 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8827.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace Bug8827;

use function PHPStan\Testing\assertType;

class HelloWorld
{
public function test(): void
{
$efferent = $afferent = 0;
$nbElements = random_int(0, 30);

$elements = array_fill(0, $nbElements, random_int(0, 2));

foreach ($elements as $element)
{
$efferent += ($element === 1);
$afferent += ($element === 2);
}

assertType('int<0, max>', $efferent); // Expected: int<0, $nbElements> | Actual: 0|1
assertType('int<0, max>', $afferent); // Expected: int<0, $nbElements> | Actual: 0|1

$instability = ($efferent + $afferent > 0) ? $efferent / ($afferent + $efferent) : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ public function testBug7075(): void
$this->analyse([__DIR__ . '/data/bug-7075.php'], []);
}

public function testBug8803(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/data/bug-8803.php'], []);
}

public function testBug8938(): void
{
$this->treatPhpDocTypesAsCertain = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ public function testBug3515(): void
$this->analyse([__DIR__ . '/data/bug-3515.php'], []);
}

public function testBug8827(): void
{
$this->analyse([__DIR__ . '/../../Analyser/data/bug-8827.php'], []);
}

public function testRuleWithNullsafeVariant(): void
{
if (PHP_VERSION_ID < 80000) {
Expand Down