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:
parent
228337415b
commit
aebbd473d9
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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
|
||||
|
45
src/Psalm/Type/Atomic/TLiteralClassString.php
Normal file
45
src/Psalm/Type/Atomic/TLiteralClassString.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user