Skip to content

Commit abab3ef

Browse files
committed
report mixed in binary operator
1 parent 2505e94 commit abab3ef

File tree

7 files changed

+538
-78
lines changed

7 files changed

+538
-78
lines changed

conf/config.level2.neon

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ rules:
3030
- PHPStan\Rules\Generics\UsedTraitsRule
3131
- PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule
3232
- PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule
33-
- PHPStan\Rules\Operators\InvalidBinaryOperationRule
3433
- PHPStan\Rules\Operators\InvalidUnaryOperationRule
3534
- PHPStan\Rules\Operators\InvalidComparisonOperationRule
3635
- PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule
@@ -138,3 +137,9 @@ services:
138137

139138
-
140139
class: PHPStan\Rules\Pure\PureMethodRule
140+
-
141+
class: PHPStan\Rules\Operators\InvalidBinaryOperationRule
142+
arguments:
143+
bleedingEdge: %featureToggles.bleedingEdge%
144+
tags:
145+
- phpstan.rules.rule

src/Collectors/Registry.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,17 @@ public function __construct(array $collectors)
3535
public function getCollectors(string $nodeType): array
3636
{
3737
if (!isset($this->cache[$nodeType])) {
38-
$parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType);
38+
$parents = class_parents($nodeType);
39+
if ($parents === false) {
40+
$parents = [];
41+
}
42+
43+
$implements = class_implements($nodeType);
44+
if ($implements === false) {
45+
$implements = [];
46+
}
47+
48+
$parentNodeTypes = [$nodeType] + $parents + $implements;
3949

4050
$collectors = [];
4151
foreach ($parentNodeTypes as $parentNodeType) {

src/Rules/DirectRegistry.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,17 @@ public function __construct(array $rules)
3535
public function getRules(string $nodeType): array
3636
{
3737
if (!isset($this->cache[$nodeType])) {
38-
$parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType);
38+
$parents = class_parents($nodeType);
39+
if ($parents === false) {
40+
$parents = [];
41+
}
42+
43+
$implements = class_implements($nodeType);
44+
if ($implements === false) {
45+
$implements = [];
46+
}
47+
48+
$parentNodeTypes = [$nodeType] + $parents + $implements;
3949

4050
$rules = [];
4151
foreach ($parentNodeTypes as $parentNodeType) {

src/Rules/LazyRegistry.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,17 @@ public function __construct(private Container $container)
3232
public function getRules(string $nodeType): array
3333
{
3434
if (!isset($this->cache[$nodeType])) {
35-
$parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType);
35+
$parents = class_parents($nodeType);
36+
if ($parents === false) {
37+
$parents = [];
38+
}
39+
40+
$implements = class_implements($nodeType);
41+
if ($implements === false) {
42+
$implements = [];
43+
}
44+
45+
$parentNodeTypes = [$nodeType] + $parents + $implements;
3646

3747
$rules = [];
3848
$rulesFromContainer = $this->getRulesFromContainer();

src/Rules/Operators/InvalidBinaryOperationRule.php

Lines changed: 74 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class InvalidBinaryOperationRule implements Rule
2626
public function __construct(
2727
private ExprPrinter $exprPrinter,
2828
private RuleLevelHelper $ruleLevelHelper,
29+
private bool $bleedingEdge,
2930
)
3031
{
3132
}
@@ -44,81 +45,81 @@ public function processNode(Node $node, Scope $scope): array
4445
return [];
4546
}
4647

47-
if ($scope->getType($node) instanceof ErrorType) {
48-
$leftName = '__PHPSTAN__LEFT__';
49-
$rightName = '__PHPSTAN__RIGHT__';
50-
$leftVariable = new Node\Expr\Variable($leftName);
51-
$rightVariable = new Node\Expr\Variable($rightName);
52-
if ($node instanceof Node\Expr\AssignOp) {
53-
$identifier = 'assignOp';
54-
$newNode = clone $node;
55-
$newNode->setAttribute('phpstan_cache_printer', null);
56-
$left = $node->var;
57-
$right = $node->expr;
58-
$newNode->var = $leftVariable;
59-
$newNode->expr = $rightVariable;
60-
} else {
61-
$identifier = 'binaryOp';
62-
$newNode = clone $node;
63-
$newNode->setAttribute('phpstan_cache_printer', null);
64-
$left = $node->left;
65-
$right = $node->right;
66-
$newNode->left = $leftVariable;
67-
$newNode->right = $rightVariable;
68-
}
69-
70-
if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) {
71-
$callback = static fn (Type $type): bool => !$type->toString() instanceof ErrorType;
72-
} else {
73-
$callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType;
74-
}
75-
76-
$leftType = $this->ruleLevelHelper->findTypeToCheck(
77-
$scope,
78-
$left,
79-
'',
80-
$callback,
81-
)->getType();
82-
if ($leftType instanceof ErrorType) {
83-
return [];
84-
}
85-
86-
$rightType = $this->ruleLevelHelper->findTypeToCheck(
87-
$scope,
88-
$right,
89-
'',
90-
$callback,
91-
)->getType();
92-
if ($rightType instanceof ErrorType) {
93-
return [];
94-
}
95-
96-
if (!$scope instanceof MutatingScope) {
97-
throw new ShouldNotHappenException();
98-
}
99-
100-
$scope = $scope
101-
->assignVariable($leftName, $leftType, $leftType)
102-
->assignVariable($rightName, $rightType, $rightType);
103-
104-
if (!$scope->getType($newNode) instanceof ErrorType) {
105-
return [];
106-
}
107-
108-
return [
109-
RuleErrorBuilder::message(sprintf(
110-
'Binary operation "%s" between %s and %s results in an error.',
111-
substr(substr($this->exprPrinter->printExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)),
112-
$scope->getType($left)->describe(VerbosityLevel::value()),
113-
$scope->getType($right)->describe(VerbosityLevel::value()),
114-
))
115-
->line($left->getStartLine())
116-
->identifier(sprintf('%s.invalid', $identifier))
117-
->build(),
118-
];
48+
if (!$scope->getType($node) instanceof ErrorType && !$this->bleedingEdge) {
49+
return [];
50+
}
51+
52+
$leftName = '__PHPSTAN__LEFT__';
53+
$rightName = '__PHPSTAN__RIGHT__';
54+
$leftVariable = new Node\Expr\Variable($leftName);
55+
$rightVariable = new Node\Expr\Variable($rightName);
56+
if ($node instanceof Node\Expr\AssignOp) {
57+
$identifier = 'assignOp';
58+
$newNode = clone $node;
59+
$newNode->setAttribute('phpstan_cache_printer', null);
60+
$left = $node->var;
61+
$right = $node->expr;
62+
$newNode->var = $leftVariable;
63+
$newNode->expr = $rightVariable;
64+
} else {
65+
$identifier = 'binaryOp';
66+
$newNode = clone $node;
67+
$newNode->setAttribute('phpstan_cache_printer', null);
68+
$left = $node->left;
69+
$right = $node->right;
70+
$newNode->left = $leftVariable;
71+
$newNode->right = $rightVariable;
72+
}
73+
74+
if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) {
75+
$callback = static fn (Type $type): bool => !$type->toString() instanceof ErrorType;
76+
} else {
77+
$callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType;
78+
}
79+
80+
$leftType = $this->ruleLevelHelper->findTypeToCheck(
81+
$scope,
82+
$left,
83+
'',
84+
$callback,
85+
)->getType();
86+
if ($leftType instanceof ErrorType) {
87+
return [];
88+
}
89+
90+
$rightType = $this->ruleLevelHelper->findTypeToCheck(
91+
$scope,
92+
$right,
93+
'',
94+
$callback,
95+
)->getType();
96+
if ($rightType instanceof ErrorType) {
97+
return [];
98+
}
99+
100+
if (!$scope instanceof MutatingScope) {
101+
throw new ShouldNotHappenException();
102+
}
103+
104+
$scope = $scope
105+
->assignVariable($leftName, $leftType, $leftType)
106+
->assignVariable($rightName, $rightType, $rightType);
107+
108+
if (!$scope->getType($newNode) instanceof ErrorType) {
109+
return [];
119110
}
120111

121-
return [];
112+
return [
113+
RuleErrorBuilder::message(sprintf(
114+
'Binary operation "%s" between %s and %s results in an error.',
115+
substr(substr($this->exprPrinter->printExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)),
116+
$scope->getType($left)->describe(VerbosityLevel::value()),
117+
$scope->getType($right)->describe(VerbosityLevel::value()),
118+
))
119+
->line($left->getStartLine())
120+
->identifier(sprintf('%s.invalid', $identifier))
121+
->build(),
122+
];
122123
}
123124

124125
}

0 commit comments

Comments
 (0)