Skip to content

Commit be7e86c

Browse files
Add support for math with constant numeric string
1 parent 5b89916 commit be7e86c

File tree

6 files changed

+104
-22
lines changed

6 files changed

+104
-22
lines changed

Diff for: src/Reflection/InitializerExprTypeResolver.php

+28-22
Original file line numberDiff line numberDiff line change
@@ -1446,45 +1446,50 @@ private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type
14461446
*/
14471447
private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
14481448
{
1449-
if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
1450-
($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType)
1451-
) {
1449+
$types = TypeCombinator::union($leftType, $rightType);
1450+
$leftNumberType = $leftType->toNumber();
1451+
$rightNumberType = $rightType->toNumber();
14521452

1453-
if ($leftType instanceof ConstantIntegerType) {
1453+
if (
1454+
!$types instanceof MixedType
1455+
&& (
1456+
$rightNumberType instanceof IntegerRangeType
1457+
|| $rightNumberType instanceof ConstantIntegerType
1458+
|| $rightNumberType instanceof UnionType
1459+
)
1460+
) {
1461+
if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) {
14541462
return $this->integerRangeMath(
1455-
$leftType,
1463+
$leftNumberType,
14561464
$expr,
1457-
$rightType,
1465+
$rightNumberType,
14581466
);
1459-
} elseif ($leftType instanceof UnionType) {
1460-
1467+
} elseif ($leftNumberType instanceof UnionType) {
14611468
$unionParts = [];
14621469

1463-
foreach ($leftType->getTypes() as $type) {
1464-
if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
1465-
$unionParts[] = $this->integerRangeMath($type, $expr, $rightType);
1470+
foreach ($leftNumberType->getTypes() as $type) {
1471+
$numberType = $type->toNumber();
1472+
if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
1473+
$unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType);
14661474
} else {
1467-
$unionParts[] = $type;
1475+
$unionParts[] = $numberType;
14681476
}
14691477
}
14701478

14711479
$union = TypeCombinator::union(...$unionParts);
1472-
if ($leftType instanceof BenevolentUnionType) {
1480+
if ($leftNumberType instanceof BenevolentUnionType) {
14731481
return TypeUtils::toBenevolentUnion($union)->toNumber();
14741482
}
14751483

14761484
return $union->toNumber();
14771485
}
1478-
1479-
return $this->integerRangeMath($leftType, $expr, $rightType);
14801486
}
14811487

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

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

1496-
$leftNumberType = $leftType->toNumber();
1497-
$rightNumberType = $rightType->toNumber();
14981501
if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
14991502
return new ErrorType();
15001503
}
@@ -1531,7 +1534,6 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
15311534
/**
15321535
* @param ConstantIntegerType|IntegerRangeType $range
15331536
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
1534-
* @param IntegerRangeType|ConstantIntegerType|UnionType $operand
15351537
*/
15361538
private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
15371539
{
@@ -1548,8 +1550,9 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
15481550
$unionParts = [];
15491551

15501552
foreach ($operand->getTypes() as $type) {
1551-
if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
1552-
$unionParts[] = $this->integerRangeMath($range, $node, $type);
1553+
$numberType = $type->toNumber();
1554+
if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
1555+
$unionParts[] = $this->integerRangeMath($range, $node, $numberType);
15531556
} else {
15541557
$unionParts[] = $type->toNumber();
15551558
}
@@ -1563,12 +1566,15 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
15631566
return $union->toNumber();
15641567
}
15651568

1569+
$operand = $operand->toNumber();
15661570
if ($operand instanceof IntegerRangeType) {
15671571
$operandMin = $operand->getMin();
15681572
$operandMax = $operand->getMax();
1569-
} else {
1573+
} elseif ($operand instanceof ConstantIntegerType) {
15701574
$operandMin = $operand->getValue();
15711575
$operandMax = $operand->getValue();
1576+
} else {
1577+
return $operand;
15721578
}
15731579

15741580
if ($node instanceof BinaryOp\Plus) {

Diff for: tests/PHPStan/Analyser/NodeScopeResolverTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,8 @@ public function dataFileAsserts(): iterable
12501250
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
12511251
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
12521252
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
1253+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8803.php');
1254+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8827.php');
12531255
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
12541256
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8609.php');
12551257
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php');

Diff for: tests/PHPStan/Analyser/data/bug-8803.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Bug8803;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function sayHello(): void
10+
{
11+
$from = new \DateTimeImmutable('2023-01-30');
12+
for ($offset = 1; $offset <= 14; $offset++) {
13+
$value = $from->format('N') + $offset;
14+
if ($value > 7) {
15+
}
16+
17+
$value2 = $offset + $from->format('N');
18+
$value3 = '1e3' + $offset;
19+
$value4 = $offset + '1e3';
20+
21+
assertType("'1'|'2'|'3'|'4'|'5'|'6'|'7'", $from->format('N'));
22+
assertType('int<1, 14>', $offset);
23+
assertType('int<2, 21>', $value);
24+
assertType('int<2, 21>', $value2);
25+
assertType('float', $value3);
26+
assertType('float', $value4);
27+
}
28+
}
29+
30+
public function testWithMixed(mixed $a, mixed $b): void
31+
{
32+
assertType('(array|float|int)', $a + $b);
33+
assertType('(float|int)', 3 + $b);
34+
assertType('(float|int)', $a + 3);
35+
}
36+
}

Diff for: tests/PHPStan/Analyser/data/bug-8827.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8827;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function test(): void
10+
{
11+
$efferent = $afferent = 0;
12+
$nbElements = random_int(0, 30);
13+
14+
$elements = array_fill(0, $nbElements, random_int(0, 2));
15+
16+
foreach ($elements as $element)
17+
{
18+
$efferent += ($element === 1);
19+
$afferent += ($element === 2);
20+
}
21+
22+
assertType('int<0, max>', $efferent); // Expected: int<0, $nbElements> | Actual: 0|1
23+
assertType('int<0, max>', $afferent); // Expected: int<0, $nbElements> | Actual: 0|1
24+
25+
$instability = ($efferent + $afferent > 0) ? $efferent / ($afferent + $efferent) : 0;
26+
}
27+
}

Diff for: tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ public function testBug7075(): void
207207
$this->analyse([__DIR__ . '/data/bug-7075.php'], []);
208208
}
209209

210+
public function testBug8803(): void
211+
{
212+
$this->treatPhpDocTypesAsCertain = true;
213+
$this->analyse([__DIR__ . '/../../Analyser/data/bug-8803.php'], []);
214+
}
215+
210216
public function testBug8938(): void
211217
{
212218
$this->treatPhpDocTypesAsCertain = true;

Diff for: tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ public function testBug3515(): void
259259
$this->analyse([__DIR__ . '/data/bug-3515.php'], []);
260260
}
261261

262+
public function testBug8827(): void
263+
{
264+
$this->analyse([__DIR__ . '/../../Analyser/data/bug-8827.php'], []);
265+
}
266+
262267
public function testRuleWithNullsafeVariant(): void
263268
{
264269
if (PHP_VERSION_ID < 80000) {

0 commit comments

Comments
 (0)