diff --git a/conf/config.level2.neon b/conf/config.level2.neon index ae718efb17..ec0c93f33f 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -30,7 +30,6 @@ rules: - PHPStan\Rules\Generics\UsedTraitsRule - PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule - - PHPStan\Rules\Operators\InvalidBinaryOperationRule - PHPStan\Rules\Operators\InvalidUnaryOperationRule - PHPStan\Rules\Operators\InvalidComparisonOperationRule - PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule @@ -138,3 +137,9 @@ services: - class: PHPStan\Rules\Pure\PureMethodRule + - + class: PHPStan\Rules\Operators\InvalidBinaryOperationRule + arguments: + bleedingEdge: %featureToggles.bleedingEdge% + tags: + - phpstan.rules.rule diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 94d2fe4307..3eee9061b6 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -856,6 +856,10 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback): return $this->getNeverType($leftType, $rightType); } + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { + return new ErrorType(); + } + $leftTypes = $leftType->getConstantScalarTypes(); $rightTypes = $rightType->getConstantScalarTypes(); $leftTypesCount = count($leftTypes); diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 4fb2167030..2c42f7be1e 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -26,6 +26,7 @@ class InvalidBinaryOperationRule implements Rule public function __construct( private ExprPrinter $exprPrinter, private RuleLevelHelper $ruleLevelHelper, + private bool $bleedingEdge, ) { } @@ -44,81 +45,83 @@ public function processNode(Node $node, Scope $scope): array return []; } - if ($scope->getType($node) instanceof ErrorType) { - $leftName = '__PHPSTAN__LEFT__'; - $rightName = '__PHPSTAN__RIGHT__'; - $leftVariable = new Node\Expr\Variable($leftName); - $rightVariable = new Node\Expr\Variable($rightName); - if ($node instanceof Node\Expr\AssignOp) { - $identifier = 'assignOp'; - $newNode = clone $node; - $newNode->setAttribute('phpstan_cache_printer', null); - $left = $node->var; - $right = $node->expr; - $newNode->var = $leftVariable; - $newNode->expr = $rightVariable; - } else { - $identifier = 'binaryOp'; - $newNode = clone $node; - $newNode->setAttribute('phpstan_cache_printer', null); - $left = $node->left; - $right = $node->right; - $newNode->left = $leftVariable; - $newNode->right = $rightVariable; - } - - if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) { - $callback = static fn (Type $type): bool => !$type->toString() instanceof ErrorType; - } else { - $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType; - } - - $leftType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $left, - '', - $callback, - )->getType(); - if ($leftType instanceof ErrorType) { - return []; - } - - $rightType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $right, - '', - $callback, - )->getType(); - if ($rightType instanceof ErrorType) { - return []; - } - - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - $scope = $scope - ->assignVariable($leftName, $leftType, $leftType) - ->assignVariable($rightName, $rightType, $rightType); - - if (!$scope->getType($newNode) instanceof ErrorType) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Binary operation "%s" between %s and %s results in an error.', - substr(substr($this->exprPrinter->printExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)), - $scope->getType($left)->describe(VerbosityLevel::value()), - $scope->getType($right)->describe(VerbosityLevel::value()), - )) - ->line($left->getStartLine()) - ->identifier(sprintf('%s.invalid', $identifier)) - ->build(), - ]; + if (!$scope->getType($node) instanceof ErrorType && !$this->bleedingEdge) { + return []; + } + + $leftName = '__PHPSTAN__LEFT__'; + $rightName = '__PHPSTAN__RIGHT__'; + $leftVariable = new Node\Expr\Variable($leftName); + $rightVariable = new Node\Expr\Variable($rightName); + if ($node instanceof Node\Expr\AssignOp) { + $identifier = 'assignOp'; + $newNode = clone $node; + $newNode->setAttribute('phpstan_cache_printer', null); + $left = $node->var; + $right = $node->expr; + $newNode->var = $leftVariable; + $newNode->expr = $rightVariable; + } else { + $identifier = 'binaryOp'; + $newNode = clone $node; + $newNode->setAttribute('phpstan_cache_printer', null); + $left = $node->left; + $right = $node->right; + $newNode->left = $leftVariable; + $newNode->right = $rightVariable; + } + + if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) { + $callback = static fn (Type $type): bool => !$type->toString() instanceof ErrorType; + } elseif ($node instanceof Node\Expr\AssignOp\Plus || $node instanceof Node\Expr\BinaryOp\Plus) { + $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType || $type->isArray()->yes(); + } else { + $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType; + } + + $leftType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $left, + '', + $callback, + )->getType(); + if ($leftType instanceof ErrorType) { + return []; + } + + $rightType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $right, + '', + $callback, + )->getType(); + if ($rightType instanceof ErrorType) { + return []; + } + + if (!$scope instanceof MutatingScope) { + throw new ShouldNotHappenException(); + } + + $scope = $scope + ->assignVariable($leftName, $leftType, $leftType) + ->assignVariable($rightName, $rightType, $rightType); + + if (!$scope->getType($newNode) instanceof ErrorType) { + return []; } - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'Binary operation "%s" between %s and %s results in an error.', + substr(substr($this->exprPrinter->printExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)), + $scope->getType($left)->describe(VerbosityLevel::value()), + $scope->getType($right)->describe(VerbosityLevel::value()), + )) + ->line($left->getStartLine()) + ->identifier(sprintf('%s.invalid', $identifier)) + ->build(), + ]; } } diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index f3076392f6..c5a08ba5ab 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -439,7 +439,15 @@ private function findTypeToCheckImplementation( } if (count($newTypes) > 0) { - return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, [], null); + $newUnion = TypeCombinator::union(...$newTypes); + if ( + !$this->checkBenevolentUnionTypes + && $type instanceof BenevolentUnionType + ) { + $newUnion = TypeUtils::toBenevolentUnion($newUnion); + } + + return new FoundTypeResult($newUnion, $directClassNames, [], null); } } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 35b3489fd8..41c947e937 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -15,11 +15,16 @@ class InvalidBinaryOperationRuleTest extends RuleTestCase { + private bool $checkExplicitMixed = false; + + private bool $checkImplicitMixed = false; + protected function getRule(): Rule { return new InvalidBinaryOperationRule( new ExprPrinter(new Printer()), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), + true, ); } @@ -246,6 +251,22 @@ public function testRule(): void 'Binary operation "+" between int and array{} results in an error.', 259, ], + [ + 'Binary operation "%" between array and 3 results in an error.', + 267, + ], + [ + 'Binary operation "%" between 3 and array results in an error.', + 268, + ], + [ + 'Binary operation "%" between object and 3 results in an error.', + 270, + ], + [ + 'Binary operation "%" between 3 and object results in an error.', + 271, + ], ]); } @@ -283,4 +304,498 @@ public function testBug5309(): void $this->analyse([__DIR__ . '/data/bug-5309.php'], []); } + public function testBinaryMixed(): void + { + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/invalid-binary-mixed.php'], [ + [ + 'Binary operation "." between T and \'a\' results in an error.', + 11, + ], + [ + 'Binary operation ".=" between \'a\' and T results in an error.', + 13, + ], + [ + 'Binary operation "**" between T and 2 results in an error.', + 15, + ], + [ + 'Binary operation "*" between T and 2 results in an error.', + 16, + ], + [ + 'Binary operation "/" between T and 2 results in an error.', + 17, + ], + [ + 'Binary operation "%" between T and 2 results in an error.', + 18, + ], + [ + 'Binary operation "+" between T and 2 results in an error.', + 19, + ], + [ + 'Binary operation "-" between T and 2 results in an error.', + 20, + ], + [ + 'Binary operation "<<" between T and 2 results in an error.', + 21, + ], + [ + 'Binary operation ">>" between T and 2 results in an error.', + 22, + ], + [ + 'Binary operation "&" between T and 2 results in an error.', + 23, + ], + [ + 'Binary operation "|" between T and 2 results in an error.', + 24, + ], + [ + 'Binary operation "+=" between 5 and T results in an error.', + 26, + ], + [ + 'Binary operation "-=" between 5 and T results in an error.', + 29, + ], + [ + 'Binary operation "*=" between 5 and T results in an error.', + 32, + ], + [ + 'Binary operation "**=" between 5 and T results in an error.', + 35, + ], + [ + 'Binary operation "/=" between 5 and T results in an error.', + 38, + ], + [ + 'Binary operation "%=" between 5 and T results in an error.', + 41, + ], + [ + 'Binary operation "&=" between 5 and T results in an error.', + 44, + ], + [ + 'Binary operation "|=" between 5 and T results in an error.', + 47, + ], + [ + 'Binary operation "^=" between 5 and T results in an error.', + 50, + ], + [ + 'Binary operation "<<=" between 5 and T results in an error.', + 53, + ], + [ + 'Binary operation ">>=" between 5 and T results in an error.', + 56, + ], + [ + 'Binary operation "." between mixed and \'a\' results in an error.', + 61, + ], + [ + 'Binary operation ".=" between \'a\' and mixed results in an error.', + 63, + ], + [ + 'Binary operation "**" between mixed and 2 results in an error.', + 65, + ], + [ + 'Binary operation "*" between mixed and 2 results in an error.', + 66, + ], + [ + 'Binary operation "/" between mixed and 2 results in an error.', + 67, + ], + [ + 'Binary operation "%" between mixed and 2 results in an error.', + 68, + ], + [ + 'Binary operation "+" between mixed and 2 results in an error.', + 69, + ], + [ + 'Binary operation "-" between mixed and 2 results in an error.', + 70, + ], + [ + 'Binary operation "<<" between mixed and 2 results in an error.', + 71, + ], + [ + 'Binary operation ">>" between mixed and 2 results in an error.', + 72, + ], + [ + 'Binary operation "&" between mixed and 2 results in an error.', + 73, + ], + [ + 'Binary operation "|" between mixed and 2 results in an error.', + 74, + ], + [ + 'Binary operation "+=" between 5 and mixed results in an error.', + 76, + ], + [ + 'Binary operation "-=" between 5 and mixed results in an error.', + 79, + ], + [ + 'Binary operation "*=" between 5 and mixed results in an error.', + 82, + ], + [ + 'Binary operation "**=" between 5 and mixed results in an error.', + 85, + ], + [ + 'Binary operation "/=" between 5 and mixed results in an error.', + 88, + ], + [ + 'Binary operation "%=" between 5 and mixed results in an error.', + 91, + ], + [ + 'Binary operation "&=" between 5 and mixed results in an error.', + 94, + ], + [ + 'Binary operation "|=" between 5 and mixed results in an error.', + 97, + ], + [ + 'Binary operation "^=" between 5 and mixed results in an error.', + 100, + ], + [ + 'Binary operation "<<=" between 5 and mixed results in an error.', + 103, + ], + [ + 'Binary operation ">>=" between 5 and mixed results in an error.', + 106, + ], + [ + 'Binary operation "." between mixed and \'a\' results in an error.', + 111, + ], + [ + 'Binary operation ".=" between \'a\' and mixed results in an error.', + 113, + ], + [ + 'Binary operation "**" between mixed and 2 results in an error.', + 115, + ], + [ + 'Binary operation "*" between mixed and 2 results in an error.', + 116, + ], + [ + 'Binary operation "/" between mixed and 2 results in an error.', + 117, + ], + [ + 'Binary operation "%" between mixed and 2 results in an error.', + 118, + ], + [ + 'Binary operation "+" between mixed and 2 results in an error.', + 119, + ], + [ + 'Binary operation "-" between mixed and 2 results in an error.', + 120, + ], + [ + 'Binary operation "<<" between mixed and 2 results in an error.', + 121, + ], + [ + 'Binary operation ">>" between mixed and 2 results in an error.', + 122, + ], + [ + 'Binary operation "&" between mixed and 2 results in an error.', + 123, + ], + [ + 'Binary operation "|" between mixed and 2 results in an error.', + 124, + ], + [ + 'Binary operation "+=" between 5 and mixed results in an error.', + 126, + ], + [ + 'Binary operation "-=" between 5 and mixed results in an error.', + 129, + ], + [ + 'Binary operation "*=" between 5 and mixed results in an error.', + 132, + ], + [ + 'Binary operation "**=" between 5 and mixed results in an error.', + 135, + ], + [ + 'Binary operation "/=" between 5 and mixed results in an error.', + 138, + ], + [ + 'Binary operation "%=" between 5 and mixed results in an error.', + 141, + ], + [ + 'Binary operation "&=" between 5 and mixed results in an error.', + 144, + ], + [ + 'Binary operation "|=" between 5 and mixed results in an error.', + 147, + ], + [ + 'Binary operation "^=" between 5 and mixed results in an error.', + 150, + ], + [ + 'Binary operation "<<=" between 5 and mixed results in an error.', + 153, + ], + [ + 'Binary operation ">>=" between 5 and mixed results in an error.', + 156, + ], + ]); + } + + public function testBug7538(): void + { + $this->analyse([__DIR__ . '/data/bug-7538.php'], [ + [ + 'Binary operation "%" between stdClass and stdClass results in an error.', + 7, + ], + ]); + } + + public function testBug10440(): void + { + $this->analyse([__DIR__ . '/data/bug-10440.php'], [ + [ + 'Binary operation "%" between array{} and array{\'\'} results in an error.', + 8, + ], + ]); + } + + public function testBenevolentUnion(): void + { + $this->analyse([__DIR__ . '/data/binary-op-benevolent-union.php'], [ + [ + 'Binary operation "+" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\Foo results in an error.', + 12, + ], + [ + 'Binary operation "+=" between BinaryOpBenevolentUnion\Foo and (array|bool|int|object|resource) results in an error.', + 24, + ], + [ + 'Binary operation "**" between (array|bool|int|object|resource) and array{} results in an error.', + 42, + ], + [ + 'Binary operation "**" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 43, + ], + [ + 'Binary operation "**=" between array{} and (array|bool|int|object|resource) results in an error.', + 52, + ], + [ + 'Binary operation "**=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 55, + ], + [ + 'Binary operation "*" between (array|bool|int|object|resource) and array{} results in an error.', + 73, + ], + [ + 'Binary operation "*" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 74, + ], + [ + 'Binary operation "*=" between array{} and (array|bool|int|object|resource) results in an error.', + 83, + ], + [ + 'Binary operation "*=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 86, + ], + [ + 'Binary operation "/" between (array|bool|int|object|resource) and array{} results in an error.', + 104, + ], + [ + 'Binary operation "/" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 105, + ], + [ + 'Binary operation "/=" between array{} and (array|bool|int|object|resource) results in an error.', + 114, + ], + [ + 'Binary operation "/=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 117, + ], + [ + 'Binary operation "%" between (array|bool|int|object|resource) and array{} results in an error.', + 135, + ], + [ + 'Binary operation "%" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 136, + ], + [ + 'Binary operation "%=" between array{} and (array|bool|int|object|resource) results in an error.', + 145, + ], + [ + 'Binary operation "%=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 148, + ], + [ + 'Binary operation "-" between (array|bool|int|object|resource) and array{} results in an error.', + 166, + ], + [ + 'Binary operation "-" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 167, + ], + [ + 'Binary operation "-=" between array{} and (array|bool|int|object|resource) results in an error.', + 176, + ], + [ + 'Binary operation "-=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 179, + ], + [ + 'Binary operation "." between (array|bool|int|object|resource) and array{} results in an error.', + 197, + ], + [ + 'Binary operation "." between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 198, + ], + [ + 'Binary operation ".=" between array{} and (array|bool|int|object|resource) results in an error.', + 207, + ], + [ + 'Binary operation ".=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 210, + ], + [ + 'Binary operation "<<" between (array|bool|int|object|resource) and array{} results in an error.', + 228, + ], + [ + 'Binary operation "<<" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 229, + ], + [ + 'Binary operation "<<=" between array{} and (array|bool|int|object|resource) results in an error.', + 238, + ], + [ + 'Binary operation "<<=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 241, + ], + [ + 'Binary operation ">>" between (array|bool|int|object|resource) and array{} results in an error.', + 259, + ], + [ + 'Binary operation ">>" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 260, + ], + [ + 'Binary operation ">>=" between array{} and (array|bool|int|object|resource) results in an error.', + 269, + ], + [ + 'Binary operation ">>=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 272, + ], + [ + 'Binary operation "&" between (array|bool|int|object|resource) and array{} results in an error.', + 290, + ], + [ + 'Binary operation "&" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 291, + ], + [ + 'Binary operation "&=" between array{} and (array|bool|int|object|resource) results in an error.', + 300, + ], + [ + 'Binary operation "&=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 303, + ], + [ + 'Binary operation "^" between (array|bool|int|object|resource) and array{} results in an error.', + 321, + ], + [ + 'Binary operation "^" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 322, + ], + [ + 'Binary operation "^=" between array{} and (array|bool|int|object|resource) results in an error.', + 331, + ], + [ + 'Binary operation "^=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 334, + ], + [ + 'Binary operation "|" between (array|bool|int|object|resource) and array{} results in an error.', + 352, + ], + [ + 'Binary operation "|" between (array|bool|int|object|resource) and BinaryOpBenevolentUnion\\Foo results in an error.', + 353, + ], + [ + 'Binary operation "|=" between array{} and (array|bool|int|object|resource) results in an error.', + 362, + ], + [ + 'Binary operation "|=" between BinaryOpBenevolentUnion\\Foo and (array|bool|int|object|resource) results in an error.', + 365, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/binary-op-benevolent-union.php b/tests/PHPStan/Rules/Operators/data/binary-op-benevolent-union.php new file mode 100644 index 0000000000..1163df914a --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/binary-op-benevolent-union.php @@ -0,0 +1,377 @@ +|int|object|bool|resource> $benevolent + */ +function plus($benevolent, Foo $object): void +{ + echo $benevolent + 1; + echo $benevolent + []; + echo $benevolent + $object; + echo $benevolent + '123'; + echo $benevolent + 1.23; + echo $benevolent + $benevolent; + + $a = 1; + $a += $benevolent; + + $a = []; + $a += $benevolent; + + $a = $object; + $a += $benevolent; + + $a = '123'; + $a += $benevolent; + + $a = 1.23; + $a += $benevolent; + + $a = $benevolent; + $a += $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function exponent($benevolent, Foo $object): void +{ + echo $benevolent ** 1; + echo $benevolent ** []; + echo $benevolent ** $object; + echo $benevolent ** '123'; + echo $benevolent ** 1.23; + echo $benevolent ** $benevolent; + + $a = 1; + $a **= $benevolent; + + $a = []; + $a **= $benevolent; + + $a = $object; + $a **= $benevolent; + + $a = '123'; + $a **= $benevolent; + + $a = 1.23; + $a **= $benevolent; + + $a = $benevolent; + $a **= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function mul($benevolent, Foo $object): void +{ + echo $benevolent * 1; + echo $benevolent * []; + echo $benevolent * $object; + echo $benevolent * '123'; + echo $benevolent * 1.23; + echo $benevolent * $benevolent; + + $a = 1; + $a *= $benevolent; + + $a = []; + $a *= $benevolent; + + $a = $object; + $a *= $benevolent; + + $a = '123'; + $a *= $benevolent; + + $a = 1.23; + $a *= $benevolent; + + $a = $benevolent; + $a *= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function div($benevolent, Foo $object): void +{ + echo $benevolent / 1; + echo $benevolent / []; + echo $benevolent / $object; + echo $benevolent / '123'; + echo $benevolent / 1.23; + echo $benevolent / $benevolent; + + $a = 1; + $a /= $benevolent; + + $a = []; + $a /= $benevolent; + + $a = $object; + $a /= $benevolent; + + $a = '123'; + $a /= $benevolent; + + $a = 1.23; + $a /= $benevolent; + + $a = $benevolent; + $a /= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function mod($benevolent, Foo $object): void +{ + echo $benevolent % 1; + echo $benevolent % []; + echo $benevolent % $object; + echo $benevolent % '123'; + echo $benevolent % 1.23; + echo $benevolent % $benevolent; + + $a = 1; + $a %= $benevolent; + + $a = []; + $a %= $benevolent; + + $a = $object; + $a %= $benevolent; + + $a = '123'; + $a %= $benevolent; + + $a = 1.23; + $a %= $benevolent; + + $a = $benevolent; + $a %= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function minus($benevolent, Foo $object): void +{ + echo $benevolent - 1; + echo $benevolent - []; + echo $benevolent - $object; + echo $benevolent - '123'; + echo $benevolent - 1.23; + echo $benevolent - $benevolent; + + $a = 1; + $a -= $benevolent; + + $a = []; + $a -= $benevolent; + + $a = $object; + $a -= $benevolent; + + $a = '123'; + $a -= $benevolent; + + $a = 1.23; + $a -= $benevolent; + + $a = $benevolent; + $a -= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function concat($benevolent, Foo $object): void +{ + echo $benevolent . 1; + echo $benevolent . []; + echo $benevolent . $object; + echo $benevolent . '123'; + echo $benevolent . 1.23; + echo $benevolent . $benevolent; + + $a = 1; + $a .= $benevolent; + + $a = []; + $a .= $benevolent; + + $a = $object; + $a .= $benevolent; + + $a = '123'; + $a .= $benevolent; + + $a = 1.23; + $a .= $benevolent; + + $a = $benevolent; + $a .= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function lshift($benevolent, Foo $object): void +{ + echo $benevolent << 1; + echo $benevolent << []; + echo $benevolent << $object; + echo $benevolent << '123'; + echo $benevolent << 1.23; + echo $benevolent << $benevolent; + + $a = 1; + $a <<= $benevolent; + + $a = []; + $a <<= $benevolent; + + $a = $object; + $a <<= $benevolent; + + $a = '123'; + $a <<= $benevolent; + + $a = 1<<23; + $a <<= $benevolent; + + $a = $benevolent; + $a <<= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function rshift($benevolent, Foo $object): void +{ + echo $benevolent >> 1; + echo $benevolent >> []; + echo $benevolent >> $object; + echo $benevolent >> '123'; + echo $benevolent >> 1.23; + echo $benevolent >> $benevolent; + + $a = 1; + $a >>= $benevolent; + + $a = []; + $a >>= $benevolent; + + $a = $object; + $a >>= $benevolent; + + $a = '123'; + $a >>= $benevolent; + + $a = 1>>23; + $a >>= $benevolent; + + $a = $benevolent; + $a >>= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function bitAnd($benevolent, Foo $object): void +{ + echo $benevolent & 1; + echo $benevolent & []; + echo $benevolent & $object; + echo $benevolent & '123'; + echo $benevolent & 1.23; + echo $benevolent & $benevolent; + + $a = 1; + $a &= $benevolent; + + $a = []; + $a &= $benevolent; + + $a = $object; + $a &= $benevolent; + + $a = '123'; + $a &= $benevolent; + + $a = 1.23; + $a &= $benevolent; + + $a = $benevolent; + $a &= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function bitXor($benevolent, Foo $object): void +{ + echo $benevolent ^ 1; + echo $benevolent ^ []; + echo $benevolent ^ $object; + echo $benevolent ^ '123'; + echo $benevolent ^ 1.23; + echo $benevolent ^ $benevolent; + + $a = 1; + $a ^= $benevolent; + + $a = []; + $a ^= $benevolent; + + $a = $object; + $a ^= $benevolent; + + $a = '123'; + $a ^= $benevolent; + + $a = 1.23; + $a ^= $benevolent; + + $a = $benevolent; + $a ^= $benevolent; +} + +/** + * @param __benevolent|int|object|bool|resource> $benevolent + */ +function bitOr($benevolent, Foo $object): void +{ + echo $benevolent | 1; + echo $benevolent | []; + echo $benevolent | $object; + echo $benevolent | '123'; + echo $benevolent | 1.23; + echo $benevolent | $benevolent; + + $a = 1; + $a |= $benevolent; + + $a = []; + $a |= $benevolent; + + $a = $object; + $a |= $benevolent; + + $a = '123'; + $a |= $benevolent; + + $a = 1.23; + $a |= $benevolent; + + $a = $benevolent; + $a |= $benevolent; +} + +class Foo {} diff --git a/tests/PHPStan/Rules/Operators/data/bug-10440.php b/tests/PHPStan/Rules/Operators/data/bug-10440.php new file mode 100644 index 0000000000..704cb8a695 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-10440.php @@ -0,0 +1,8 @@ + 0; + var_dump($a ** 2); + var_dump($a * 2); + var_dump($a / 2); + var_dump($a % 2); + var_dump($a + 2); + var_dump($a - 2); + var_dump($a << 2); + var_dump($a >> 2); + var_dump($a & 2); + var_dump($a | 2); + $c = 5; + $c += $a; + + $c = 5; + $c -= $a; + + $c = 5; + $c *= $a; + + $c = 5; + $c **= $a; + + $c = 5; + $c /= $a; + + $c = 5; + $c %= $a; + + $c = 5; + $c &= $a; + + $c = 5; + $c |= $a; + + $c = 5; + $c ^= $a; + + $c = 5; + $c <<= $a; + + $c = 5; + $c >>= $a; +} + +function explicitMixed(mixed $a): void +{ + var_dump($a . 'a'); + $b = 'a'; + $b .= $a; + $bool = rand() > 0; + var_dump($a ** 2); + var_dump($a * 2); + var_dump($a / 2); + var_dump($a % 2); + var_dump($a + 2); + var_dump($a - 2); + var_dump($a << 2); + var_dump($a >> 2); + var_dump($a & 2); + var_dump($a | 2); + $c = 5; + $c += $a; + + $c = 5; + $c -= $a; + + $c = 5; + $c *= $a; + + $c = 5; + $c **= $a; + + $c = 5; + $c /= $a; + + $c = 5; + $c %= $a; + + $c = 5; + $c &= $a; + + $c = 5; + $c |= $a; + + $c = 5; + $c ^= $a; + + $c = 5; + $c <<= $a; + + $c = 5; + $c >>= $a; +} + +function implicitMixed($a): void +{ + var_dump($a . 'a'); + $b = 'a'; + $b .= $a; + $bool = rand() > 0; + var_dump($a ** 2); + var_dump($a * 2); + var_dump($a / 2); + var_dump($a % 2); + var_dump($a + 2); + var_dump($a - 2); + var_dump($a << 2); + var_dump($a >> 2); + var_dump($a & 2); + var_dump($a | 2); + $c = 5; + $c += $a; + + $c = 5; + $c -= $a; + + $c = 5; + $c *= $a; + + $c = 5; + $c **= $a; + + $c = 5; + $c /= $a; + + $c = 5; + $c %= $a; + + $c = 5; + $c &= $a; + + $c = 5; + $c |= $a; + + $c = 5; + $c ^= $a; + + $c = 5; + $c <<= $a; + + $c = 5; + $c >>= $a; +} diff --git a/tests/PHPStan/Rules/Operators/data/invalid-binary.php b/tests/PHPStan/Rules/Operators/data/invalid-binary.php index ef5f6e4c58..60d71d4ba1 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-binary.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-binary.php @@ -258,3 +258,15 @@ function benevolentPlus(array $a, int $i): void { function (int $int) { $int + []; }; + +function testMod(array $a, object $o): void { + echo 4 % 3; + echo '4' % 3; + echo 4 % '3'; + + echo $a % 3; + echo 3 % $a; + + echo $o % 3; + echo 3 % $o; +}