getClassByFQSEN() public method

public getClassByFQSEN ( FullyQualifiedClassName $fqsen ) : Clazz
$fqsen Phan\Language\FQSEN\FullyQualifiedClassName The FQSEN of a class to get
return Phan\Language\Element\Clazz A class with the given FQSEN
Example #1
0
 public function testMethodInCodeBase()
 {
     $context = $this->contextForCode("\n                namespace A;\n                Class B {\n                    public function c() {\n                        return 42;\n                    }\n                }\n            ");
     $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\A\\b');
     self::assertTrue($this->code_base->hasClassWithFQSEN($class_fqsen), "Class with FQSEN {$class_fqsen} not found");
     $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     self::assertTrue($clazz->hasMethodWithName($this->code_base, 'c'), "Method with FQSEN not found");
 }
Example #2
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitProp(Node $node) : string
 {
     if (!($node->children['expr']->kind == \ast\AST_VAR && !$node->children['expr']->children['name'] instanceof Node)) {
         return '';
     }
     // $var->prop->method()
     $var = $node->children['expr'];
     $class = null;
     if ($var->children['name'] == 'this') {
         // If we're not in a class scope, 'this' won't work
         if (!$this->context->isInClassScope()) {
             Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
             return '';
         }
         // $this->$node->method()
         if ($node->children['prop'] instanceof Node) {
             // Too hard. Giving up.
             return '';
         }
         $class = $this->context->getClassInScope($this->code_base);
     } else {
         // Get the list of viable class types for the
         // variable
         $union_type = AST::varUnionType($this->context, $var)->nonNativeTypes()->nonGenericArrayTypes();
         if ($union_type->isEmpty()) {
             return '';
         }
         $class_fqsen = $union_type->head()->asFQSEN();
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             return '';
         }
         $class = $this->code_base->getClassByFQSEN($class_fqsen);
     }
     $property_name = $node->children['prop'];
     if (!$class->hasPropertyWithName($this->code_base, $property_name)) {
         // If we can't find the property, there's
         // no type. Thie issue should be caught
         // elsewhere.
         return '';
     }
     try {
         $property = $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
     } catch (AccessException $exception) {
         Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
         return '';
     }
     $union_type = $property->getUnionType()->nonNativeTypes();
     if ($union_type->isEmpty()) {
         // If we don't have a type on the property we
         // can't figure out the class type.
         return '';
     } else {
         // Return the first type on the property
         // that could be a reference to a class
         return (string) $union_type->head()->asFQSEN();
     }
     // No such property was found, or none were classes
     // that could be found
     return '';
 }
Example #3
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeParentConstructorCalled(CodeBase $code_base, Clazz $clazz)
 {
     // Only look at classes configured to require a call
     // to its parent constructor
     if (!in_array($clazz->getName(), Config::get()->parent_constructor_required)) {
         return;
     }
     // Don't worry about internal classes
     if ($clazz->isInternal()) {
         return;
     }
     // Don't worry if there's no parent class
     if (!$clazz->hasParentClassFQSEN()) {
         return;
     }
     if (!$code_base->hasClassWithFQSEN($clazz->getParentClassFQSEN())) {
         // This is an error, but its caught elsewhere. We'll
         // just roll through looking for other errors
         return;
     }
     $parent_clazz = $code_base->getClassByFQSEN($clazz->getParentClassFQSEN());
     if (!$parent_clazz->isAbstract() && !$clazz->getIsParentConstructorCalled()) {
         Issue::emit(Issue::TypeParentConstructorCalled, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz->getFQSEN(), (string) $parent_clazz->getFQSEN());
     }
 }
Example #4
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz)
 {
     // Determine if its a duplicate by looking to see if
     // the FQSEN is suffixed with an alternate ID.
     if (!$clazz->getFQSEN()->isAlternate()) {
         return;
     }
     $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN();
     if (!$code_base->hasClassWithFQSEN($original_fqsen)) {
         // If there's a missing class we'll catch that
         // elsewhere
         return;
     }
     // Get the original class
     $original_class = $code_base->getClassByFQSEN($original_fqsen);
     // Check to see if the original definition was from
     // an internal class
     if ($original_class->isInternal()) {
         Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} internally", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
         // Otherwise, print the coordinates of the original
         // definition
     } else {
         Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} at " . "{$original_class->getContext()->getFile()}:{$original_class->getContext()->getLineNumberStart()}", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
     }
     return;
 }
Example #5
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz)
 {
     // Determine if its a duplicate by looking to see if
     // the FQSEN is suffixed with an alternate ID.
     if (!$clazz->getFQSEN()->isAlternate()) {
         return;
     }
     $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN();
     if (!$code_base->hasClassWithFQSEN($original_fqsen)) {
         // If there's a missing class we'll catch that
         // elsewhere
         return;
     }
     // Get the original class
     $original_class = $code_base->getClassByFQSEN($original_fqsen);
     // Check to see if the original definition was from
     // an internal class
     if ($original_class->isInternal()) {
         Issue::emit(Issue::RedefineClassInternal, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class);
         // Otherwise, print the coordinates of the original
         // definition
     } else {
         Issue::emit(Issue::RedefineClass, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class, $original_class->getContext()->getFile(), $original_class->getContext()->getLineNumberStart());
     }
     return;
 }
Example #6
0
 /**
  * Visit a node with kind `\ast\AST_METHOD_CALL`
  *
  * @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 visitMethodCall(Node $node) : UnionType
 {
     $class_name = AST::classNameFromNode($this->context, $this->code_base, $node);
     if (empty($class_name)) {
         return new UnionType();
     }
     $class_fqsen = FullyQualifiedClassName::fromstringInContext($class_name, $this->context);
     assert($this->code_base->hasClassWithFQSEN($class_fqsen), "Class {$class_fqsen} must exist");
     $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     $method_name = $node->children['method'];
     // Give up on any complicated nonsense where the
     // method name is a variable such as in
     // `$variable->$function_name()`.
     if ($method_name instanceof Node) {
         return new UnionType();
     }
     // Method names can some times turn up being
     // other method calls.
     assert(is_string($method_name), "Method name must be a string. Something else given.");
     if (!$clazz->hasMethodWithName($this->code_base, $method_name)) {
         Log::err(Log::EUNDEF, "call to undeclared method {$class_fqsen}->{$method_name}()", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     $method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context);
     return $method->getUnionType();
 }
Example #7
0
 /**
  * @return Clazz
  * The class that defined this element
  *
  * @throws CodeBaseException
  * An exception may be thrown if we can't find the
  * class
  */
 public function getDefiningClass(CodeBase $code_base) : Clazz
 {
     $class_fqsen = $this->getDefiningClassFQSEN();
     if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
         throw new CodeBaseException($class_fqsen, "Defining class {$class_fqsen} for {$this->getFQSEN()} not found");
     }
     return $code_base->getClassByFQSEN($class_fqsen);
 }
 public function visitNew(Node $node) : bool
 {
     if (!$this->classExists()) {
         return $this->classExistsOrIsNative($node);
     }
     $clazz = $this->code_base->getClassByFQSEN($this->class_fqsen);
     if ($clazz->isAbstract()) {
         if (!$this->context->hasClassFQSEN() || $clazz->getFQSEN() != $this->context->getClassFQSEN()) {
             Log::err(Log::ETYPE, "Cannot instantiate abstract class {$this->class_name}", $this->context->getFile(), $node->lineno);
             return false;
         }
         return true;
     }
     if ($clazz->isInterface()) {
         if (!UnionType::fromStringInContext($this->class_name, $this->context)->isNativeType()) {
             Log::err(Log::ETYPE, "Cannot instantiate interface {$this->class_name}", $this->context->getFile(), $node->lineno);
             return false;
         }
     }
     return true;
 }
Example #9
0
 public function visitStaticProp(Node $node) : bool
 {
     if (!$this->classExists()) {
         return $this->classExistsOrIsNative($node);
     }
     $clazz = $this->code_base->getClassByFQSEN($this->class_fqsen);
     if (is_string($node->children['prop']) && !$clazz->hasPropertyWithName($this->code_base, $node->children['prop'])) {
         Log::err(Log::ETYPE, "Access to undeclared static property {$node->children['prop']} on {$this->class_name}", $this->context->getFile(), $node->lineno);
         return false;
     }
     return true;
 }
Example #10
0
 /**
  * @return Clazz[]
  * A list of classes associated with the given node
  *
  * @throws CodeBaseException
  * An exception is thrown if we can't find a class for
  * the given type
  */
 private function classListFromNode(Node $node)
 {
     // Get the types associated with the node
     $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node);
     // Iterate over each viable class type to see if any
     // have the constant we're looking for
     foreach ($union_type->nonNativeTypes()->getTypeList() as $class_type) {
         // Get the class FQSEN
         $class_fqsen = $class_type->asFQSEN();
         // See if the class exists
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new CodeBaseException($class_fqsen, "reference to undeclared class {$class_fqsen}");
         }
         (yield $this->code_base->getClassByFQSEN($class_fqsen));
     }
 }
Example #11
0
 /**
  * @return Clazz[]
  * A list of classes associated with the given node
  *
  * @throws IssueException
  * An exception is thrown if we can't find a class for
  * the given type
  */
 private function classListFromNode(Node $node)
 {
     // Get the types associated with the node
     $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node);
     // Iterate over each viable class type to see if any
     // have the constant we're looking for
     foreach ($union_type->nonNativeTypes()->getTypeSet() as $class_type) {
         // Get the class FQSEN
         $class_fqsen = $class_type->asFQSEN();
         // See if the class exists
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new IssueException(Issue::fromType(Issue::UndeclaredClassReference)($this->context->getFile(), $node->lineno ?? 0, [(string) $class_fqsen]));
         }
         (yield $this->code_base->getClassByFQSEN($class_fqsen));
     }
 }
Example #12
0
 /**
  * @param bool $validate_class_name
  * If true, we'll validate that the name of the class
  * is valid.
  *
  * @return Clazz
  * The class being referenced in the given node in
  * the given context
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the referenced
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public function getClass(bool $validate_class_name = true) : Clazz
 {
     // Figure out the name of the class
     $class_name = $this->getClassName($validate_class_name);
     // If we can't figure out the class name (which happens
     // from time to time), then give up
     if (empty($class_name)) {
         throw new NodeException($this->node, 'Could not find class name');
     }
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context);
     // Check to see if the class actually exists
     if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
         throw new CodeBaseException($class_fqsen, "Can't find class {$class_fqsen}");
     }
     $class = $this->code_base->getClassByFQSEN($class_fqsen);
     return $class;
 }
Example #13
0
 /**
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @return Clazz
  * Get the class in this scope, or fail real hard
  */
 public function getClassInScope(CodeBase $code_base) : Clazz
 {
     assert($this->isInClassScope(), "Must be in class scope to get class");
     if (!$code_base->hasClassWithFQSEN($this->getClassFQSEN())) {
         Log::err(Log::EFATAL, "Cannot find class with FQSEN {$this->getClassFQSEN()} in context {$this}", $this->getFile(), 0);
     }
     return $code_base->getClassByFQSEN($this->getClassFQSEN());
 }
Example #14
0
 /**
  * @return Clazz[]
  * The set of all alternates to this class
  */
 public function alternateGenerator(CodeBase $code_base) : \Generator
 {
     $alternate_id = 0;
     $fqsen = $this->getFQSEN();
     while ($code_base->hasClassWithFQSEN($fqsen)) {
         (yield $code_base->getClassByFQSEN($fqsen));
         $fqsen = $fqsen->withAlternateId(++$alternate_id);
     }
 }
Example #15
0
 /**
  * @return Clazz[]
  * A list of classes representing the non-native types
  * associated with this UnionType
  *
  * @throws CodeBaseException
  * An exception is thrown if a non-native type does not have
  * an associated class
  */
 public function asClassList(CodeBase $code_base)
 {
     // Iterate over each viable class type to see if any
     // have the constant we're looking for
     foreach ($this->nonNativeTypes()->getTypeList() as $class_type) {
         // Get the class FQSEN
         $class_fqsen = $class_type->asFQSEN();
         // See if the class exists
         if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new CodeBaseException($class_fqsen, "Cannot find class {$class_fqsen}");
         }
         (yield $code_base->getClassByFQSEN($class_fqsen));
     }
 }
