Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ef2a246

Browse files
authored
Bleeding edge - check mixed in binary operator
1 parent d64b39f commit ef2a246

File tree

10 files changed

+1172
-76
lines changed

10 files changed

+1172
-76
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/Reflection/InitializerExprTypeResolver.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,10 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback):
856856
return $this->getNeverType($leftType, $rightType);
857857
}
858858

859+
if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
860+
return new ErrorType();
861+
}
862+
859863
$leftTypes = $leftType->getConstantScalarTypes();
860864
$rightTypes = $rightType->getConstantScalarTypes();
861865
$leftTypesCount = count($leftTypes);

‎src/Rules/Operators/InvalidBinaryOperationRule.php‎

Lines changed: 76 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,83 @@ 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+
} elseif ($node instanceof Node\Expr\AssignOp\Plus || $node instanceof Node\Expr\BinaryOp\Plus) {
77+
$callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType || $type->isArray()->yes();
78+
} else {
79+
$callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType;
80+
}
81+
82+
$leftType = $this->ruleLevelHelper->findTypeToCheck(
83+
$scope,
84+
$left,
85+
'',
86+
$callback,
87+
)->getType();
88+
if ($leftType instanceof ErrorType) {
89+
return [];
90+
}
91+
92+
$rightType = $this->ruleLevelHelper->findTypeToCheck(
93+
$scope,
94+
$right,
95+
'',
96+
$callback,
97+
)->getType();
98+
if ($rightType instanceof ErrorType) {
99+
return [];
100+
}
101+
102+
if (!$scope instanceof MutatingScope) {
103+
throw new ShouldNotHappenException();
104+
}
105+
106+
$scope = $scope
107+
->assignVariable($leftName, $leftType, $leftType)
108+
->assignVariable($rightName, $rightType, $rightType);
109+
110+
if (!$scope->getType($newNode) instanceof ErrorType) {
111+
return [];
119112
}
120113

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

124127
}

‎src/Rules/RuleLevelHelper.php‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,15 @@ private function findTypeToCheckImplementation(
439439
}
440440

441441
if (count($newTypes) > 0) {
442-
return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, [], null);
442+
$newUnion = TypeCombinator::union(...$newTypes);
443+
if (
444+
!$this->checkBenevolentUnionTypes
445+
&& $type instanceof BenevolentUnionType
446+
) {
447+
$newUnion = TypeUtils::toBenevolentUnion($newUnion);
448+
}
449+
450+
return new FoundTypeResult($newUnion, $directClassNames, [], null);
443451
}
444452
}
445453

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /