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

Allow trait_exists to inform type for ReflectionClass

This commit is contained in:
Matthew Brown 2019-05-31 09:43:46 -04:00
parent 84c0554595
commit 4002504ff0
10 changed files with 173 additions and 4 deletions

View File

@ -1674,6 +1674,14 @@ class AssertionFinder
$if_types[$first_var_name] = [['=class-string']];
}
}
} elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) {
if ($first_var_name) {
if ($class_exists_check_type === 2) {
$if_types[$first_var_name] = [[$prefix . 'trait-string']];
} elseif (!$prefix) {
$if_types[$first_var_name] = [['=trait-string']];
}
}
} elseif (self::hasInterfaceExistsCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'interface-string']];
@ -2319,6 +2327,35 @@ class AssertionFinder
return 0;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return 0|1|2
*/
protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& strtolower($stmt->name->parts[0]) === 'trait_exists'
) {
if (!isset($stmt->args[1])) {
return 2;
}
$second_arg = $stmt->args[1]->value;
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
&& $second_arg->name instanceof PhpParser\Node\Name
&& strtolower($second_arg->name->parts[0]) === 'true'
) {
return 2;
}
return 1;
}
return 0;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*

View File

@ -38,6 +38,7 @@ use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TScalar;
use Psalm\Type\Atomic\TSingleLetter;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTraitString;
use Psalm\Type\Atomic\TTrue;
/**
@ -1092,6 +1093,16 @@ class TypeAnalyzer
);
}
if ($container_type_part instanceof TString && $input_type_part instanceof TTraitString) {
return true;
}
if ($container_type_part instanceof TTraitString && get_class($input_type_part) === TString::class) {
$type_coerced = true;
return false;
}
if (($input_type_part instanceof TClassString
|| $input_type_part instanceof TLiteralClassString)
&& (get_class($container_type_part) === TString::class

View File

@ -1167,7 +1167,7 @@ class ReflectionClass implements Reflector {
public $name;
/**
* @param T|class-string<T> $argument
* @param T|class-string<T>|trait-string $argument
*/
public function __construct($argument) {}

View File

