From 7ecf9b27ba113735dbf084d32bdfb62600548daf Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 3 Mar 2022 16:19:05 +0100 Subject: [PATCH 1/4] Retain left and right types in equal expression --- src/Analyser/TypeSpecifier.php | 4 +++ tests/PHPStan/Analyser/TypeSpecifierTest.php | 32 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c55d1b06d4..7b84cc9ea7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -434,6 +434,10 @@ public function specifyTypesInCondition( ) { return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context); } + + $leftTypes = $this->create($expr->left, $leftType, $context, false, $scope); + $rightTypes = $this->create($expr->right, $rightType, $context, false, $scope); + return $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($scope)); } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { return $this->specifyTypesInCondition( $scope, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 424a1ab1e9..9ea86aedaf 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -22,7 +22,9 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -68,6 +70,8 @@ protected function setUp(): void $this->scope = $this->scope->assignVariable('classString', new ClassStringType()); $this->scope = $this->scope->assignVariable('genericClassString', new GenericClassStringType(new ObjectType('Bar'))); $this->scope = $this->scope->assignVariable('object', new ObjectWithoutClassType()); + $this->scope = $this->scope->assignVariable('int', new IntegerType()); + $this->scope = $this->scope->assignVariable('float', new FloatType()); } /** @@ -1120,6 +1124,34 @@ public function dataCondition(): array '$foo' => 'mixed~non-empty-string', ], ], + [ + new Expr\BinaryOp\BooleanAnd( + $this->createFunctionCall('is_numeric', 'int'), + new Expr\BinaryOp\Equal( + new Variable('int'), + new Expr\Cast\Int_(new Variable('int')), + ), + ), + [ + '$int' => 'int', + '(int) $int' => 'int', + ], + [], + ], + [ + new Expr\BinaryOp\BooleanAnd( + $this->createFunctionCall('is_numeric', 'float'), + new Expr\BinaryOp\Equal( + new Variable('float'), + new Expr\Cast\Int_(new Variable('float')), + ), + ), + [ + '$float' => 'float', + '(int) $float' => 'int', + ], + [], + ], ]; } From a633d7279e35a852ffa1d1ccf83dff0f53a12c62 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 3 Mar 2022 16:19:05 +0100 Subject: [PATCH 2/4] Retain left and right types in equal and identical expression --- src/Analyser/TypeSpecifier.php | 8 +++++++- .../Comparison/ImpossibleCheckTypeMethodCallRuleTest.php | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 7b84cc9ea7..c094bbd0c4 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -317,6 +317,9 @@ public function specifyTypesInCondition( if ($types !== null) { return $types; } + + return $this->create($expr->left, $exprLeftType, $context, false, $scope)->normalize($scope) + ->intersectWith($this->create($expr->right, $exprRightType, $context, false, $scope)->normalize($scope)); } } elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) { @@ -437,7 +440,10 @@ public function specifyTypesInCondition( $leftTypes = $this->create($expr->left, $leftType, $context, false, $scope); $rightTypes = $this->create($expr->right, $rightType, $context, false, $scope); - return $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($scope)); + + return $context->true() + ? $leftTypes->unionWith($rightTypes) + : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($scope)); } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { return $this->specifyTypesInCondition( $scope, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 40c3a6cae1..31d32db056 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -72,6 +72,14 @@ public function testRule(): void 'Call to method ImpossibleMethodCall\Foo::isSame() with stdClass and stdClass will always evaluate to true.', 78, ], + [ + 'Call to method ImpossibleMethodCall\Foo::isNotSame() with stdClass and stdClass will always evaluate to false.', + 81, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isSame() with *NEVER* and stdClass will always evaluate to false.', + 84, + ], ]); } From ade789863df44718f14ae4f20d2ce22e0ec1f058 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 3 Mar 2022 17:48:52 +0100 Subject: [PATCH 3/4] Add equal and identical NodeScopeResolverTest cases for stdClass comparisons --- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/equal.php | 22 +++++++++++++++ tests/PHPStan/Analyser/data/identical.php | 28 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/identical.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2620bf7552..0cae949669 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -649,6 +649,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/weird-array_key_exists-issue.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/equal.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/identical.php'); if (PHP_VERSION_ID >= 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5698-php8.php'); diff --git a/tests/PHPStan/Analyser/data/equal.php b/tests/PHPStan/Analyser/data/equal.php index 27dd59743f..0eb99a6632 100644 --- a/tests/PHPStan/Analyser/data/equal.php +++ b/tests/PHPStan/Analyser/data/equal.php @@ -65,4 +65,26 @@ public function doIpsum(array $a): void assertType('array', $a); } + public function stdClass(\stdClass $a, \stdClass $b): void + { + if ($a == $b) { + assertType('stdClass', $a); + assertType('stdClass', $b); + } else { + assertType('stdClass', $a); + assertType('stdClass', $b); + } + + if ($a != $b) { + assertType('stdClass', $a); + assertType('stdClass', $b); + } else { + assertType('stdClass', $a); + assertType('stdClass', $b); + } + + assertType('stdClass', $a); + assertType('stdClass', $b); + } + } diff --git a/tests/PHPStan/Analyser/data/identical.php b/tests/PHPStan/Analyser/data/identical.php new file mode 100644 index 0000000000..0b748beccd --- /dev/null +++ b/tests/PHPStan/Analyser/data/identical.php @@ -0,0 +1,28 @@ + Date: Thu, 3 Mar 2022 17:56:50 +0100 Subject: [PATCH 4/4] Add more test cases --- tests/PHPStan/Analyser/data/equal.php | 12 ++++++++++++ tests/PHPStan/Analyser/data/identical.php | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/PHPStan/Analyser/data/equal.php b/tests/PHPStan/Analyser/data/equal.php index 0eb99a6632..113d2868b2 100644 --- a/tests/PHPStan/Analyser/data/equal.php +++ b/tests/PHPStan/Analyser/data/equal.php @@ -67,6 +67,18 @@ public function doIpsum(array $a): void public function stdClass(\stdClass $a, \stdClass $b): void { + if ($a == $a) { + assertType('stdClass', $a); + } else { + assertType('*NEVER*', $a); + } + + if ($b != $b) { + assertType('*NEVER*', $b); + } else { + assertType('stdClass', $b); + } + if ($a == $b) { assertType('stdClass', $a); assertType('stdClass', $b); diff --git a/tests/PHPStan/Analyser/data/identical.php b/tests/PHPStan/Analyser/data/identical.php index 0b748beccd..26602e2918 100644 --- a/tests/PHPStan/Analyser/data/identical.php +++ b/tests/PHPStan/Analyser/data/identical.php @@ -21,6 +21,22 @@ public function foo(\stdClass $a, \stdClass $b): void assertType('stdClass', $b); } + if ($a === $b) { + assertType('stdClass', $a); + assertType('stdClass', $b); + } else { + assertType('stdClass', $a); + assertType('stdClass', $b); + } + + if ($a !== $b) { + assertType('stdClass', $a); + assertType('stdClass', $b); + } else { + assertType('stdClass', $a); + assertType('stdClass', $b); + } + assertType('stdClass', $a); assertType('stdClass', $b); }