From 5bd9ba620758420ebf59472e9ebe80ed6b86c9d8 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sun, 20 May 2018 00:27:53 -0400 Subject: [PATCH] Make TClassString inherit from TLiteralString --- .../Statements/Expression/Call/NewChecker.php | 56 ++++++++++--------- .../Statements/Expression/CallChecker.php | 25 +++++---- .../Checker/Statements/ReturnChecker.php | 25 +++++---- src/Psalm/Checker/TypeChecker.php | 17 ++++++ src/Psalm/Type/Atomic/TClassString.php | 26 ++------- src/Psalm/Type/TypeCombination.php | 16 ------ src/Psalm/Type/Union.php | 2 +- tests/AnnotationTest.php | 2 +- 8 files changed, 80 insertions(+), 89 deletions(-) diff --git a/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php b/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php index 11ab92ff9..0bc9ec373 100644 --- a/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php +++ b/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php @@ -114,11 +114,20 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker } if (isset($stmt->class->inferredType)) { + $new_type = null; + foreach ($stmt->class->inferredType->getTypes() as $lhs_type_part) { // this is always OK if ($lhs_type_part instanceof Type\Atomic\TClassString) { if (!isset($stmt->inferredType)) { - $stmt->inferredType = Type::parseString($lhs_type_part->class_type); + if ($new_type) { + $new_type = Type::combineUnionTypes( + $new_type, + Type::parseString($lhs_type_part->value) + ); + } else { + $new_type = Type::parseString($lhs_type_part->value); + } } continue; @@ -128,12 +137,8 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker if ($config->allow_string_standin_for_class && !$lhs_type_part instanceof Type\Atomic\TNumericString ) { - $stmt->inferredType = Type::getObject(); - - continue; - } - - if (IssueBuffer::accepts( + // do nothing + } elseif (IssueBuffer::accepts( new InvalidStringClass( 'String cannot be used as a class', new CodeLocation($statements_checker->getSource(), $stmt) @@ -142,29 +147,15 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker )) { // fall through } - - $stmt->inferredType = Type::getObject(); - - continue; - } - - if ($lhs_type_part instanceof Type\Atomic\TMixed + } elseif ($lhs_type_part instanceof Type\Atomic\TMixed || $lhs_type_part instanceof Type\Atomic\TGenericParam ) { - $stmt->inferredType = Type::getObject(); - - continue; - } - - if ($lhs_type_part instanceof Type\Atomic\TNull + // do nothing + } elseif ($lhs_type_part instanceof Type\Atomic\TNull && $stmt->class->inferredType->ignore_nullable_issues ) { - $stmt->inferredType = Type::getObject(); - - continue; - } - - if (IssueBuffer::accepts( + // do nothing + } elseif (IssueBuffer::accepts( new UndefinedClass( 'Type ' . $lhs_type_part . ' cannot be called as a class', new CodeLocation($statements_checker->getSource(), $stmt), @@ -175,7 +166,18 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker // fall through } - $stmt->inferredType = Type::getObject(); + if ($new_type) { + $new_type = Type::combineUnionTypes( + $new_type, + Type::getObject() + ); + } else { + $new_type = Type::getObject(); + } + } + + if ($new_type) { + $stmt->inferredType = $new_type; } } diff --git a/src/Psalm/Checker/Statements/Expression/CallChecker.php b/src/Psalm/Checker/Statements/Expression/CallChecker.php index 42505afc9..aa9ab8404 100644 --- a/src/Psalm/Checker/Statements/Expression/CallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/CallChecker.php @@ -1600,19 +1600,22 @@ class CallChecker return false; } } elseif ($param_type_part instanceof TArray - && isset($param_type_part->type_params[1]->getTypes()['class-string']) && $input_expr instanceof PhpParser\Node\Expr\Array_ ) { - foreach ($input_expr->items as $item) { - if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { - if (ClassLikeChecker::checkFullyQualifiedClassLikeName( - $statements_checker, - $item->value->value, - $code_location, - $statements_checker->getSuppressedIssues() - ) === false - ) { - return false; + foreach ($param_type_part->type_params[1]->getTypes() as $param_array_type_part) { + if ($param_array_type_part instanceof TClassString) { + foreach ($input_expr->items as $item) { + if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { + if (ClassLikeChecker::checkFullyQualifiedClassLikeName( + $statements_checker, + $item->value->value, + $code_location, + $statements_checker->getSuppressedIssues() + ) === false + ) { + return false; + } + } } } } diff --git a/src/Psalm/Checker/Statements/ReturnChecker.php b/src/Psalm/Checker/Statements/ReturnChecker.php index 65f5eb940..0f1ad0525 100644 --- a/src/Psalm/Checker/Statements/ReturnChecker.php +++ b/src/Psalm/Checker/Statements/ReturnChecker.php @@ -207,19 +207,22 @@ class ReturnChecker return false; } } elseif ($local_type_part instanceof Type\Atomic\TArray - && isset($local_type_part->type_params[1]->getTypes()['class-string']) && $stmt->expr instanceof PhpParser\Node\Expr\Array_ ) { - foreach ($stmt->expr->items as $item) { - if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { - if (ClassLikeChecker::checkFullyQualifiedClassLikeName( - $statements_checker, - $item->value->value, - new CodeLocation($source, $item->value), - $statements_checker->getSuppressedIssues() - ) === false - ) { - return false; + foreach ($local_type_part->type_params[1]->getTypes() as $local_array_type_part) { + if ($local_array_type_part instanceof Type\Atomic\TClassString) { + foreach ($stmt->expr->items as $item) { + if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { + if (ClassLikeChecker::checkFullyQualifiedClassLikeName( + $statements_checker, + $item->value->value, + new CodeLocation($source, $item->value), + $statements_checker->getSuppressedIssues() + ) === false + ) { + return false; + } + } } } } diff --git a/src/Psalm/Checker/TypeChecker.php b/src/Psalm/Checker/TypeChecker.php index 18313e799..33ab067c8 100644 --- a/src/Psalm/Checker/TypeChecker.php +++ b/src/Psalm/Checker/TypeChecker.php @@ -630,6 +630,23 @@ class TypeChecker return false; } + if ($container_type_part instanceof TClassString && $input_type_part instanceof TClassString) { + if ($container_type_part->value === 'object') { + return true; + } + + if ($input_type_part->value === 'object') { + $type_coerced = true; + + return false; + } + + $fake_container_object = new TNamedObject($container_type_part->value); + $fake_input_object = new TNamedObject($container_type_part->value); + + return self::isObjectContainedByObject($codebase, $fake_input_object, $fake_container_object); + } + if ($input_type_part instanceof TClassString && (get_class($container_type_part) === TString::class || get_class($container_type_part) === Type\Atomic\GetClassT::class) diff --git a/src/Psalm/Type/Atomic/TClassString.php b/src/Psalm/Type/Atomic/TClassString.php index 5359d4006..086ddcceb 100644 --- a/src/Psalm/Type/Atomic/TClassString.php +++ b/src/Psalm/Type/Atomic/TClassString.php @@ -1,19 +1,14 @@ class_type = $class_type; + $this->value = $value; } public function __toString() @@ -21,19 +16,6 @@ class TClassString extends TString return 'class-string'; } - /** - * @return string - */ - public function getKey() - { - return 'class-string'; - } - - public function getId() - { - return $this->getKey(); - } - /** * @param string|null $namespace * @param array $aliased_classes diff --git a/src/Psalm/Type/TypeCombination.php b/src/Psalm/Type/TypeCombination.php index bab571e7d..97c25b555 100644 --- a/src/Psalm/Type/TypeCombination.php +++ b/src/Psalm/Type/TypeCombination.php @@ -9,7 +9,6 @@ use Psalm\Type\Atomic\ObjectLike; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TCallable; -use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; @@ -48,9 +47,6 @@ class TypeCombination /** @var bool */ public $objectlike_sealed = true; - /** @var array */ - public $class_string_types = []; - /** @var array|null */ public $strings = []; @@ -195,10 +191,6 @@ class TypeCombination unset($combination->type_params['array']); } - if ($combination->class_string_types) { - $new_types[] = new TClassString(implode('|', $combination->class_string_types)); - } - foreach ($combination->type_params as $generic_type => $generic_type_params) { if ($generic_type === 'array') { if ($combination->objectlike_entries) { @@ -369,14 +361,6 @@ class TypeCombination foreach ($possibly_undefined_entries as $type) { $type->possibly_undefined = true; } - } elseif ($type instanceof TClassString) { - if (!isset($combination->class_string_types['object'])) { - $class_string_types = explode('|', $type->class_type); - - foreach ($class_string_types as $class_string_type) { - $combination->class_string_types[strtolower($class_string_type)] = $class_string_type; - } - } } else { if ($type instanceof TString) { if ($type instanceof TLiteralString) { diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index e77abc53d..970c1205e 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -500,7 +500,7 @@ class Union */ public function hasString() { - return isset($this->types['string']) || isset($this->types['class-string']) || $this->literal_string_types; + return isset($this->types['string']) || $this->literal_string_types; } /** diff --git a/tests/AnnotationTest.php b/tests/AnnotationTest.php index a2647a12b..9007474ae 100644 --- a/tests/AnnotationTest.php +++ b/tests/AnnotationTest.php @@ -581,7 +581,7 @@ class AnnotationTest extends TestCase 'annotations' => [], 'error_levels' => ['TypeCoercion'], ], - 'singleClassConstant' => [ + 'singleClassConstantAsConstant' => [ '