From 4277cf43aac869f9880b7a50afd2525c986cd616 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 20 Jun 2022 10:05:08 +0200 Subject: [PATCH] Fix resolving mixed + array --- .../InitializerExprTypeResolver.php | 21 +++++++++++++ .../Operators/InvalidBinaryOperationRule.php | 31 ++++++++++++------- .../Analyser/LegacyNodeScopeResolverTest.php | 10 +++++- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/binary.php | 4 +++ tests/PHPStan/Analyser/data/bug-7492.php | 14 +++++++++ .../InvalidBinaryOperationRuleTest.php | 8 +++++ .../Rules/Operators/data/invalid-binary.php | 15 +++++++++ 8 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-7492.php diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8b785274d50..3b2bc54f982 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -924,6 +924,27 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): ]); } + $mixedArray = new ArrayType(new MixedType(), new MixedType()); + if ( + ($leftType->isArray()->yes() && $rightType->isSuperTypeOf($mixedArray)->no()) + || ($rightType->isArray()->yes() && $leftType->isSuperTypeOf($mixedArray)->no()) + ) { + return new ErrorType(); + } + + if ( + ($leftType->isArray()->yes() && $rightType->isSuperTypeOf($mixedArray)->yes()) + || ($rightType->isArray()->yes() && $leftType->isSuperTypeOf($mixedArray)->yes()) + ) { + $arrayType = new ArrayType(new MixedType(), new MixedType()); + + if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { + return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + + return $arrayType; + } + return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType); } diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 5acfb28dc7b..723f2dc7fcb 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -11,6 +11,7 @@ use PHPStan\Rules\RuleLevelHelper; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ErrorType; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -71,22 +72,28 @@ public function processNode(Node $node, Scope $scope): array $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType; } - $leftType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $left, - '', - $callback, - )->getType(); + $leftType = $scope->getType($left); + if (!$leftType instanceof MixedType) { + $leftType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $left, + '', + $callback, + )->getType(); + } if ($leftType instanceof ErrorType) { return []; } - $rightType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $right, - '', - $callback, - )->getType(); + $rightType = $scope->getType($right); + if (!$rightType instanceof MixedType) { + $rightType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $right, + '', + $callback, + )->getType(); + } if ($rightType instanceof ErrorType) { return []; } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 29768a13390..2692f200682 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2696,9 +2696,17 @@ public function dataBinaryOperations(): array '$mixed - $mixed', ], [ - '*ERROR*', + 'array', '$mixed + []', ], + [ + '*ERROR*', + '$mixedNoArray + []', + ], + [ + '*ERROR*', + '$integer + []', + ], [ '124', '1 + "123"', diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 85d802ca25f..06ae6091d2a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -962,6 +962,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5758.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-3931.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5223.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7492.php'); } /** diff --git a/tests/PHPStan/Analyser/data/binary.php b/tests/PHPStan/Analyser/data/binary.php index fc19fca14ef..61676df6384 100644 --- a/tests/PHPStan/Analyser/data/binary.php +++ b/tests/PHPStan/Analyser/data/binary.php @@ -190,6 +190,10 @@ public function doFoo(array $generalArray) $severalSumWithStaticConst2 = 1 + static::INT_CONST + 1; $severalSumWithStaticConst3 = 1 + 1 + static::INT_CONST; + if (!is_array($mixed)) { + $mixedNoArray = $mixed; + } + die; } diff --git a/tests/PHPStan/Analyser/data/bug-7492.php b/tests/PHPStan/Analyser/data/bug-7492.php new file mode 100644 index 00000000000..67fb2909d9b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7492.php @@ -0,0 +1,14 @@ + '', 'login' => '', 'password' => '', 'name' => '']; + assertType('non-empty-array', $x); + } +} diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index c566f32744f..809a47025f3 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -242,6 +242,14 @@ public function testRule(): void 'Binary operation "/" between 10 and literal-string results in an error.', 222, ], + [ + 'Binary operation "+" between int and array{} results in an error.', + 259, + ], + [ + 'Binary operation "+" between mixed and array results in an error.', + 266, + ], ]); } diff --git a/tests/PHPStan/Rules/Operators/data/invalid-binary.php b/tests/PHPStan/Rules/Operators/data/invalid-binary.php index 3069c82ce8d..d11f93316c9 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-binary.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-binary.php @@ -254,3 +254,18 @@ function benevolentPlus(array $a, int $i): void { echo $k + $i; } }; + +function (int $int) { + $int + []; +}; + +function ($mixed, array $arr) { + if (is_array($mixed)) { + return; + } + $mixed + $arr; // mixed~array + array +}; + +function noErrorOnMixedPlusArray($mixed, array $arr) { + $mixed + $arr; +};