Skip to content

Commit d8ef02e

Browse files
JokubasRjanedbal
andauthored
forbidArithmeticOperationOnNonNumber: add support for BcMath\Number (#321)
Co-authored-by: Jan Nedbal <janedbal@gmail.com>
1 parent 90aea84 commit d8ef02e

File tree

6 files changed

+271
-7
lines changed

6 files changed

+271
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,11 @@ class EnforceReadonlyPublicPropertyRule {
227227
```
228228

229229
### forbidArithmeticOperationOnNonNumber
230-
- Disallows using [arithmetic operators](https://www.php.net/manual/en/language.operators.arithmetic.php) with non-numeric types (only float and int is allowed)
230+
- Disallows using [arithmetic operators](https://www.php.net/manual/en/language.operators.arithmetic.php) with non-numeric types (only `float`, `int` and `BcMath\Number` is allowed)
231231
- You can allow numeric-string by using `allowNumericString: true` configuration
232232
- Modulo operator (`%`) allows only integers as it [emits deprecation otherwise](https://3v4l.org/VpVoq)
233233
- Plus operator is allowed for merging arrays
234+
- `float` and `BcMath\Number` cannot be combined as it emits deprecations
234235

235236
```php
236237
function add(string $a, string $b) {

src/Rule/ForbidArithmeticOperationOnNonNumberRule.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use PHPStan\Rules\RuleErrorBuilder;
1919
use PHPStan\Type\FloatType;
2020
use PHPStan\Type\IntegerType;
21+
use PHPStan\Type\ObjectType;
2122
use PHPStan\Type\Type;
23+
use PHPStan\Type\TypeTraverser;
2224
use PHPStan\Type\UnionType;
2325
use PHPStan\Type\VerbosityLevel;
2426
use function sprintf;
@@ -113,6 +115,12 @@ private function processBinary(
113115
return []; // array merge syntax
114116
}
115117

118+
if (($this->containsBcMathNumber($leftType) && $this->isFloat($rightType))
119+
|| ($this->isFloat($leftType) && $this->containsBcMathNumber($rightType))
120+
) {
121+
return $this->buildBinaryErrors($operator, 'BcMath\\Number and float', $leftType, $rightType);
122+
}
123+
116124
if (
117125
$operator === '%' &&
118126
(!$leftType->isInteger()->yes() || !$rightType->isInteger()->yes())
@@ -129,13 +137,13 @@ private function processBinary(
129137

130138
private function isNumeric(Type $type): bool
131139
{
132-
$int = new IntegerType();
133-
$float = new FloatType();
134-
$intOrFloat = new UnionType([$int, $float]);
140+
$numericUnion = new UnionType([
141+
new IntegerType(),
142+
new FloatType(),
143+
new ObjectType('BcMath\Number'),
144+
]);
135145

136-
return $int->isSuperTypeOf($type)->yes()
137-
|| $float->isSuperTypeOf($type)->yes()
138-
|| $intOrFloat->isSuperTypeOf($type)->yes()
146+
return $numericUnion->isSuperTypeOf($type)->yes()
139147
|| ($this->allowNumericString && $type->isNumericString()->yes());
140148
}
141149

@@ -164,4 +172,25 @@ private function buildBinaryErrors(
164172
return [$error];
165173
}
166174

175+
private function isFloat(Type $type): bool
176+
{
177+
$float = new FloatType();
178+
179+
return $type->isSuperTypeOf($float)->yes();
180+
}
181+
182+
private function containsBcMathNumber(Type $type): bool
183+
{
184+
$bcMathFound = false;
185+
$bcMathNumber = new ObjectType('BcMath\Number');
186+
187+
TypeTraverser::map($type, static function (Type $traversedTyped, callable $traverse) use (&$bcMathFound, $bcMathNumber): Type {
188+
if ($bcMathNumber->isSuperTypeOf($traversedTyped)->yes()) {
189+
$bcMathFound = true;
190+
}
191+
return $traverse($traversedTyped);
192+
});
193+
return $bcMathFound;
194+
}
195+
167196
}

tests/Rule/ForbidArithmeticOperationOnNonNumberRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ public function testNoNumericString(): void
3535
$this->analyseFile(__DIR__ . '/data/ForbidArithmeticOperationOnNonNumberRule/no-numeric-string.php');
3636
}
3737

38+
public function testBcMathNumber(): void
39+
{
40+
$this->allowNumericString = true;
41+
$this->analyseFile(__DIR__ . '/data/ForbidArithmeticOperationOnNonNumberRule/bcmath-number.php');
42+
}
43+
44+
public function testBcMathNumberNoNumeric(): void
45+
{
46+
$this->allowNumericString = false;
47+
$this->analyseFile(__DIR__ . '/data/ForbidArithmeticOperationOnNonNumberRule/bcmath-number-no-numeric.php');
48+
}
49+
3850
protected function shouldFailOnPhpErrors(): bool
3951
{
4052
return false; // https://github.com/phpstan/phpstan-src/pull/3031
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ShipMonk\PHPStan\Rule\data\ForbidArithmeticOperationOnNonNumberRule;
4+
use BcMath\Number;
5+
6+
class BcMathNumberNoNumeric {
7+
8+
/**
9+
* @param numeric-string $ns
10+
*/
11+
public function testBcMathNumbers(Number $n, string $ns, int $int, float $float, int|float $intFloat): void {
12+
+$n;
13+
-$n;
14+
15+
$x = $n + $n;
16+
$x = $n - $n;
17+
$x = $n * $n;
18+
$x = $n / $n;
19+
20+
$x = $n + $int;
21+
$x = $int + $n;
22+
$x = $n + $ns; // error: Using + over non-number (BcMath\Number + string)
23+
$x = $ns + $n; // error: Using + over non-number (string + BcMath\Number)
24+
$x = $n - $int;
25+
$x = $int - $n;
26+
$x = $n - $ns; // error: Using - over non-number (BcMath\Number - string)
27+
$x = $ns - $n; // error: Using - over non-number (string - BcMath\Number)
28+
$x = $n * $int;
29+
$x = $int * $n;
30+
$x = $n * $ns; // error: Using * over non-number (BcMath\Number * string)
31+
$x = $ns * $n; // error: Using * over non-number (string * BcMath\Number)
32+
$x = $n / $int;
33+
$x = $int / $n;
34+
$x = $n / $ns; // error: Using / over non-number (BcMath\Number / string)
35+
$x = $ns / $n; // error: Using / over non-number (string / BcMath\Number)
36+
}
37+
}
38+
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ForbidArithmeticOperationOnNonNumberRule;
4+
5+
use BcMath\Number;
6+
7+
class BcMathNumber {
8+
9+
/**
10+
* @param numeric-string $ns
11+
*/
12+
public function testBcMathNumbers(
13+
object $object,
14+
Number $number,
15+
string $ns,
16+
int $int,
17+
float $float,
18+
bool|float $boolFloat,
19+
int|float $intFloat,
20+
Number|int $nInt,
21+
Number|float $nFloat,
22+
Number|int|float $nIntFloat,
23+
Number|int|float|bool $nIntFloatBool
24+
): void {
25+
+$number;
26+
-$number;
27+
28+
$x = $object + $float; // error: Using + over non-number (object + float)
29+
$x = $object + $number; // error: Using + over non-number (object + BcMath\Number)
30+
31+
$x = $number + $number;
32+
$x = $number - $number;
33+
$x = $number * $number;
34+
$x = $number / $number;
35+
36+
$x = $number / $boolFloat; // error: Using / over BcMath\Number and float (BcMath\Number / bool|float)
37+
38+
$x = $number + $int;
39+
$x = $int + $number;
40+
$x = $number + $ns;
41+
$x = $ns + $number;
42+
$x = $number - $int;
43+
$x = $int - $number;
44+
$x = $number - $ns;
45+
$x = $ns - $number;
46+
$x = $number * $int;
47+
$x = $int * $number;
48+
$x = $number * $ns;
49+
$x = $ns * $number;
50+
$x = $number / $int;
51+
$x = $int / $number;
52+
$x = $number / $ns;
53+
$x = $ns / $number;
54+
55+
$x = $nInt + $int;
56+
$x = $int + $nInt;
57+
$x = $nInt + $ns;
58+
$x = $ns + $nInt;
59+
$x = $nInt - $int;
60+
$x = $int - $nInt;
61+
$x = $nInt - $ns;
62+
$x = $ns - $nInt;
63+
$x = $nInt * $int;
64+
$x = $int * $nInt;
65+
$x = $nInt * $ns;
66+
$x = $ns * $nInt;
67+
$x = $nInt / $int;
68+
$x = $int / $nInt;
69+
$x = $nInt / $ns;
70+
$x = $ns / $nInt;
71+
72+
$x = $nFloat + $int;
73+
$x = $int + $nFloat;
74+
$x = $nFloat + $ns;
75+
$x = $ns + $nFloat;
76+
$x = $nFloat - $int;
77+
$x = $int - $nFloat;
78+
$x = $nFloat - $ns;
79+
$x = $ns - $nFloat;
80+
$x = $nFloat * $int;
81+
$x = $int * $nFloat;
82+
$x = $nFloat * $ns;
83+
$x = $ns * $nFloat;
84+
$x = $nFloat / $int;
85+
$x = $int / $nFloat;
86+
$x = $nFloat / $ns;
87+
$x = $ns / $nFloat;
88+
89+
$x = $nIntFloat + $int;
90+
$x = $int + $nIntFloat;
91+
$x = $nIntFloat + $ns;
92+
$x = $ns + $nIntFloat;
93+
$x = $nIntFloat - $int;
94+
$x = $int - $nIntFloat;
95+
$x = $nIntFloat - $ns;
96+
$x = $ns - $nIntFloat;
97+
$x = $nIntFloat * $int;
98+
$x = $int * $nIntFloat;
99+
$x = $nIntFloat * $ns;
100+
$x = $ns * $nIntFloat;
101+
$x = $nIntFloat / $int;
102+
$x = $int / $nIntFloat;
103+
$x = $nIntFloat / $ns;
104+
$x = $ns / $nIntFloat;
105+
106+
$x = $number + $float; // error: Using + over BcMath\Number and float (BcMath\Number + float)
107+
$x = $float + $number; // error: Using + over BcMath\Number and float (float + BcMath\Number)
108+
$x = $number - $float; // error: Using - over BcMath\Number and float (BcMath\Number - float)
109+
$x = $float - $number; // error: Using - over BcMath\Number and float (float - BcMath\Number)
110+
$x = $number * $float; // error: Using * over BcMath\Number and float (BcMath\Number * float)
111+
$x = $float * $number; // error: Using * over BcMath\Number and float (float * BcMath\Number)
112+
$x = $number / $float; // error: Using / over BcMath\Number and float (BcMath\Number / float)
113+
$x = $float / $number; // error: Using / over BcMath\Number and float (float / BcMath\Number)
114+
$x = $number % $float; // error: Using % over BcMath\Number and float (BcMath\Number % float)
115+
$x = $float % $number; // error: Using % over BcMath\Number and float (float % BcMath\Number)
116+
$x = $number ** $float; // error: Using ** over BcMath\Number and float (BcMath\Number ** float)
117+
$x = $float ** $number; // error: Using ** over BcMath\Number and float (float ** BcMath\Number)
118+
$x = $number + $intFloat; // error: Using + over BcMath\Number and float (BcMath\Number + float|int)
119+
$x = $intFloat + $number; // error: Using + over BcMath\Number and float (float|int + BcMath\Number)
120+
121+
$x = $nInt + $float; // error: Using + over BcMath\Number and float (BcMath\Number|int + float)
122+
$x = $float + $nInt; // error: Using + over BcMath\Number and float (float + BcMath\Number|int)
123+
$x = $nInt - $float; // error: Using - over BcMath\Number and float (BcMath\Number|int - float)
124+
$x = $float - $nInt; // error: Using - over BcMath\Number and float (float - BcMath\Number|int)
125+
$x = $nInt * $float; // error: Using * over BcMath\Number and float (BcMath\Number|int * float)
126+
$x = $float * $nInt; // error: Using * over BcMath\Number and float (float * BcMath\Number|int)
127+
$x = $nInt / $float; // error: Using / over BcMath\Number and float (BcMath\Number|int / float)
128+
$x = $float / $nInt; // error: Using / over BcMath\Number and float (float / BcMath\Number|int)
129+
$x = $nInt % $float; // error: Using % over BcMath\Number and float (BcMath\Number|int % float)
130+
$x = $float % $nInt; // error: Using % over BcMath\Number and float (float % BcMath\Number|int)
131+
$x = $nInt ** $float; // error: Using ** over BcMath\Number and float (BcMath\Number|int ** float)
132+
$x = $float ** $nInt; // error: Using ** over BcMath\Number and float (float ** BcMath\Number|int)
133+
$x = $nInt + $intFloat; // error: Using + over BcMath\Number and float (BcMath\Number|int + float|int)
134+
$x = $intFloat + $nInt; // error: Using + over BcMath\Number and float (float|int + BcMath\Number|int)
135+
136+
$x = $nFloat + $float; // error: Using + over BcMath\Number and float (BcMath\Number|float + float)
137+
$x = $float + $nFloat; // error: Using + over BcMath\Number and float (float + BcMath\Number|float)
138+
$x = $nFloat - $float; // error: Using - over BcMath\Number and float (BcMath\Number|float - float)
139+
$x = $float - $nFloat; // error: Using - over BcMath\Number and float (float - BcMath\Number|float)
140+
$x = $nFloat * $float; // error: Using * over BcMath\Number and float (BcMath\Number|float * float)
141+
$x = $float * $nFloat; // error: Using * over BcMath\Number and float (float * BcMath\Number|float)
142+
$x = $nFloat / $float; // error: Using / over BcMath\Number and float (BcMath\Number|float / float)
143+
$x = $float / $nFloat; // error: Using / over BcMath\Number and float (float / BcMath\Number|float)
144+
$x = $nFloat % $float; // error: Using % over BcMath\Number and float (BcMath\Number|float % float)
145+
$x = $float % $nFloat; // error: Using % over BcMath\Number and float (float % BcMath\Number|float)
146+
$x = $nFloat ** $float; // error: Using ** over BcMath\Number and float (BcMath\Number|float ** float)
147+
$x = $float ** $nFloat; // error: Using ** over BcMath\Number and float (float ** BcMath\Number|float)
148+
$x = $nFloat + $intFloat; // error: Using + over BcMath\Number and float (BcMath\Number|float + float|int)
149+
$x = $intFloat + $nFloat; // error: Using + over BcMath\Number and float (float|int + BcMath\Number|float)
150+
151+
$x = $nIntFloat + $float; // error: Using + over BcMath\Number and float (BcMath\Number|float|int + float)
152+
$x = $float + $nIntFloat; // error: Using + over BcMath\Number and float (float + BcMath\Number|float|int)
153+
$x = $nIntFloat - $float; // error: Using - over BcMath\Number and float (BcMath\Number|float|int - float)
154+
$x = $float - $nIntFloat; // error: Using - over BcMath\Number and float (float - BcMath\Number|float|int)
155+
$x = $nIntFloat * $float; // error: Using * over BcMath\Number and float (BcMath\Number|float|int * float)
156+
$x = $float * $nIntFloat; // error: Using * over BcMath\Number and float (float * BcMath\Number|float|int)
157+
$x = $nIntFloat / $float; // error: Using / over BcMath\Number and float (BcMath\Number|float|int / float)
158+
$x = $float / $nIntFloat; // error: Using / over BcMath\Number and float (float / BcMath\Number|float|int)
159+
$x = $nIntFloat % $float; // error: Using % over BcMath\Number and float (BcMath\Number|float|int % float)
160+
$x = $float % $nIntFloat; // error: Using % over BcMath\Number and float (float % BcMath\Number|float|int)
161+
$x = $nIntFloat ** $float; // error: Using ** over BcMath\Number and float (BcMath\Number|float|int ** float)
162+
$x = $float ** $nIntFloat; // error: Using ** over BcMath\Number and float (float ** BcMath\Number|float|int)
163+
$x = $nIntFloat + $intFloat; // error: Using + over BcMath\Number and float (BcMath\Number|float|int + float|int)
164+
$x = $intFloat + $nIntFloat; // error: Using + over BcMath\Number and float (float|int + BcMath\Number|float|int)
165+
166+
$x = $nIntFloatBool + $float; // error: Using + over BcMath\Number and float (BcMath\Number|bool|float|int + float)
167+
$x = $float + $nIntFloatBool; // error: Using + over BcMath\Number and float (float + BcMath\Number|bool|float|int)
168+
$x = $nIntFloatBool - $float; // error: Using - over BcMath\Number and float (BcMath\Number|bool|float|int - float)
169+
$x = $float - $nIntFloatBool; // error: Using - over BcMath\Number and float (float - BcMath\Number|bool|float|int)
170+
$x = $nIntFloatBool * $float; // error: Using * over BcMath\Number and float (BcMath\Number|bool|float|int * float)
171+
$x = $float * $nIntFloatBool; // error: Using * over BcMath\Number and float (float * BcMath\Number|bool|float|int)
172+
$x = $nIntFloatBool / $float; // error: Using / over BcMath\Number and float (BcMath\Number|bool|float|int / float)
173+
$x = $float / $nIntFloatBool; // error: Using / over BcMath\Number and float (float / BcMath\Number|bool|float|int)
174+
$x = $nIntFloatBool % $float; // error: Using % over BcMath\Number and float (BcMath\Number|bool|float|int % float)
175+
$x = $float % $nIntFloatBool; // error: Using % over BcMath\Number and float (float % BcMath\Number|bool|float|int)
176+
$x = $nIntFloatBool ** $float; // error: Using ** over BcMath\Number and float (BcMath\Number|bool|float|int ** float)
177+
$x = $float ** $nIntFloatBool; // error: Using ** over BcMath\Number and float (float ** BcMath\Number|bool|float|int)
178+
$x = $nIntFloatBool + $intFloat; // error: Using + over BcMath\Number and float (BcMath\Number|bool|float|int + float|int)
179+
$x = $intFloat + $nIntFloatBool; // error: Using + over BcMath\Number and float (float|int + BcMath\Number|bool|float|int)
180+
}
181+
}
182+

tests/Rule/data/ForbidArithmeticOperationOnNonNumberRule/code.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public function testUnions(
130130
-$intFloat;
131131
-$intFloatString; // error: Using - over non-number (float|int|string)
132132
-$intArray; // error: Using - over non-number (array|int)
133+
134+
$intString - $intArray; // error: Using - over non-number (int|string - array|int)
133135
}
134136

135137
/**

0 commit comments

Comments
 (0)