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);
}