public static fromStringInContext ( string $string, |
||
$string | string | A string representing a type |
$context | The context in which the type string was found | |
return | Parse a type from the given string |
/** * @param string $type_string * A '|' delimited string representing a type in the form * 'int|string|null|ClassName'. * * @param Context $context * The context in which the type string was * found * * @return UnionType */ public static function fromStringInContext(string $type_string, Context $context) : UnionType { if (empty($type_string)) { return new UnionType(); } return new UnionType(array_map(function (string $type_name) use($context, $type_string) { assert($type_name !== '', "Type cannot be empty. Type '{$type_name}' given as part of the union type '{$type_string}' in {$context}."); return Type::fromStringInContext($type_name, $context); }, array_filter(array_map(function (string $type_name) { return trim($type_name); }, explode('|', $type_string))))); }
/** * Visit a node with kind `\ast\AST_NAME` * * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return UnionType * The set of types that are possibly produced by the * given node */ public function visitName(Node $node) : UnionType { if ($node->flags & \ast\flags\NAME_NOT_FQ) { if ('parent' === $node->children['name']) { $class = $this->context->getClassInScope($this->code_base); return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType(); } return Type::fromStringInContext($node->children['name'], $this->context)->asUnionType(); } return Type::fromFullyQualifiedString('\\' . $node->children['name'])->asUnionType(); }
/** * Visit a node with kind `\ast\AST_NAME` * * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return UnionType * The set of types that are possibly produced by the * given node */ public function visitName(Node $node) : UnionType { if ($node->flags & \ast\flags\NAME_NOT_FQ) { if ('parent' === $node->children['name']) { $class = $this->context->getClassInScope($this->code_base); if ($class->hasParentClassFQSEN()) { return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType(); } else { if (!$class->isTrait()) { $this->emitIssue(Issue::ParentlessClass, $node->lineno ?? 0, (string) $class->getFQSEN()); } return new UnionType(); } } return Type::fromStringInContext($node->children['name'], $this->context)->asUnionType(); } return Type::fromFullyQualifiedString('\\' . $node->children['name'])->asUnionType(); }
/** * @param string $string * A string representing a type * * @param Context $context * The context in which the type string was * found * * @return Type * Parse a type from the given string */ public static function fromStringInContext(string $string, Context $context) : Type { assert($string !== '', "Type cannot be empty"); // Extract the namespace, type and parameter type name list $tuple = self::typeStringComponents($string); $namespace = $tuple->_0; $type_name = $tuple->_1; $template_parameter_type_name_list = $tuple->_2; // Map the names of the types to actual types in the // template parameter type list $template_parameter_type_list = array_map(function (string $type_name) use($context) { return Type::fromStringInContext($type_name, $context)->asUnionType(); }, $template_parameter_type_name_list); // @var bool // True if this type name if of the form 'C[]' $is_generic_array_type = self::isGenericArrayString($type_name); // If this is a generic array type, get the name of // the type of each element $non_generic_array_type_name = $type_name; if ($is_generic_array_type && false !== ($pos = strrpos($type_name, '[]'))) { $non_generic_array_type_name = substr($type_name, 0, $pos); } // Check to see if the type name is mapped via // a using clause. // // Gotta check this before checking for native types // because there are monsters out there that will // remap the names via things like `use \Foo\String`. $non_generic_partially_qualified_array_type_name = $non_generic_array_type_name; if ($namespace) { $non_generic_partially_qualified_array_type_name = $namespace . '\\' . $non_generic_partially_qualified_array_type_name; } if ($context->hasNamespaceMapFor(\ast\flags\USE_NORMAL, $non_generic_partially_qualified_array_type_name)) { $fqsen = $context->getNamespaceMapFor(\ast\flags\USE_NORMAL, $non_generic_partially_qualified_array_type_name); if ($is_generic_array_type) { return GenericArrayType::fromElementType(Type::make($fqsen->getNamespace(), $fqsen->getName(), $template_parameter_type_list)); } return Type::make($fqsen->getNamespace(), $fqsen->getName(), $template_parameter_type_list); } // If this was a fully qualified type, we're all // set if (!empty($namespace) && 0 === strpos($namespace, '\\')) { return self::make($namespace, $type_name, $template_parameter_type_list); } if ($is_generic_array_type && self::isNativeTypeString($type_name)) { return self::fromInternalTypeName($type_name); } else { // Check to see if its a builtin type switch (strtolower(self::canonicalNameFromName($type_name))) { case 'array': return \Phan\Language\Type\ArrayType::instance(); case 'bool': return \Phan\Language\Type\BoolType::instance(); case 'callable': return \Phan\Language\Type\CallableType::instance(); case 'float': return \Phan\Language\Type\FloatType::instance(); case 'int': return \Phan\Language\Type\IntType::instance(); case 'mixed': return \Phan\Language\Type\MixedType::instance(); case 'null': return \Phan\Language\Type\NullType::instance(); case 'object': return \Phan\Language\Type\ObjectType::instance(); case 'resource': return \Phan\Language\Type\ResourceType::instance(); case 'string': return \Phan\Language\Type\StringType::instance(); case 'void': return \Phan\Language\Type\VoidType::instance(); case 'static': return \Phan\Language\Type\StaticType::instance(); } } // Things like `self[]` or `$this[]` if ($is_generic_array_type && self::isSelfTypeString($non_generic_array_type_name) && $context->isInClassScope()) { // Callers of this method should be checking on their own // to see if this type is a reference to 'parent' and // dealing with it there. We don't want to have this // method be dependent on the code base assert('parent' !== $non_generic_array_type_name, __METHOD__ . " does not know how to handle the type name 'parent'"); return GenericArrayType::fromElementType(static::fromFullyQualifiedString((string) $context->getClassFQSEN())); } // If this is a type referencing the current class // in scope such as 'self' or 'static', return that. if (self::isSelfTypeString($type_name) && $context->isInClassScope()) { // Callers of this method should be checking on their own // to see if this type is a reference to 'parent' and // dealing with it there. We don't want to have this // method be dependent on the code base assert('parent' !== $type_name, __METHOD__ . " does not know how to handle the type name 'parent'"); return static::fromFullyQualifiedString((string) $context->getClassFQSEN()); } // Merge the current namespace with the given relative // namespace if (!empty($context->getNamespace()) && !empty($namespace)) { $namespace = $context->getNamespace() . '\\' . $namespace; } else { if (!empty($context->getNamespace())) { $namespace = $context->getNamespace(); } else { $namespace = '\\' . $namespace; } } // Attach the context's namespace to the type name return self::make($namespace, $type_name, $template_parameter_type_list); }
/** * @param Context $context * The context in which the comment line appears * * @param string $line * An individual line of a comment * * @return Option<Type> * An optional type overriding the extended type of the class */ private static function inheritsFromCommentLine(Context $context, string $line) { $match = []; if (preg_match('/@inherits\\s+(' . Type::type_regex . ')/', $line, $match)) { $type_string = $match[1]; $type = new Some(Type::fromStringInContext($type_string, $context)); return $type; } return new None(); }
/** * @param CodeBase $code_base * The code base within which we're operating * * @param $context $context * The context of the parser at the node for which we'd * like to determine a type * * @param Node|mixed $node * The node for which we'd like to determine its type * * @return UnionType * The UnionType associated with the given node * in the given Context within the given CodeBase * * @throws IssueException * An exception is thrown if we can't find a class for * the given type */ public static function unionTypeFromClassNode(CodeBase $code_base, Context $context, $node) : UnionType { // For simple nodes or very complicated nodes, // recurse if (!$node instanceof \ast\Node || $node->kind != \ast\AST_NAME) { return self::unionTypeFromNode($code_base, $context, $node); } $class_name = $node->children['name']; if ('parent' === $class_name) { if (!$context->isInClassScope()) { throw new IssueException(Issue::fromType(Issue::ContextNotObject)($context->getFile(), $node->lineno ?? 0, [$class_name])); } $class = $context->getClassInScope($code_base); if ($class->isTrait()) { throw new IssueException(Issue::fromType(Issue::TraitParentReference)($context->getFile(), $node->lineno ?? 0, [(string) $context->getClassFQSEN()])); } if (!$class->hasParentType()) { throw new IssueException(Issue::fromType(Issue::ParentlessClass)($context->getFile(), $node->lineno ?? 0, [(string) $context->getClassFQSEN()])); } $parent_class_fqsen = $class->getParentClassFQSEN(); if (!$code_base->hasClassWithFQSEN($parent_class_fqsen)) { throw new IssueException(Issue::fromType(Issue::UndeclaredClass)($context->getFile(), $node->lineno ?? 0, [(string) $parent_class_fqsen])); } else { $parent_class = $code_base->getClassByFQSEN($parent_class_fqsen); return $parent_class->getUnionType(); } } // We're going to convert the class reference to a type $type = null; // Check to see if the name is fully qualified if (!($node->flags & \ast\flags\NAME_NOT_FQ)) { if (0 !== strpos($class_name, '\\')) { $class_name = '\\' . $class_name; } $type = Type::fromFullyQualifiedString($class_name); } else { $type = Type::fromStringInContext($class_name, $context); } return $type->asUnionType(); }
/** * @param string $type_string * A '|' delimited string representing a type in the form * 'int|string|null|ClassName'. * * @param Context $context * The context in which the type string was * found * * @return UnionType */ public static function fromStringInContext(string $type_string, Context $context) : UnionType { if (empty($type_string)) { return new UnionType(); } // If our scope has a generic type identifier defined on it // that matches the type string, return that UnionType. if ($context->getScope()->hasTemplateType($type_string)) { return $context->getScope()->getTemplateType($type_string)->asUnionType(); } return new UnionType(array_map(function (string $type_name) use($context, $type_string) { assert($type_name !== '', "Type cannot be empty."); return Type::fromStringInContext($type_name, $context); }, array_filter(array_map(function (string $type_name) { return trim($type_name); }, explode('|', $type_string))))); }