Beispiel #1
0
 public function getUnionType() : UnionType
 {
     if (!empty($this->future_union_type)) {
         // null out the future_union_type before
         // we compute it to avoid unbounded
         // recursion
         $future_union_type = $this->future_union_type;
         $this->future_union_type = null;
         // Set a default value for my type in case
         // there's some unbounded recursion
         $this->setUnionType(new UnionType([IntType::instance(), FloatType::instance(), StringType::instance(), BoolType::instance()]));
         $this->setUnionType($future_union_type->get());
     }
     return parent::getUnionType();
 }
Beispiel #2
0
 /**
  * @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 in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = $string;
     // @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 = strpos($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`.
     if ($context->hasNamespaceMapFor(T_CLASS, $non_generic_array_type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $non_generic_array_type_name);
         if ($is_generic_array_type) {
             return GenericArrayType::fromElementType(Type::make($fqsen->getNamespace(), $fqsen->getName()));
         }
         return Type::make($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     if ($is_generic_array_type && self::isNativeTypeString($type_name)) {
         return self::fromInternalTypeName($type_name);
     } else {
         // Check to see if its a builtin type
         switch (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();
         }
     }
     // 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' in {$context}");
         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' in {$context}");
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
 /**
  * Visit a node with kind `\ast\AST_CAST`
  *
  * @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 visitCast(Node $node) : UnionType
 {
     switch ($node->flags) {
         case \ast\flags\TYPE_NULL:
             return NullType::instance()->asUnionType();
         case \ast\flags\TYPE_BOOL:
             return BoolType::instance()->asUnionType();
         case \ast\flags\TYPE_LONG:
             return IntType::instance()->asUnionType();
         case \ast\flags\TYPE_DOUBLE:
             return FloatType::instance()->asUnionType();
         case \ast\flags\TYPE_STRING:
             return StringType::instance()->asUnionType();
         case \ast\flags\TYPE_ARRAY:
             return ArrayType::instance()->asUnionType();
         case \ast\flags\TYPE_OBJECT:
             return ObjectType::instance()->asUnionType();
         default:
             Log::err(Log::EFATAL, 'Unknown type (' . $node->flags . ') in cast');
     }
 }
Beispiel #4
0
 /**
  * @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);
 }
Beispiel #5
0
 /**
  * @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 in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = strtolower($string);
     // 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`.
     if ($context->hasNamespaceMapFor(T_CLASS, $type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $type_name);
         return new Type($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     // Check to see if its a builtin type
     switch (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();
     }
     // 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()) {
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
Beispiel #6
0
 /**
  * @return Parameter
  * A parameter built from a node
  *
  * @see \Phan\Deprecated\Pass1::node_param
  * Formerly `function node_param`
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Parameter
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     // Get the type of the parameter
     $union_type = UnionType::fromNode($context, $code_base, $node->children['type']);
     // Create the skeleton parameter from what we know so far
     $parameter = new Parameter($context, (string) $node->children['name'], $union_type, $node->flags ?? 0);
     // If there is a default value, store it and its type
     if (($default_node = $node->children['default']) !== null) {
         // We can't figure out default values during the
         // parsing phase, unfortunately
         if (!$default_node instanceof Node || $default_node->kind == \ast\AST_CONST || $default_node->kind == \ast\AST_UNARY_OP || $default_node->kind == \ast\AST_ARRAY) {
             // Get the type of the default
             $union_type = UnionType::fromNode($context, $code_base, $default_node);
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         } else {
             try {
                 // Get the type of the default
                 $union_type = UnionType::fromNode($context, $code_base, $default_node, false);
             } catch (IssueException $exception) {
                 // If we're in the parsing phase and we
                 // depend on a constant that isn't yet
                 // defined, give up and set it to
                 // bool|float|int|string to avoid having
                 // to handle a future type.
                 $union_type = new UnionType([BoolType::instance(), FloatType::instance(), IntType::instance(), StringType::instance()]);
             }
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         }
     }
     return $parameter;
 }
Beispiel #7
0
 /**
  * @param UnionType $target
  * A type to check to see if this can cast to it
  *
  * @return bool
  * True if this type is allowed to cast to the given type
  * i.e. int->float is allowed  while float->int is not.
  *
  * @see \Phan\Deprecated\Pass2::type_check
  * Formerly 'function type_check'
  */
 public function canCastToUnionType(UnionType $target) : bool
 {
     // Fast-track most common cases first
     // If either type is unknown, we can't call it
     // a success
     if ($this->isEmpty() || $target->isEmpty()) {
         return true;
     }
     // T === T
     if ($this->isEqualTo($target)) {
         return true;
     }
     if (Config::get()->null_casts_as_any_type) {
         // null <-> null
         if ($this->isType(NullType::instance()) || $target->isType(NullType::instance())) {
             return true;
         }
     }
     // mixed <-> mixed
     if ($target->hasType(MixedType::instance()) || $this->hasType(MixedType::instance())) {
         return true;
     }
     // int -> float
     if ($this->isType(IntType::instance()) && $target->isType(FloatType::instance())) {
         return true;
     }
     // Check conversion on the cross product of all
     // type combinations and see if any can cast to
     // any.
     foreach ($this->getTypeList() as $source_type) {
         if (empty($source_type)) {
             continue;
         }
         foreach ($target->getTypeList() as $target_type) {
             if (empty($target_type)) {
                 continue;
             }
             if ($source_type->canCastToType($target_type)) {
                 return true;
             }
         }
     }
     // Only if no source types can be cast to any target
     // types do we say that we cannot perform the cast
     return false;
 }
Beispiel #8
0
 /**
  * Visit a node with kind `\ast\AST_CAST`
  *
  * @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 visitCast(Node $node) : UnionType
 {
     // TODO: Check if the cast is allowed based on the right side type
     UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
     switch ($node->flags) {
         case \ast\flags\TYPE_NULL:
             return NullType::instance()->asUnionType();
         case \ast\flags\TYPE_BOOL:
             return BoolType::instance()->asUnionType();
         case \ast\flags\TYPE_LONG:
             return IntType::instance()->asUnionType();
         case \ast\flags\TYPE_DOUBLE:
             return FloatType::instance()->asUnionType();
         case \ast\flags\TYPE_STRING:
             return StringType::instance()->asUnionType();
         case \ast\flags\TYPE_ARRAY:
             return ArrayType::instance()->asUnionType();
         case \ast\flags\TYPE_OBJECT:
             return ObjectType::instance()->asUnionType();
         default:
             throw new NodeException($node, 'Unknown type (' . $node->flags . ') in cast');
     }
 }