public static fromStringInContext ( string $type_string, |
||
$type_string | string | A '|' delimited string representing a type in the form 'int|string|null|ClassName'. |
$context | The context in which the type string was found | |
return |
/** * @return Func[] * One or more (alternate) methods begotten from * reflection info and internal method data */ public static function functionListFromSignature(CodeBase $code_base, FullyQualifiedFunctionName $fqsen, array $signature) : array { $context = new Context(); $return_type = UnionType::fromStringInContext(array_shift($signature), $context); $func = new Func($context, $fqsen->getName(), $return_type, 0, $fqsen); return self::functionListFromFunction($func, $code_base); }
/** * Visit a node with kind `\ast\AST_CLASS` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitClass(Node $node) : Context { if ($node->flags & \ast\flags\CLASS_ANONYMOUS) { $class_name = AST::unqualifiedNameForAnonymousClassNode($node, $this->context); } else { $class_name = $node->name; } // This happens now and then and I have no idea // why. if (empty($class_name)) { return $this->context; } assert(!empty($class_name), "Class must have name in {$this->context}"); $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context); // Hunt for an available alternate ID if necessary $alternate_id = 0; while ($this->code_base->hasClassWithFQSEN($class_fqsen)) { $class_fqsen = $class_fqsen->withAlternateId(++$alternate_id); } // Build the class from what we know so far $clazz = new Clazz($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? -1), $class_name, UnionType::fromStringInContext($class_name, $this->context), $node->flags ?? 0); // Override the FQSEN with the found alternate ID $clazz->setFQSEN($class_fqsen); // Add the class to the code base as a globally // accessible object $this->code_base->addClass($clazz); // Look to see if we have a parent class if (!empty($node->children['extends'])) { $parent_class_name = $node->children['extends']->children['name']; // Check to see if the name isn't fully qualified if ($node->children['extends']->flags & \ast\flags\NAME_NOT_FQ) { if ($this->context->hasNamespaceMapFor(T_CLASS, $parent_class_name)) { // Get a fully-qualified name $parent_class_name = (string) $this->context->getNamespaceMapFor(T_CLASS, $parent_class_name); } else { $parent_class_name = $this->context->getNamespace() . '\\' . $parent_class_name; } } // The name is fully qualified. Make sure it looks // like it is if (0 !== strpos($parent_class_name, '\\')) { $parent_class_name = '\\' . $parent_class_name; } $parent_fqsen = FullyQualifiedClassName::fromStringInContext($parent_class_name, $this->context); // Set the parent for the class $clazz->setParentClassFQSEN($parent_fqsen); } // Add any implemeneted interfaces if (!empty($node->children['implements'])) { $interface_list = AST::qualifiedNameList($this->context, $node->children['implements']); foreach ($interface_list as $name) { $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString($name)); } } // Update the context to signal that we're now // within a class context. $context = $clazz->getContext()->withClassFQSEN($class_fqsen); return $context; }
/** * @param CodeBase $code_base * A reference to the entire code base in which this * context exists * * @param ReflectionClass $class * A reflection class representing a builtin class. * * @return Clazz * A Class structural element representing the given named * builtin. */ public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz { // Build a set of flags based on the constitution // of the built-in class $flags = 0; if ($class->isFinal()) { $flags = \ast\flags\CLASS_FINAL; } else { if ($class->isInterface()) { $flags = \ast\flags\CLASS_INTERFACE; } else { if ($class->isTrait()) { $flags = \ast\flags\CLASS_TRAIT; } } } if ($class->isAbstract()) { $flags |= \ast\flags\CLASS_ABSTRACT; } $context = new Context(); // Build a base class element $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags); // If this class has a parent class, add it to the // class info if ($parent_class = $class->getParentClass()) { $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName()); $clazz->setParentClassFQSEN($parent_class_fqsen); } foreach ($class->getDefaultProperties() as $name => $value) { // TODO: whats going on here? $reflection_property = new \ReflectionProperty($class->getName(), $name); $property = new Property($context->withClassFQSEN($clazz->getFQSEN()), $name, Type::fromObject($value)->asUnionType(), 0); $clazz->addProperty($code_base, $property); } foreach ($class->getInterfaceNames() as $name) { $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name)); } foreach ($class->getTraitNames() as $name) { $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name)); } foreach ($class->getConstants() as $name => $value) { $clazz->addConstant($code_base, new Constant($context, $name, Type::fromObject($value)->asUnionType(), 0)); } foreach ($class->getMethods() as $reflection_method) { $method_list = Method::methodListFromReflectionClassAndMethod($context->withClassFQSEN($clazz->getFQSEN()), $code_base, $class, $reflection_method); foreach ($method_list as $method) { $clazz->addMethod($code_base, $method); } } return $clazz; }
/** * @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 * * @return UnionType * The union type for a node of type \ast\AST_CLASS */ 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']; // 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; } return UnionType::fromFullyQualifiedString($class_name); } if ('parent' === $class_name) { $class = $context->getClassInScope($code_base); $parent_class_fqsen = $class->getParentClassFQSEN(); $parent_class = $code_base->getClassByFQSEN($parent_class_fqsen); return $parent_class->getUnionType(); } return UnionType::fromStringInContext($class_name, $context); }
/** * @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']; // 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; } return UnionType::fromFullyQualifiedString($class_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->hasParentClassFQSEN()) { 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(); } } return UnionType::fromStringInContext($class_name, $context); }
/** * A list of types for parameters associated with the * given builtin function with the given name * * @param FullyQualifiedMethodName|FullyQualifiedFunctionName $function_fqsen * * @see internal_varargs_check * Formerly `function internal_varargs_check` */ public static function internalFunctionSignatureMapForFQSEN($function_fqsen) : array { $context = new Context(); $map = self::internalFunctionSignatureMap(); if ($function_fqsen instanceof FullyQualifiedMethodName) { $class_fqsen = $function_fqsen->getFullyQualifiedClassName(); $class_name = $class_fqsen->getName(); $function_name = $class_name . '::' . $function_fqsen->getName(); } else { $function_name = $function_fqsen->getName(); } $function_name_original = $function_name; $alternate_id = 0; $configurations = []; while (isset($map[$function_name])) { // Get some static data about the function $type_name_struct = $map[$function_name]; if (empty($type_name_struct)) { continue; } // Figure out the return type $return_type_name = array_shift($type_name_struct); $return_type = $return_type_name ? UnionType::fromStringInContext($return_type_name, $context) : null; $name_type_name_map = $type_name_struct; $property_name_type_map = []; foreach ($name_type_name_map as $name => $type_name) { $property_name_type_map[$name] = empty($type_name) ? new UnionType() : UnionType::fromStringInContext($type_name, $context); } $configurations[] = ['return_type' => $return_type, 'property_name_type_map' => $property_name_type_map]; $function_name = $function_name_original . '\'' . ++$alternate_id; } return $configurations; }
/** * @param Context $context * The context in which the comment line appears * * @param string $line * An individual line of a comment * * @return CommentParameter * A CommentParameter associated with a line that has a var * or param reference. */ private static function parameterFromCommentLine(Context $context, string $line) { $match = []; if (preg_match('/@(param|var)\\s+(' . UnionType::union_type_regex . ')(\\s+(\\$\\S+))?/', $line, $match)) { $type = $match[2]; $variable_name = empty($match[23]) ? '' : trim($match[23], '$'); // If the type looks like a variable name, make it an // empty type so that other stuff can match it. We can't // just skip it or we'd mess up the parameter order. $union_type = null; if (0 !== strpos($type, '$')) { $union_type = UnionType::fromStringInContext($type, $context); } else { $union_type = new UnionType(); } return new CommentParameter($variable_name, $union_type); } return new CommentParameter('', new UnionType()); }
/** * @return UnionType|null * Returns UnionType (Possible with empty set) if and only if isHardcodedGlobalVariableWithName is true. * Returns null otherwise. */ public static function getUnionTypeOfHardcodedGlobalVariableWithName(string $name, Context $context) { if (array_key_exists($name, self::_BUILTIN_SUPERGLOBAL_TYPES)) { // More efficient than using context. return UnionType::fromFullyQualifiedString(self::_BUILTIN_SUPERGLOBAL_TYPES[$name]); } if (array_key_exists($name, Config::get()->globals_type_map) || in_array($name, Config::get()->runkit_superglobals)) { $type_string = Config::get()->globals_type_map[$name] ?? ''; return UnionType::fromStringInContext($type_string, $context); } return null; }
/** * @return bool * False if the class name doesn't point to a known class */ private function classExistsOrIsNative(Node $node) : bool { if ($this->classExists()) { return true; } $type = UnionType::fromStringInContext($this->class_name, $this->context); if ($type->isNativeType()) { return true; } Log::err(Log::EUNDEF, "reference to undeclared class {$this->class_fqsen}", $this->context->getFile(), $node->lineno); return false; }
/** * @return Comment * A comment built by parsing the given doc block * string. */ public static function fromStringInContext(string $comment, Context $context) : Comment { $is_deprecated = false; $variable_list = []; $parameter_list = []; $return = null; // A legal type identifier $simple_type_regex = '[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*'; // A legal type identifier optionally with a [] // indicating that its a generic typed array $generic_array_type_regex = "{$simple_type_regex}(\\[\\])?"; // A list of one or more types delimited by the '|' // character $union_type_regex = "{$generic_array_type_regex}(\\|{$generic_array_type_regex})*"; $lines = explode("\n", $comment); foreach ($lines as $line) { if (($pos = strpos($line, '@param')) !== false) { $match = []; if (preg_match("/@param\\s+({$union_type_regex})(\\s+(\\\$\\S+))?/", $line, $match)) { $type = null; if (stripos($match[1], '\\') === 0 && strpos($match[1], '\\', 1) === false) { $type = trim($match[1], '\\'); } else { $type = $match[1]; } $variable_name = empty($match[6]) ? '' : trim($match[6], '$'); // If the type looks like a variable name, // make it an empty type so that other stuff // can match it. We can't just skip it or // we'd mess up the parameter order. $union_type = null; if (0 !== strpos($type, '$')) { $union_type = UnionType::fromStringInContext($type, $context); } else { $union_type = new UnionType(); } $comment_parameter = new CommentParameter($variable_name, $union_type, $line); } else { $comment_parameter = new CommentParameter('', new UnionType(), $line); } $parameter_list[] = $comment_parameter; } if (($pos = stripos($line, '@var')) !== false) { $match = []; if (preg_match("/@var\\s+({$union_type_regex})\\s*(?:(\\S+))*/", $line, $match)) { $type = null; if (strpos($match[1], '\\') === 0 && strpos($match[1], '\\', 1) === false) { $type = trim($match[1], '\\'); } else { $type = $match[1]; } $var_name = empty($match[2]) ? '' : trim($match[2], '$'); $var_type = UnionType::fromStringInContext($type, $context); $comment_parameter = new CommentParameter($var_name, $var_type, $line); } else { $comment_parameter = new CommentParameter('', new UnionType(), $line); } $variable_list[] = $comment_parameter; } if (($pos = stripos($line, '@return')) !== false) { $match = []; if (preg_match("/@return\\s+({$union_type_regex}+)/", $line, $match)) { if (strpos($match[1], '\\') === 0 && strpos($match[1], '\\', 1) === false) { $return = trim($match[1], '\\'); } else { $return = $match[1]; } } } if (($pos = stripos($line, '@deprecated')) !== false) { if (preg_match('/@deprecated\\b/', $line, $match)) { $is_deprecated = true; } } } $return_type = UnionType::fromStringInContext($return ?: '', $context); return new Comment($is_deprecated, $variable_list, $parameter_list, $return_type); }
/** * Get a fully qualified name form a node * * @return string * * @see \Phan\Deprecated\Util::qualified_name * From `function qualified_name` */ public static function qualifiedName(Context $context, $node) : string { if (!$node instanceof \ast\Node || $node->kind != \ast\AST_NAME) { return (string) self::varUnionType($context, $node); } $type_name = $node->children['name']; $type = null; // Check to see if the name is fully qualified if (!($node->flags & \ast\flags\NAME_NOT_FQ)) { if (0 !== strpos($type_name, '\\')) { $type_name = '\\' . $type_name; } return (string) UnionType::fromFullyQualifiedString($type_name); } $type = UnionType::fromStringInContext($type_name, $context); return (string) $type; }
/** * @return Method[] * One or more (alternate) methods begotten from * reflection info and internal method data */ public static function methodListFromSignature(CodeBase $code_base, FullyQualifiedFunctionName $fqsen, array $signature) : array { $context = new Context(); $return_type = UnionType::fromStringInContext(array_shift($signature), $context); $method = new Method($context, $fqsen->getName(), $return_type, 0); $method->setFQSEN($fqsen); return self::methodListFromMethod($method, $code_base); }
/** * @param CodeBase $code_base * A reference to the entire code base in which this * context exists * * @param ReflectionClass $class * A reflection class representing a builtin class. * * @return Clazz * A Class structural element representing the given named * builtin. */ public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz { // Build a set of flags based on the constitution // of the built-in class $flags = 0; if ($class->isFinal()) { $flags = \ast\flags\CLASS_FINAL; } elseif ($class->isInterface()) { $flags = \ast\flags\CLASS_INTERFACE; } elseif ($class->isTrait()) { $flags = \ast\flags\CLASS_TRAIT; } if ($class->isAbstract()) { $flags |= \ast\flags\CLASS_ABSTRACT; } $context = new Context(); $class_fqsen = FullyQualifiedClassName::fromStringInContext($class->getName(), $context); // Build a base class element $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags, $class_fqsen); // If this class has a parent class, add it to the // class info if ($parent_class = $class->getParentClass()) { $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName()); $parent_type = $parent_class_fqsen->asType(); $clazz->setParentType($parent_type); } // n.b.: public properties on internal classes don't get // listed via reflection until they're set unless // they have a default value. Therefore, we don't // bother iterating over `$class->getProperties()` // `$class->getStaticProperties()`. foreach ($class->getDefaultProperties() as $name => $value) { $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN())); $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name); $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0, $property_fqsen); $clazz->addProperty($code_base, $property, new None()); } foreach (UnionType::internalPropertyMapForClassName($clazz->getName()) as $property_name => $property_type_string) { $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN())); $property_type = UnionType::fromStringInContext($property_type_string, new Context()); $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property_name); $property = new Property($property_context, $property_name, $property_type, 0, $property_fqsen); $clazz->addProperty($code_base, $property, new None()); } foreach ($class->getInterfaceNames() as $name) { $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name)); } foreach ($class->getTraitNames() as $name) { $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name)); } foreach ($class->getConstants() as $name => $value) { $constant_fqsen = FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name); $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0, $constant_fqsen); $clazz->addConstant($code_base, $constant); } foreach ($class->getMethods() as $reflection_method) { $method_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN())); $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($method_context, $code_base, $class, $reflection_method); foreach ($method_list as $method) { $clazz->addMethod($code_base, $method, new None()); } } return $clazz; }