Example #16
0
 public function importParentClass(CodeBase $code_base)
 {
     $this->memoize(__METHOD__, function () use($code_base) {
         if (!$this->hasParentClassFQSEN()) {
             return;
         }
         // Let the parent class finder worry about this
         if (!$code_base->hasClassWithFQSEN($this->getParentClassFQSEN())) {
             return;
         }
         assert($code_base->hasClassWithFQSEN($this->getParentClassFQSEN()), "Clazz {$this->getParentClassFQSEN()} should already have been proven to exist.");
         // Get the parent class
         $parent = $code_base->getClassByFQSEN($this->getParentClassFQSEN());
         // Tell the parent to import its own parents first
         $parent->importAncestorClasses($code_base);
         // Import elements from the parent
         $this->importAncestorClass($code_base, $parent);
     });
 }
Example #17
0
File: Clazz.php Project: etsy/phan
 /**
  * @param CodeBase $code_base
  * The entire code base from which we'll find ancestor
  * details
  *
  * @param FullyQualifiedClassName[]
  * A list of class FQSENs to turn into a list of
  * Clazz objects
  *
  * @return Clazz[]
  */
 private function getClassListFromFQSENList(CodeBase $code_base, array $fqsen_list) : array
 {
     $class_list = [];
     foreach ($fqsen_list as $fqsen) {
         if ($code_base->hasClassWithFQSEN($fqsen)) {
             $class_list[] = $code_base->getClassByFQSEN($fqsen);
         }
     }
     return $class_list;
 }
Example #18
0
File: Type.php Project: etsy/phan
 public function isSubclassOf(CodeBase $code_base, Type $parent)
 {
     $fqsen = $this->asFQSEN();
     assert($fqsen instanceof FullyQualifiedClassName);
     $this_clazz = $code_base->getClassByFQSEN($fqsen);
     $parent_fqsen = $parent->asFQSEN();
     assert($parent_fqsen instanceof FullyQualifiedClassName);
     $parent_clazz = $code_base->getClassByFQSEN($parent_fqsen);
     return $this_clazz->isSubclassOf($code_base, $parent_clazz);
 }
Example #19
0
 /**
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @return Clazz
  * Get the class in this scope, or fail real hard
  *
  * @throws CodeBaseException
  * Thrown if we can't find the class in scope within the
  * given codebase.
  */
 public function getClassInScope(CodeBase $code_base) : Clazz
 {
     assert($this->isInClassScope(), "Must be in class scope to get class");
     if (!$code_base->hasClassWithFQSEN($this->getClassFQSEN())) {
         throw new CodeBaseException($this->getClassFQSEN(), "Cannot find class with FQSEN {$this->getClassFQSEN()} in context {$this}");
     }
     return $code_base->getClassByFQSEN($this->getClassFQSEN());
 }
Example #20
0
File: Type.php Project: tpunt/phan
 public function isSubclassOf(CodeBase $code_base, Type $parent)
 {
     $this_clazz = $code_base->getClassByFQSEN($this->asFQSEN());
     $parent_clazz = $code_base->getClassByFQSEN($parent->asFQSEN());
     return $this_clazz->isSubclassOf($code_base, $parent_clazz);
 }
Example #21
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitMethodCall(Node $node) : string
 {
     if ($node->children['expr']->kind == \ast\AST_VAR) {
         if ($node->children['expr']->children['name'] instanceof Node) {
             return '';
         }
         // $var->method()
         if ($node->children['expr']->children['name'] == 'this') {
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             return (string) $this->context->getClassFQSEN();
         }
         $variable_name = $node->children['expr']->children['name'];
         if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
             // Got lost, couldn't find the variable in the current scope
             // If it really isn't defined, it will be caught by the
             // undefined var error
             return '';
         }
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         // Hack - loop through the possible types of the var and assume
         // first found class is correct
         foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
             $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
                 return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             }
         }
         // Could not find name
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_PROP) {
         $prop = $node->children['expr'];
         if (!($prop->children['expr']->kind == \ast\AST_VAR && !$prop->children['expr']->children['name'] instanceof Node)) {
             return '';
         }
         // $var->prop->method()
         $var = $prop->children['expr'];
         if ($var->children['name'] == 'this') {
             // If we're not in a class scope, 'this' won't work
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             // Get the class in scope
             $clazz = $this->code_base->getClassByFQSEN($this->context->getClassFQSEN());
             if ($prop->children['prop'] instanceof Node) {
                 // $this->$prop->method() - too dynamic, give up
                 return '';
             }
             $property_name = $prop->children['prop'];
             if ($clazz->hasPropertyWithName($this->code_base, $property_name)) {
                 try {
                     $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
                 } catch (AccessException $exception) {
                     Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                     return '';
                 }
                 // Find the first viable property type
                 foreach ($property->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
                     $class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
                     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
                         return (string) $class_fqsen;
                     }
                 }
             }
             // No such property was found, or none were classes
             // that could be found
             return '';
         }
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_METHOD_CALL) {
         // Get the type returned by the first method
         // call.
         $union_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
         // Find the subset of types that are viable
         // classes
         $viable_class_types = $union_type->nonNativeTypes()->nonGenericArrayTypes();
         // If there are no non-native types, give up
         if ($viable_class_types->isEmpty()) {
             return '';
         }
         // Return the first non-native type in the
         // list and hope its a class
         return (string) $viable_class_types->head();
     }
     return '';
 }
