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:
parent
84c0554595
commit
4002504ff0
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
66
src/Psalm/Type/Atomic/TTraitString.php
Normal file
66
src/Psalm/Type/Atomic/TTraitString.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user