1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Fix issues reconciling class-string params to actual types

This commit is contained in:
Matt Brown 2018-05-21 12:40:39 -04:00
parent 228337415b
commit aebbd473d9
8 changed files with 103 additions and 20 deletions

View File

@ -180,6 +180,7 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
case Type\Atomic\ObjectLike::class:
case Type\Atomic\TString::class:
case Type\Atomic\TLiteralString::class:
case Type\Atomic\TLiteralClassString::class:
case Type\Atomic\TNumericString::class:
case Type\Atomic\TClassString::class:
case Type\Atomic\TEmptyMixed::class:

View File

@ -118,15 +118,21 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker
foreach ($stmt->class->inferredType->getTypes() as $lhs_type_part) {
// this is always OK
if ($lhs_type_part instanceof Type\Atomic\TClassString) {
if ($lhs_type_part instanceof Type\Atomic\TLiteralClassString
|| $lhs_type_part instanceof Type\Atomic\TClassString
) {
if (!isset($stmt->inferredType)) {
$class_name = $lhs_type_part instanceof Type\Atomic\TClassString
? 'object'
: $lhs_type_part->value;
if ($new_type) {
$new_type = Type::combineUnionTypes(
$new_type,
Type::parseString($lhs_type_part->value)
Type::parseString($class_name)
);
} else {
$new_type = Type::parseString($lhs_type_part->value);
$new_type = Type::parseString($class_name);
}
}

View File

@ -198,6 +198,11 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
continue;
}
// ok for now
if ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) {
continue;
}
if ($lhs_type_part instanceof Type\Atomic\TString) {
if ($config->allow_string_standin_for_class
&& !$lhs_type_part instanceof Type\Atomic\TNumericString

View File

@ -16,6 +16,7 @@ use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TGenericParam;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
@ -630,24 +631,26 @@ class TypeChecker
return false;
}
if ($container_type_part instanceof TClassString && $input_type_part instanceof TClassString) {
if ($container_type_part->value === 'object') {
if (($container_type_part instanceof TClassString || $container_type_part instanceof TLiteralClassString)
&& ($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString)
) {
if ($container_type_part instanceof TClassString) {
return true;
}
if ($input_type_part->value === 'object') {
if ($input_type_part instanceof TClassString) {
$type_coerced = true;
return false;
}
$fake_container_object = new TNamedObject($container_type_part->value);
$fake_input_object = new TNamedObject($container_type_part->value);
$fake_input_object = new TNamedObject($input_type_part->value);
return self::isObjectContainedByObject($codebase, $fake_input_object, $fake_container_object);
}
if ($input_type_part instanceof TClassString
if (($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString)
&& (get_class($container_type_part) === TString::class
|| get_class($container_type_part) === Type\Atomic\GetClassT::class)
) {
@ -664,7 +667,9 @@ class TypeChecker
return false;
}
if ($container_type_part instanceof TClassString && $input_type_part instanceof TString) {
if (($container_type_part instanceof TClassString || $container_type_part instanceof TLiteralClassString)
&& $input_type_part instanceof TString
) {
if (\Psalm\Config::getInstance()->allow_coercion_from_string_to_class_const) {
$type_coerced = true;
}

View File

@ -14,6 +14,7 @@ use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
@ -707,9 +708,13 @@ abstract class Type
*
* @return Type\Union
*/
public static function getClassString($class_type = 'object')
public static function getClassString($class_type = null)
{
$type = new TClassString($class_type);
if (!$class_type) {
return new Union([new TClassString()]);
}
$type = new TLiteralClassString($class_type);
return new Union([$type]);
}

View File

@ -1,21 +1,26 @@
<?php
namespace Psalm\Type\Atomic;
class TClassString extends TLiteralString
class TClassString extends TString
{
/**
* @param string $value string
*/
public function __construct($value = 'object')
{
$this->value = $value;
}
public function __toString()
{
return 'class-string';
}
/**
* @return string
*/
public function getKey()
{
return 'class-string';
}
public function getId()
{
return $this->getKey();
}
/**
* @param string|null $namespace
* @param array<string> $aliased_classes

View File

@ -0,0 +1,45 @@
<?php
namespace Psalm\Type\Atomic;
class TLiteralClassString extends TLiteralString
{
/**
* @param string $value string
*/
public function __construct($value)
{
$this->value = $value;
}
public function __toString()
{
return 'class-string';
}
/**
* @param string|null $namespace
* @param array<string> $aliased_classes
* @param string|null $this_class
* @param int $php_major_version
* @param int $php_minor_version
*
* @return string
*/
public function toPhpString(
$namespace,
array $aliased_classes,
$this_class,
$php_major_version,
$php_minor_version
) {
return 'string';
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
}

View File

@ -656,6 +656,17 @@ class AnnotationTest extends TestCase
'annotations' => [],
'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'],
],
'ifClassStringEquals' => [
'<?php
class A {}
class B {}
/** @param class-string $class */
function foo(string $class) : void {
if ($class === A::class) {}
if ($class === A::class || $class === B::class) {}
}',
],
'allowOptionalParamsToBeEmptyArray' => [
'<?php
/** @param array{b?: int, c?: string} $a */