Example #22
0
 /**
  * @param CodeBase
  * The code base to use in order to find super classes, etc.
  *
  * @param $recursion_depth
  * This thing has a tendency to run-away on me. This tracks
  * how bad I messed up by seeing how far the expanded types
  * go
  *
  * @return UnionType
  * Expands class types to all inherited classes returning
  * a superset of this type.
  */
 public function asExpandedTypes(CodeBase $code_base, int $recursion_depth = 0) : UnionType
 {
     return $this->memoize(__METHOD__, function () use($code_base, $recursion_depth) : UnionType {
         // We're going to assume that if the type hierarchy
         // is taller than some value we probably messed up
         // and should bail out.
         assert($recursion_depth < 20, "Recursion has gotten out of hand for type {$this}");
         if ($this->isNativeType()) {
             return $this->asUnionType();
         }
         $union_type = $this->asUnionType();
         $class_fqsen = $this->isGenericArray() ? $this->genericArrayElementType()->asFQSEN() : $this->asFQSEN();
         if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
             return $union_type;
         }
         $clazz = $code_base->getClassByFQSEN($class_fqsen);
         $union_type->addUnionType($this->isGenericArray() ? $clazz->getUnionType()->asGenericArrayTypes() : $clazz->getUnionType());
         // Resurse up the tree to include all types
         $recursive_union_type = new UnionType();
         foreach ($union_type->getTypeSet() as $clazz_type) {
             if ((string) $clazz_type != (string) $this) {
                 $recursive_union_type->addUnionType($clazz_type->asExpandedTypes($code_base, $recursion_depth + 1));
             } else {
                 $recursive_union_type->addType($clazz_type);
             }
         }
         return $recursive_union_type;
     });
 }
Example #23
0
 /**
  * @param CodeBase
  * The code base to use in order to find super classes, etc.
  *
  * @param $recursion_depth
  * This thing has a tendency to run-away on me. This tracks
  * how bad I messed up by seeing how far the expanded types
  * go
  *
  * @return UnionType
  * Expands class types to all inherited classes returning
  * a superset of this type.
  */
 public function asExpandedTypes(CodeBase $code_base, int $recursion_depth = 0) : UnionType
 {
     return $this->memoize(__METHOD__, function () use($code_base, $recursion_depth) : UnionType {
         assert($recursion_depth < 10, "Recursion has gotten out of hand for type {$this}");
         if ($this->isNativeType()) {
             return $this->asUnionType();
         }
         $union_type = $this->asUnionType();
         $class_fqsen = $this->isGenericArray() ? $this->genericArrayElementType()->asFQSEN() : $this->asFQSEN();
         if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
             return $union_type;
         }
         $clazz = $code_base->getClassByFQSEN($class_fqsen);
         $union_type->addUnionType($this->isGenericArray() ? $clazz->getUnionType()->asGenericArrayTypes() : $clazz->getUnionType());
         // Resurse up the tree to include all types
         $recursive_union_type = new UnionType();
         foreach ($union_type->getTypeList() as $clazz_type) {
             if ((string) $clazz_type != (string) $this) {
                 $recursive_union_type->addUnionType($clazz_type->asExpandedTypes($code_base, $recursion_depth + 1));
             } else {
                 $recursive_union_type->addType($clazz_type);
             }
         }
         return $recursive_union_type;
     });
 }