/** * @param string $function_name * The name of the function we'd like to look up * * @param bool $is_function_declaration * This must be set to true if we're getting a function * that is being declared and false if we're getting a * function being called. * * @return FunctionInterface * A method with the given name in the given context * * @throws IssueException * An exception is thrown if we can't find the given * function */ public function getFunction(string $function_name, bool $is_function_declaration = false) : FunctionInterface { if ($is_function_declaration) { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); // If it doesn't exist in the local namespace, try it // in the global namespace if (!$this->code_base->hasFunctionWithFQSEN($function_fqsen)) { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context); } } // Make sure the method we're calling actually exists if (!$this->code_base->hasFunctionWithFQSEN($function_fqsen)) { throw new IssueException(Issue::fromType(Issue::UndeclaredFunction)($this->context->getFile(), $this->node->lineno ?? 0, ["{$function_fqsen}()"])); } return $this->code_base->getFunctionByFQSEN($function_fqsen); }
/** * @param string $function_name * The name of the function we'd like to look up * * @param bool $is_function_declaration * This must be set to true if we're getting a function * that is being declared and false if we're getting a * function being called. * * @return Method * A method with the given name in the given context * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * function */ public function getFunction(string $function_name, bool $is_function_declaration = false) : Method { if ($is_function_declaration) { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); // If it doesn't exist in the local namespace, try it // in the global namespace if (!$this->code_base->hasMethod($function_fqsen)) { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context); } } // Make sure the method we're calling actually exists if (!$this->code_base->hasMethod($function_fqsen)) { throw new CodeBaseException($function_fqsen, "call to undefined function {$function_fqsen}()"); } $method = $this->code_base->getMethod($function_fqsen); return $method; }
/** * @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 Context $context * The context in which the FQSEN string was found * * @param $fqsen_string * An FQSEN string like '\Namespace\Class' */ public static function fromStringInContext(string $fqsen_string, Context $context) : FullyQualifiedGlobalStructuralElement { // Check to see if we're fully qualified if (0 === strpos($fqsen_string, '\\')) { return static::fromFullyQualifiedString($fqsen_string); } // Split off the alternate ID $parts = explode(',', $fqsen_string); $fqsen_string = $parts[0]; $alternate_id = (int) ($parts[1] ?? 0); assert(is_int($alternate_id), "Alternate must be an integer in {$fqsen_string}"); $parts = explode('\\', $fqsen_string); $name = array_pop($parts); assert(!empty($name), "The name cannot be empty in {$fqsen_string}"); // Check for a name map if ($context->hasNamespaceMapFor(static::getNamespaceMapType(), $name)) { return $context->getNamespaceMapFor(static::getNamespaceMapType(), $name); } $namespace = implode('\\', array_filter($parts)); // n.b.: Functions must override this method because // they don't prefix the namespace for naked // calls if (empty($namespace)) { $namespace = $context->getNamespace(); } return static::make($namespace, $name, $alternate_id); }
/** * @param string $function_name * The name of the function we'd like to look up * * @param Context $context * The context in which we found the reference to the * given function name * * @param CodeBase $code_base * The global code base holding all state * * @param bool $is_function_declaration * This must be set to true if we're getting a function * that is being declared and false if we're getting a * function being called. * * @return Method * A method with the given name in the given context * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * function */ public static function functionFromNameInContext(string $function_name, Context $context, CodeBase $code_base, bool $is_function_declaration = false) : Method { if ($is_function_declaration) { $function_fqsen = FullyQualifiedFunctionName::make($context->getNamespace(), $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $context); } // Make sure the method we're calling actually exists if (!$code_base->hasMethod($function_fqsen)) { throw new CodeBaseException("call to undefined function {$function_fqsen}()"); } $method = $code_base->getMethod($function_fqsen); return $method; }