Skip to content
Permalink
Browse files

Allow trait_exists to inform type for ReflectionClass

  • Loading branch information...
muglug committed May 31, 2019
1 parent 84c0554 commit 4002504ff03ac2894f96b25b852eb774bff712d2
@@ -1674,6 +1674,14 @@ public static function processFunctionCall(
$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 @@ protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt
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\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 @@ public static function isAtomicContainedBy(
);
}
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\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 @@ private static function scrapeTypeProperties(
}
}
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 @@ private static function scrapeTypeProperties(
} 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\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 @@ public static function create(
case 'interface-string':
return new TClassString();
case 'trait-string':
return new TTraitString();
case 'callable-string':
return new TCallableString();
@@ -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 @@ private static function handleLiteralEquality(
|| $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 @@ private static function handleLiteralEquality(
);
}
} 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 @@ private static function handleLiteralNegatedEquality(
} 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 @@ public function removeType($type_string)
}
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 @@ public function hasString()
{
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 @@ public function hasScalarType()
|| 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 @@ public function isString()
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 @@ function bar($s) : void {
$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);
}
}'
],
];
}

0 comments on commit 4002504

Please sign in to comment.
You can’t perform that action at this time.