Skip to content

Commit 41f3063

Browse files
committed
Arithmetic operators - respect levels more
1 parent 4428a5a commit 41f3063

17 files changed

+141
-36
lines changed

rules.neon

+4
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ rules:
2727
- PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule
2828
- PHPStan\Rules\StrictCalls\StrictFunctionCallsRule
2929
- PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule
30+
31+
services:
32+
-
33+
class: PHPStan\Rules\Operators\OperatorRuleHelper

src/Rules/Operators/OperandsInArithmeticAdditionRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
class OperandsInArithmeticAdditionRule implements \PHPStan\Rules\Rule
1010
{
1111

12+
/** @var OperatorRuleHelper */
13+
private $helper;
14+
15+
public function __construct(OperatorRuleHelper $helper)
16+
{
17+
$this->helper = $helper;
18+
}
19+
1220
public function getNodeType(): string
1321
{
1422
return \PhpParser\Node\Expr\BinaryOp\Plus::class;
@@ -30,13 +38,13 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
3038
}
3139

3240
$messages = [];
33-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
41+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
3442
$messages[] = sprintf(
3543
'Only numeric types are allowed in +, %s given on the left side.',
3644
$leftType->describe(VerbosityLevel::typeOnly())
3745
);
3846
}
39-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
47+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
4048
$messages[] = sprintf(
4149
'Only numeric types are allowed in +, %s given on the right side.',
4250
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticDivisionRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class OperandsInArithmeticDivisionRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var OperatorRuleHelper */
11+
private $helper;
12+
13+
public function __construct(OperatorRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\Div::class;
@@ -21,15 +29,15 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2129
{
2230
$messages = [];
2331
$leftType = $scope->getType($node->left);
24-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
32+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
2533
$messages[] = sprintf(
2634
'Only numeric types are allowed in /, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

3139
$rightType = $scope->getType($node->right);
32-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
40+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
3341
$messages[] = sprintf(
3442
'Only numeric types are allowed in /, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticExponentiationRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class OperandsInArithmeticExponentiationRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var OperatorRuleHelper */
11+
private $helper;
12+
13+
public function __construct(OperatorRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\Pow::class;
@@ -21,15 +29,15 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2129
{
2230
$messages = [];
2331
$leftType = $scope->getType($node->left);
24-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
32+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
2533
$messages[] = sprintf(
2634
'Only numeric types are allowed in **, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

3139
$rightType = $scope->getType($node->right);
32-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
40+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
3341
$messages[] = sprintf(
3442
'Only numeric types are allowed in **, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticModuloRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class OperandsInArithmeticModuloRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var OperatorRuleHelper */
11+
private $helper;
12+
13+
public function __construct(OperatorRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\Mod::class;
@@ -21,15 +29,15 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2129
{
2230
$messages = [];
2331
$leftType = $scope->getType($node->left);
24-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
32+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
2533
$messages[] = sprintf(
2634
'Only numeric types are allowed in %%, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

3139
$rightType = $scope->getType($node->right);
32-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
40+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
3341
$messages[] = sprintf(
3442
'Only numeric types are allowed in %%, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class OperandsInArithmeticMultiplicationRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var OperatorRuleHelper */
11+
private $helper;
12+
13+
public function __construct(OperatorRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\Mul::class;
@@ -21,15 +29,15 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2129
{
2230
$messages = [];
2331
$leftType = $scope->getType($node->left);
24-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
32+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
2533
$messages[] = sprintf(
2634
'Only numeric types are allowed in *, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

3139
$rightType = $scope->getType($node->right);
32-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
40+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
3341
$messages[] = sprintf(
3442
'Only numeric types are allowed in *, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticSubtractionRule.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class OperandsInArithmeticSubtractionRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var OperatorRuleHelper */
11+
private $helper;
12+
13+
public function __construct(OperatorRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\Minus::class;
@@ -21,15 +29,15 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2129
{
2230
$messages = [];
2331
$leftType = $scope->getType($node->left);
24-
if (!OperatorRuleHelper::isValidForArithmeticOperation($leftType)) {
32+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
2533
$messages[] = sprintf(
2634
'Only numeric types are allowed in -, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

3139
$rightType = $scope->getType($node->right);
32-
if (!OperatorRuleHelper::isValidForArithmeticOperation($rightType)) {
40+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
3341
$messages[] = sprintf(
3442
'Only numeric types are allowed in -, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperatorRuleHelper.php

+26-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace PHPStan\Rules\Operators;
44

5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\RuleLevelHelper;
58
use PHPStan\Type\ErrorType;
69
use PHPStan\Type\FloatType;
710
use PHPStan\Type\IntegerType;
@@ -12,8 +15,17 @@
1215
class OperatorRuleHelper
1316
{
1417

15-
public static function isValidForArithmeticOperation(Type $type): bool
18+
/** @var \PHPStan\Rules\RuleLevelHelper */
19+
private $ruleLevelHelper;
20+
21+
public function __construct(RuleLevelHelper $ruleLevelHelper)
22+
{
23+
$this->ruleLevelHelper = $ruleLevelHelper;
24+
}
25+
26+
public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool
1627
{
28+
$type = $scope->getType($expr);
1729
if ($type instanceof MixedType) {
1830
return true;
1931
}
@@ -24,6 +36,19 @@ public static function isValidForArithmeticOperation(Type $type): bool
2436

2537
$acceptedType = new UnionType([new IntegerType(), new FloatType()]);
2638

39+
$typeToCheck = $this->ruleLevelHelper->findTypeToCheck(
40+
$scope,
41+
$expr,
42+
'',
43+
function (Type $type) use ($acceptedType): bool {
44+
return $acceptedType->isSuperTypeOf($type)->yes();
45+
}
46+
);
47+
$type = $typeToCheck->getType();
48+
if ($type instanceof ErrorType) {
49+
return true;
50+
}
51+
2752
return $acceptedType->isSuperTypeOf($type)->yes();
2853
}
2954

tests/Levels/data/arithmeticOperators-0.json

-17
This file was deleted.

tests/Levels/data/arithmeticOperators-2.json

+10
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@
44
"line": 26,
55
"ignorable": true
66
},
7+
{
8+
"message": "Only numeric types are allowed in +, string given on the left side.",
9+
"line": 27,
10+
"ignorable": true
11+
},
712
{
813
"message": "Binary operation \"+\" between stdClass|string and int results in an error.",
914
"line": 36,
1015
"ignorable": true
16+
},
17+
{
18+
"message": "Only numeric types are allowed in +, string given on the left side.",
19+
"line": 43,
20+
"ignorable": true
1121
}
1222
]

tests/Levels/data/arithmeticOperators-6.json

+5
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,10 @@
33
"message": "Binary operation \"+\" between int|string and int results in an error.",
44
"line": 28,
55
"ignorable": true
6+
},
7+
{
8+
"message": "Only numeric types are allowed in +, int|string given on the left side.",
9+
"line": 29,
10+
"ignorable": true
611
}
712
]

tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class OperandsInArithmeticAdditionRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new OperandsInArithmeticAdditionRule();
13+
return new OperandsInArithmeticAdditionRule(
14+
new OperatorRuleHelper(
15+
new RuleLevelHelper($this->createBroker(), true, false, true)
16+
)
17+
);
1318
}
1419

1520
public function testRule(): void

tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class OperandsInArithmeticDivisionRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new OperandsInArithmeticDivisionRule();
13+
return new OperandsInArithmeticDivisionRule(
14+
new OperatorRuleHelper(
15+
new RuleLevelHelper($this->createBroker(), true, false, true)
16+
)
17+
);
1318
}
1419

1520
public function testRule(): void

tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class OperandsInArithmeticExponentiationRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new OperandsInArithmeticExponentiationRule();
13+
return new OperandsInArithmeticExponentiationRule(
14+
new OperatorRuleHelper(
15+
new RuleLevelHelper($this->createBroker(), true, false, true)
16+
)
17+
);
1318
}
1419

1520
public function testRule(): void

tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class OperandsInArithmeticModuloRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new OperandsInArithmeticModuloRule();
13+
return new OperandsInArithmeticModuloRule(
14+
new OperatorRuleHelper(
15+
new RuleLevelHelper($this->createBroker(), true, false, true)
16+
)
17+
);
1318
}
1419

1520
public function testRule(): void

0 commit comments

Comments
 (0)