Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix comparison with get_class() in traits always evaluate to true #2044

Merged
merged 3 commits into from Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Type/Php/GetClassDynamicReturnTypeExtension.php
Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -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 {
Expand Down
Expand Up @@ -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<Bug3633\HelloWorld> 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<Bug3633\OtherClass> 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<Bug3633\FinalClass> and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
93,
],
[
'Strict comparison using === between class-string<Bug3633\FinalClass> 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,
],
]);
}

}
128 changes: 128 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-3633.php
@@ -0,0 +1,128 @@
<?php declare(strict_types = 1);

namespace Bug3633;

trait Foo {
public function test($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";
}
}
}

class HelloWorld {
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();
}
}

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();
}
}