/** * @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 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); }
/** * @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); }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitMethod(Decl $node) : Context { $method = $this->context->getMethodInScope($this->code_base); $return_type = $method->getUnionType(); $has_interface_class = false; if ($method->getFQSEN() instanceof FullyQualifiedMethodName) { try { $class = $method->getDefiningClass($this->code_base); $has_interface_class = $class->isInterface(); } catch (\Exception $exception) { } } if (!$method->isAbstract() && !$has_interface_class && !$return_type->isEmpty() && !$method->getHasReturn() && !$return_type->hasType(VoidType::instance()) && !$return_type->hasType(NullType::instance())) { Log::err(Log::ETYPE, "Method {$method->getFQSEN()} is declared to return {$return_type} but has no return value", $this->context->getFile(), $node->lineno); } return $this->context; }
/** * Visit a node with kind `\ast\AST_FUNC_DECL` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitFuncDecl(Decl $node) : Context { $method = $this->context->getFunctionLikeInScope($this->code_base); $return_type = $method->getUnionType(); if (!$return_type->isEmpty() && !$method->getHasReturn() && !$this->declOnlyThrows($node) && !$return_type->hasType(VoidType::instance()) && !$return_type->hasType(NullType::instance())) { $this->emitIssue(Issue::TypeMissingReturn, $node->lineno ?? 0, (string) $method->getFQSEN(), (string) $return_type); } $parameters_seen = []; foreach ($method->getParameterList() as $i => $parameter) { if (isset($parameters_seen[$parameter->getName()])) { $this->emitIssue(Issue::ParamRedefined, $node->lineno ?? 0, '$' . $parameter->getName()); } else { $parameters_seen[$parameter->getName()] = $i; } } return $this->context; }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitMethod(Decl $node) : Context { $method = $this->context->getMethodInScope($this->code_base); $return_type = $method->getUnionType(); $has_interface_class = false; if ($method->getFQSEN() instanceof FullyQualifiedMethodName) { try { $class = $method->getDefiningClass($this->code_base); $has_interface_class = $class->isInterface(); } catch (\Exception $exception) { } } if (!$method->isAbstract() && !$has_interface_class && !$return_type->isEmpty() && !$method->getHasReturn() && !$return_type->hasType(VoidType::instance()) && !$return_type->hasType(NullType::instance())) { Issue::emit(Issue::TypeMissingReturn, $this->context->getFile(), $node->lineno ?? 0, $method->getFQSEN(), (string) $return_type); } return $this->context; }
/** * Visit a node with kind `\ast\AST_FUNC_DECL` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitFuncDecl(Decl $node) : Context { $method = $this->context->getMethodInScope($this->code_base); $return_type = $method->getUnionType(); if (!$return_type->isEmpty() && !$method->getHasReturn() && !$return_type->hasType(VoidType::instance()) && !$return_type->hasType(NullType::instance())) { $this->emitIssue(Issue::TypeMissingReturn, $node->lineno ?? 0, $method->getFQSEN(), (string) $return_type); } return $this->context; }