@ -31,6 +31,7 @@ use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TScalar;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTraitString;
use Psalm\Type\Atomic\TTrue;
use Psalm\Internal\Type\TypeCombination;
use Psalm\Type\Union;
@ -902,7 +903,9 @@ class TypeCombination
}
}
if ($has_non_literal_class_string || !$type instanceof TClassString) {
if ($has_non_literal_class_string ||
!$type instanceof TClassString
) {
$combination->value_types[$type_key] = new TString();
} else {
if (isset($shared_classlikes[$type->as])) {
@ -952,6 +955,13 @@ class TypeCombination
} else {
$combination->value_types[$type_key] = new TClassString();
}
} elseif ($combination->value_types['string'] instanceof TTraitString
&& $type instanceof TClassString
) {
$combination->value_types['trait-string'] = $combination->value_types['string'];
$combination->value_types['class-string'] = $type;
unset($combination->value_types['string']);
} elseif (get_class($combination->value_types['string']) !== get_class($type)) {
$combination->value_types[$type_key] = new TString();
}

View File

@ -59,6 +59,7 @@ abstract class Type
'numeric-string' => true,
'class-string' => true,
'callable-string' => true,
'trait-string' => true,
'mysql-escaped-string' => true,
'html-escaped-string' => true,
'boolean' => true,

View File

@ -45,6 +45,7 @@ use Psalm\Type\Atomic\TResource;
use Psalm\Type\Atomic\TScalar;
use Psalm\Type\Atomic\TScalarClassConstant;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTraitString;
use Psalm\Type\Atomic\TTrue;
use Psalm\Type\Atomic\TVoid;
@ -166,6 +167,9 @@ abstract class Atomic
case 'interface-string':
return new TClassString();
case 'trait-string':
return new TTraitString();
case 'callable-string':
return new TCallableString();

View File

@ -0,0 +1,66 @@
<?php
namespace Psalm\Type\Atomic;
class TTraitString extends TString
{
/**
* @return string
*/
public function getKey()
{
return 'trait-string';
}
/**
* @return string
*/
public function __toString()
{
return $this->getKey();
}
public function getId()
{
return $this->getKey();
}
/**
* @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|null
*/
public function toPhpString(
$namespace,
array $aliased_classes,
$this_class,
$php_major_version,
$php_minor_version
) {
return 'string';
}
/**
* @param string|null $namespace
* @param array<string> $aliased_classes
* @param string|null $this_class
* @param bool $use_phpdoc_format
*
* @return string
*/
public function toNamespacedString($namespace, array $aliased_classes, $this_class, $use_phpdoc_format)
{
return 'trait-string';
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
}

View File

@ -2205,9 +2205,13 @@ class Reconciler
|| $scalar_type === 'class-string'
|| $scalar_type === 'interface-string'
|| $scalar_type === 'callable-string'
|| $scalar_type === 'trait-string'
) {
if ($existing_var_type->hasMixed() || $existing_var_type->hasScalar()) {
if ($scalar_type === 'class-string' || $scalar_type === 'interface-string') {
if ($scalar_type === 'class-string'
|| $scalar_type === 'interface-string'
|| $scalar_type === 'trait-string'
) {
return new Type\Union([new Type\Atomic\TLiteralClassString($value)]);
}
@ -2245,7 +2249,10 @@ class Reconciler
);
}
} else {
if ($scalar_type === 'class-string' || $scalar_type === 'interface-string') {
if ($scalar_type === 'class-string'
|| $scalar_type === 'interface-string'
|| $scalar_type === 'trait-string'
) {
$existing_var_type = new Type\Union([new Type\Atomic\TLiteralClassString($value)]);
} else {
$existing_var_type = new Type\Union([new Type\Atomic\TLiteralString($value)]);
@ -2401,6 +2408,7 @@ class Reconciler
} elseif ($scalar_type === 'string'
|| $scalar_type === 'class-string'
|| $scalar_type === 'interface-string'
|| $scalar_type === 'trait-string'
|| $scalar_type === 'callable-string'
) {
if ($existing_var_type->hasString()) {

View File

@ -516,6 +516,7 @@ class Union
}
unset($this->types['class-string']);
unset($this->types['trait-string']);
} elseif ($type_string === 'int' && $this->literal_int_types) {
foreach ($this->literal_int_types as $literal_key => $_) {
unset($this->types[$literal_key]);
@ -646,6 +647,7 @@ class Union
{
return isset($this->types['string'])
|| isset($this->types['class-string'])
|| isset($this->types['trait-string'])
|| isset($this->types['numeric-string'])
|| isset($this->types['array-key'])
|| $this->literal_string_types
@ -727,6 +729,7 @@ class Union
|| isset($this->types['float'])
|| isset($this->types['string'])
|| isset($this->types['class-string'])
|| isset($this->types['trait-string'])
|| isset($this->types['bool'])
|| isset($this->types['false'])
|| isset($this->types['true'])
@ -1529,6 +1532,7 @@ class Union
return isset($this->types['string'])
|| isset($this->types['class-string'])
|| isset($this->types['trait-string'])
|| isset($this->types['numeric-string'])
|| $this->literal_string_types;
}

View File

@ -1680,6 +1680,34 @@ class FunctionCallTest extends TestCase
$xml = new SimpleXMLElement("<?xml version=\"1.0\"?><a><b></b><b></b></a>");
echo count($xml);'
],
'refineWithTraitExists' => [
'<?php
function foo(string $s) : void {
if (trait_exists($s)) {
new ReflectionClass($s);
}
}'
],
'refineWithClassExistsOrTraitExists' => [
'<?php
function foo(string $s) : void {
if (trait_exists($s) || class_exists($s)) {
new ReflectionClass($s);
}
}
function bar(string $s) : void {
if (class_exists($s) || trait_exists($s)) {
new ReflectionClass($s);
}
}
function baz(string $s) : void {
if (class_exists($s) || interface_exists($s) || trait_exists($s)) {
new ReflectionClass($s);
}
}'
],
];
}