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:
parent
b831baee51
commit
2af7ea05da
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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' => [
|
||||
|
Loading…
x
Reference in New Issue
Block a user