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

Fix issue reconciling class strings

This commit is contained in:
Matthew Brown 2019-02-03 17:25:22 -05:00
parent f06ed8bf84
commit d665f98fe7
8 changed files with 116 additions and 43 deletions

View File

@ -6,6 +6,7 @@ use Psalm\Storage\FunctionLikeParameter;
use Psalm\Codebase;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\HasClassString;
use Psalm\Type\Atomic\ObjectLike;
use Psalm\Type\Atomic\Scalar;
use Psalm\Type\Atomic\TArray;
@ -707,8 +708,8 @@ class TypeCombination
if (!isset($combination->value_types['string'])) {
if ($combination->strings) {
$has_non_literal_class_string = false;
foreach ($combination->strings as $literal_string) {
if (!$literal_string instanceof TLiteralClassString) {
foreach ($combination->strings as $string_type) {
if (!$string_type instanceof TLiteralClassString) {
$has_non_literal_class_string = true;
}
}

View File

@ -6,8 +6,15 @@ use Psalm\Type\Union;
/**
* Represents a string whose value is a fully-qualified class found by get_class($var)
*/
class GetClassT extends T
class GetClassT extends TString implements HasClassString
{
/**
* Used to hold information as to what this refers to
*
* @var string
*/
public $typeof;
/**
* @var Union
*/
@ -26,4 +33,28 @@ class GetClassT extends T
{
return 'class-string';
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
public function hasSingleNamedObject() : bool
{
return $this->as_type->isSingle() && $this->as_type->hasNamedObject();
}
public function getSingleNamedObject() : TNamedObject
{
$first_value = array_values($this->as_type->getTypes())[0];
if (!$first_value instanceof TNamedObject) {
throw new \UnexpectedValueException('Bad object');
}
return $first_value;
}
}

View File

@ -4,6 +4,28 @@ namespace Psalm\Type\Atomic;
/**
* Represents a string whose value is that of a type found by gettype($var)
*/
class GetTypeT extends T
class GetTypeT extends TString
{
/**
* Used to hold information as to what this refers to
*
* @var string
*/
public $typeof;
/**
* @param string $typeof the variable id
*/
public function __construct($typeof)
{
$this->typeof = $typeof;
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Psalm\Type\Atomic;
interface HasClassString
{
public function hasSingleNamedObject() : bool;
public function getSingleNamedObject() : TNamedObject;
}

View File

@ -1,32 +0,0 @@
<?php
namespace Psalm\Type\Atomic;
/**
* Represents a string that we know holds some type information about another variable,
* specified in the $typeof property
*/
abstract class T extends TString
{
/**
* Used to hold information as to what this refers to
*
* @var string
*/
public $typeof;
/**
* @param string $typeof the variable id
*/
public function __construct($typeof)
{
$this->typeof = $typeof;
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
}

View File

@ -4,7 +4,7 @@ namespace Psalm\Type\Atomic;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
class TClassString extends TString
class TClassString extends TString implements HasClassString
{
/**
* @var string
@ -105,4 +105,18 @@ class TClassString extends TString
{
return false;
}
public function hasSingleNamedObject() : bool
{
return (bool) $this->as_type;
}
public function getSingleNamedObject() : TNamedObject
{
if (!$this->as_type) {
throw new \UnexpectedValueException('Bad object');
}
return $this->as_type;
}
}

View File

@ -1790,8 +1790,6 @@ class Reconciler
} elseif (substr($new_var_type, 0, 9) === 'getclass-') {
$new_var_type = substr($new_var_type, 9);
} elseif (!$is_equality) {
$new_type_part = new TNamedObject($new_var_type);
$codebase = $statements_analyzer->getCodebase();
// if there wasn't a direct hit, go deeper, eliminating subtypes
@ -1801,6 +1799,8 @@ class Reconciler
continue;
}
$new_type_part = new TNamedObject($new_var_type);
if (TypeAnalyzer::isAtomicContainedBy(
$codebase,
$existing_var_type_part,

View File

@ -438,11 +438,24 @@ class Union
$this->id = null;
return true;
} elseif ($type_string === 'string' && $this->literal_string_types) {
foreach ($this->literal_string_types as $literal_key => $_) {
unset($this->types[$literal_key]);
}
if ($type_string === 'string') {
if ($this->literal_string_types) {
foreach ($this->literal_string_types as $literal_key => $_) {
unset($this->types[$literal_key]);
}
$this->literal_string_types = [];
}
$this->literal_string_types = [];
if ($this->typed_class_strings) {
foreach ($this->typed_class_strings as $typed_class_key => $_) {
unset($this->types[$typed_class_key]);
}
$this->typed_class_strings = [];
}
unset($this->types['class-string']);
} elseif ($type_string === 'int' && $this->literal_int_types) {
foreach ($this->literal_int_types as $literal_key => $_) {
unset($this->types[$literal_key]);
@ -506,6 +519,20 @@ class Union
return false;
}
/**
* @return bool
*/
public function hasNamedObject()
{
foreach ($this->types as $type) {
if ($type instanceof TNamedObject) {
return true;
}
}
return false;
}
/**
* @return bool
*/