From 503e82092684fc6089b94ff604746d3edd88cc78 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 8 Jan 2023 21:53:09 +0100 Subject: [PATCH] Fix comparison with get_class() in traits always evaluate to true --- .../GetClassDynamicReturnTypeExtension.php | 10 ++ ...rictComparisonOfDifferentTypesRuleTest.php | 59 ++++++++ .../Rules/Comparison/data/bug-3633.php | 128 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3633.php diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index 863871f75b..cbbc45f049 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -17,6 +17,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StaticType; +use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeWithClassName; @@ -34,7 +35,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { $args = $functionCall->getArgs(); + if (count($args) === 0) { + if ($scope->isInTrait()) { + return new ClassStringType(); + } + if ($scope->isInClass()) { return new ConstantStringType($scope->getClassReflection()->getName(), true); } @@ -44,6 +50,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $argType = $scope->getType($args[0]->value); + if ($scope->isInTrait() && $argType instanceof ThisType) { + return new ClassStringType(); + } + return TypeTraverser::map( $argType, static function (Type $type, callable $traverse): Type { diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4f8b9cbfc2..6683505ed9 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -619,4 +619,63 @@ public function testBug8586(): void $this->analyse([__DIR__ . '/data/bug-8586.php'], []); } + public function testBug3633(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-3633.php'], [ + [ + 'Strict comparison using === between class-string and \'Bug3633\\\OtherClass\' will always evaluate to false.', + 37, + ], + [ + 'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\HelloWorld\' will always evaluate to true.', + 41, + ], + [ + 'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\OtherClass\' will always evaluate to false.', + 44, + ], + [ + 'Strict comparison using === between class-string and \'Bug3633\\\HelloWorld\' will always evaluate to false.', + 64, + ], + [ + 'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.', + 71, + ], + [ + 'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\OtherClass\' will always evaluate to true.', + 74, + ], + [ + 'Strict comparison using === between class-string and \'Bug3633\\\HelloWorld\' will always evaluate to false.', + 93, + ], + [ + 'Strict comparison using === between class-string and \'Bug3633\\\OtherClass\' will always evaluate to false.', + 96, + ], + [ + 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.', + 102, + ], + [ + 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.', + 106, + ], + [ + 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\OtherClass\' will always evaluate to false.', + 109, + ], + [ + 'Strict comparison using !== between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to false.', + 112, + ], + [ + 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.', + 115, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3633.php b/tests/PHPStan/Rules/Comparison/data/bug-3633.php new file mode 100644 index 0000000000..95aedbcf12 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3633.php @@ -0,0 +1,128 @@ +test(); + } +} + +class OtherClass { + use Foo; + + public function bar($obj): void { + if (get_class($this) === HelloWorld::class) { + echo "OK"; + } + if (get_class($this) === OtherClass::class) { + echo "OK"; + } + + if (get_class() === HelloWorld::class) { + echo "OK"; + } + if (get_class() === OtherClass::class) { + echo "OK"; + } + + if (get_class($obj) === HelloWorld::class) { + echo "OK"; + } + if (get_class($obj) === OtherClass::class) { + echo "OK"; + } + + $this->test(); + } +} + +final class FinalClass { + use Foo; + + public function bar($obj): void { + if (get_class($this) === HelloWorld::class) { + echo "OK"; + } + if (get_class($this) === OtherClass::class) { + echo "OK"; + } + if (get_class($this) !== FinalClass::class) { + echo "OK"; + } + if (get_class($this) === FinalClass::class) { + echo "OK"; + } + + if (get_class() === HelloWorld::class) { + echo "OK"; + } + if (get_class() === OtherClass::class) { + echo "OK"; + } + if (get_class() !== FinalClass::class) { + echo "OK"; + } + if (get_class() === FinalClass::class) { + echo "OK"; + } + + if (get_class($obj) === HelloWorld::class) { + echo "OK"; + } + if (get_class($obj) === OtherClass::class) { + echo "OK"; + } + + $this->test(); + } +}