1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #897 - understand static class comparisons in is_a

This commit is contained in:
Matthew Brown 2018-07-17 22:50:30 -04:00
parent b831baee51
commit 2af7ea05da
4 changed files with 174 additions and 41 deletions

View File

@ -608,12 +608,15 @@ class AssertionFinder
throw new \UnexpectedValueException('$getclass_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $getclass_expr */
$var_name = ExpressionChecker::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
$var_name = ExpressionChecker::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
} else {
$var_name = '$this';
}
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
$var_type = $whichclass_expr->value;
@ -1108,12 +1111,15 @@ class AssertionFinder
throw new \UnexpectedValueException('$getclass_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $getclass_expr */
$var_name = ExpressionChecker::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
$var_name = ExpressionChecker::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
} else {
$var_name = '$this';
}
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
$var_type = $whichclass_expr->value;
@ -1280,6 +1286,16 @@ class AssertionFinder
$if_types[$first_var_name] = [[$prefix . 'null']];
}
} elseif (self::hasIsACheck($expr)) {
if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
&& $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
&& strtolower($expr->args[0]->value->name->name) === 'class'
&& $expr->args[0]->value->class instanceof PhpParser\Node\Name
&& count($expr->args[0]->value->class->parts) === 1
&& strtolower($expr->args[0]->value->class->parts[0]) === 'static'
) {
$first_var_name = '$this';
}
if ($first_var_name) {
$second_arg = $expr->args[1]->value;
@ -1657,31 +1673,43 @@ class AssertionFinder
*/
protected static function hasGetClassCheck(PhpParser\Node\Expr\BinaryOp $conditional)
{
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall &&
$conditional->right->name instanceof PhpParser\Node\Name &&
strtolower($conditional->right->name->parts[0]) === 'get_class' &&
(
$conditional->left instanceof PhpParser\Node\Scalar\String_
|| ($conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->left->class instanceof PhpParser\Node\Name
&& $conditional->left->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->left->name->name) === 'class')
)
) {
$right_get_class = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->right->name instanceof PhpParser\Node\Name
&& strtolower($conditional->right->name->parts[0]) === 'get_class';
$right_static_class = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->right->class instanceof PhpParser\Node\Name
&& $conditional->right->class->parts === ['static']
&& $conditional->right->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->right->name->name) === 'class';
$left_class_string = $conditional->left instanceof PhpParser\Node\Scalar\String_
|| ($conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->left->class instanceof PhpParser\Node\Name
&& $conditional->left->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->left->name->name) === 'class');
if (($right_get_class || $right_static_class) && $left_class_string) {
return self::ASSIGNMENT_TO_RIGHT;
}
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall &&
$conditional->left->name instanceof PhpParser\Node\Name &&
strtolower($conditional->left->name->parts[0]) === 'get_class' &&
(
$conditional->right instanceof PhpParser\Node\Scalar\String_
|| ($conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->right->class instanceof PhpParser\Node\Name
&& $conditional->right->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->right->name->name) === 'class')
)
) {
$left_get_class = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->left->name instanceof PhpParser\Node\Name
&& strtolower($conditional->left->name->parts[0]) === 'get_class';
$left_static_class = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->left->class instanceof PhpParser\Node\Name
&& $conditional->left->class->parts === ['static']
&& $conditional->left->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->left->name->name) === 'class';
$right_class_string = $conditional->right instanceof PhpParser\Node\Scalar\String_
|| ($conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
&& $conditional->right->class instanceof PhpParser\Node\Name
&& $conditional->right->name instanceof PhpParser\Node\Identifier
&& strtolower($conditional->right->name->name) === 'class');
if (($left_get_class || $left_static_class) && $right_class_string) {
return self::ASSIGNMENT_TO_LEFT;
}

View File

@ -131,12 +131,19 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
$context->vars_in_scope['$this'] = Type::parseString($old_self);
}
}
} elseif ($context->self) {
if ($stmt->class->parts[0] === 'static' && isset($context->vars_in_scope['$this'])) {
$fq_class_name = (string) $context->vars_in_scope['$this'];
$lhs_type = clone $context->vars_in_scope['$this'];
} else {
$fq_class_name = $context->self;
}
} else {
$namespace = $statements_checker->getNamespace()
? $statements_checker->getNamespace() . '\\'
: '';
$fq_class_name = $context->self ?: $namespace . $statements_checker->getClassName();
$fq_class_name = $namespace . $statements_checker->getClassName();
}
if ($context->isPhantomClass($fq_class_name)) {
@ -178,7 +185,7 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
}
}
if ($fq_class_name) {
if ($fq_class_name && !$lhs_type) {
$lhs_type = new Type\Union([new TNamedObject($fq_class_name)]);
}
} else {

View File

@ -220,6 +220,62 @@ class ClassTest extends TestCase
}
}',
],
'staticClassComparison' => [
'<?php
class C {
public function foo1(): string {
if (static::class === D::class) {
return $this->baz();
}
return "";
}
public static function foo2(): string {
if (static::class === D::class) {
return static::bat();
}
return "";
}
}
class D extends C {
public function baz(): string {
return "baz";
}
public static function bat(): string {
return "baz";
}
}',
],
'isAStaticClass' => [
'<?php
class C {
public function foo1(): string {
if (is_a(static::class, D::class, true)) {
return $this->baz();
}
return "";
}
public static function foo2(): string {
if (is_a(static::class, D::class, true)) {
return static::bat();
}
return "";
}
}
class D extends C {
public function baz(): string {
return "baz";
}
public static function bat(): string {
return "baz";
}
}',
],
'typedMagicCall' => [
'<?php
class B {

View File

@ -281,17 +281,59 @@ class TraitTest extends TestCase
'getClassTraitUser' => [
'<?php
trait T {
public function f(): void {
if (get_class($this) === "B") { }
}
public function f(): void {
if (get_class($this) === "B") {
$this->foo();
}
}
}
class A {
use T;
use T;
}
class B {
use T;
use T;
public function foo() : void {}
}',
],
'staticClassTraitUser' => [
'<?php
trait T {
public function f(): void {
if (static::class === "B") {
$this->foo();
}
}
}
class A {
use T;
}
class B {
use T;
public function foo() : void {}
}',
],
'isAClassTraitUser' => [
'<?php
trait T {
public function f(): void {
if (is_a(static::class, "B")) { }
}
}
class A {
use T;
}
class B {
use T;
public function foo() : void {}
}',
],
'useTraitInClassWithAbstractMethod' => [