1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Fix #641 - allow is_a to operate on strings as well

This commit is contained in:
Matthew Brown 2018-04-03 23:14:23 -04:00
parent 4552e69ef2
commit 03b3a764e3
5 changed files with 68 additions and 24 deletions

View File

@ -814,6 +814,20 @@ class AssertionFinder
if ($first_var_name) {
$first_arg = $expr->args[1]->value;
$is_a_prefix = '';
if (isset($expr->args[2]->value)) {
$third_arg = $expr->args[2]->value;
if (!$third_arg instanceof PhpParser\Node\Expr\ConstFetch
|| !in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])
) {
return $if_types;
}
$is_a_prefix = strtolower($third_arg->name->parts[0]) === 'true' ? 'isa-' : '';
}
if ($first_arg instanceof PhpParser\Node\Scalar\String_) {
$if_types[$first_var_name] = $prefix . $first_arg->value;
} elseif ($first_arg instanceof PhpParser\Node\Expr\ClassConstFetch
@ -824,11 +838,11 @@ class AssertionFinder
$class_node = $first_arg->class;
if ($class_node->parts === ['static'] || $class_node->parts === ['self']) {
$if_types[$first_var_name] = $prefix . $this_class_name;
$if_types[$first_var_name] = $prefix . $is_a_prefix . $this_class_name;
} elseif ($class_node->parts === ['parent']) {
// do nothing
} else {
$if_types[$first_var_name] = $prefix . ClassLikeChecker::getFQCLNFromNameObject(
$if_types[$first_var_name] = $prefix . $is_a_prefix . ClassLikeChecker::getFQCLNFromNameObject(
$class_node,
$source->getAliases()
);
@ -1125,16 +1139,6 @@ class AssertionFinder
&& strtolower($second_arg->name) === 'class'
)
) {
if (isset($stmt->args[2]->value)) {
$third_arg = $stmt->args[2]->value;
if (!$third_arg instanceof PhpParser\Node\Expr\ConstFetch
|| strtolower($third_arg->name->parts[0]) !== 'false'
) {
return false;
}
}
return true;
}
}

View File

@ -11,7 +11,8 @@ class TClassString extends TString
/**
* @param string $class_type string
*/
public function __construct($class_type = 'object') {
public function __construct($class_type = 'object')
{
$this->class_type = $class_type;
}

View File

@ -731,7 +731,26 @@ class Reconciler
return Type::getMixed();
}
if (substr($new_var_type, 0, 4) === 'isa-') {
if ($existing_var_type->isMixed()) {
return Type::getMixed();
}
$new_var_type = substr($new_var_type, 4);
$existing_has_object = $existing_var_type->hasObjectType();
$existing_has_string = $existing_var_type->hasString();
if ($existing_has_object && !$existing_has_string) {
$new_type = Type::parseString($new_var_type);
} elseif ($existing_has_string && !$existing_has_object) {
$new_type = Type::getClassString($new_var_type);
} else {
$new_type = Type::getMixed();
}
} else {
$new_type = Type::parseString($new_var_type);
}
if ($existing_var_type->isMixed()) {
return $new_type;

View File

@ -103,6 +103,36 @@ class ClassTest extends TestCase
}
}',
],
'classStringInstantiation' => [
'<?php
class Foo {}
class Bar {}
$class = mt_rand(0, 1) === 1 ? Foo::class : Bar::class;
$object = new $class();',
'assertions' => [
'$object' => 'Foo|Bar',
],
],
'instantiateClassAndIsA' => [
'<?php
class Foo {
public function bar() : void{}
}
/**
* @return string|null
*/
function getFooClass() {
return mt_rand(0, 1) === 1 ? Foo::class : null;
}
$foo_class = getFooClass();
if (is_string($foo_class) && is_a($foo_class, Foo::class, true)) {
$foo = new $foo_class();
$foo->bar();
}',
],
];
}

View File

@ -753,16 +753,6 @@ class TypeReconciliationTest extends TestCase
function takesNullableString(?string $s) : void {}',
],
'classStringInstantiation' => [
'<?php
class Foo {}
class Bar {}
$class = mt_rand(0, 1) === 1 ? Foo::class : Bar::class;
$object = new $class();',
'assertions' => [
'$object' => 'Foo|Bar',
],
],
];
}