Inheritance: use trait Phan\Memoize
Beispiel #1
0
 /**
  * @param Context $context
  * @param CodeBase $code_base
  * @param Node|string|null $node
  *
  * @return UnionType
  *
  * @see \Phan\Deprecated\Pass2::node_type
  * Formerly 'function node_type'
  */
 public static function fromNode(Context $context, CodeBase $code_base, $node) : UnionType
 {
     if (!$node instanceof Node) {
         if ($node === null) {
             return new UnionType();
         }
         return Type::fromObject($node)->asUnionType();
     }
     return (new Element($node))->acceptKindVisitor(new UnionTypeVisitor($context, $code_base));
 }
 private function visitClassNode(Node $node) : UnionType
 {
     // Things of the form `new $class_name();`
     if ($node->kind == \ast\AST_VAR) {
         return new UnionType();
     }
     // Anonymous class of form `new class { ... }`
     if ($node->kind == \ast\AST_CLASS && $node->flags & \ast\flags\CLASS_ANONYMOUS) {
         // Generate a stable name for the anonymous class
         $anonymous_class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
         // Turn that into a fully qualified name
         $fqsen = FullyQualifiedClassName::fromStringInContext($anonymous_class_name, $this->context);
         // Turn that into a union type
         return Type::fromFullyQualifiedString((string) $fqsen)->asUnionType();
     }
     // Things of the form `new $method->name()`
     if ($node->kind !== \ast\AST_NAME) {
         return new UnionType();
     }
     // Get the name of the class
     $class_name = $node->children['name'];
     // If this is a straight-forward class name, recurse into the
     // class node and get its type
     if (!Type::isSelfTypeString($class_name)) {
         // TODO: does anyone else call this method?
         return self::unionTypeFromClassNode($this->code_base, $this->context, $node);
     }
     // This is a self-referential node
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name} when not in a class scope", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     // Reference to a parent class
     if ($class_name === 'parent') {
         $class = $this->context->getClassInScope($this->code_base);
         if (!$class->hasParentClassFQSEN()) {
             Log::err(Log::ESTATIC, "Reference to parent of parentless class {$class->getFQSEN()}", $this->context->getFile(), $node->lineno);
             return new UnionType();
         }
         return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType();
     }
     return Type::fromFullyQualifiedString((string) $this->context->getClassFQSEN())->asUnionType();
 }
Beispiel #3
0
 /**
  * @param Context $context
  * The context in which the comment line appears
  *
  * @param string $line
  * An individual line of a comment
  *
  * @return Option<Type>
  * An optional type overriding the extended type of the class
  */
 private static function inheritsFromCommentLine(Context $context, string $line)
 {
     $match = [];
     if (preg_match('/@inherits\\s+(' . Type::type_regex . ')/', $line, $match)) {
         $type_string = $match[1];
         $type = new Some(Type::fromStringInContext($type_string, $context));
         return $type;
     }
     return new None();
 }
Beispiel #4
0
 /**
  * @return bool
  * True if this Type can be cast to the given Type
  * cleanly
  */
 public function canCastToType(Type $type) : bool
 {
     if ($this === $type) {
         return true;
     }
     $s = strtolower((string) $this);
     $d = strtolower((string) $type);
     $s_is_generic_array = $this->isGenericArray();
     $d_is_generic_array = $type->isGenericArray();
     if ($s[0] == '\\') {
         $s = substr($s, 1);
     }
     if ($d[0] == '\\') {
         $d = substr($d, 1);
     }
     if ($s === $d) {
         return true;
     }
     if (Config::get()->scalar_implicit_cast) {
         if ($type->isScalar() && $this->isScalar()) {
             return true;
         }
     }
     if ($s_is_generic_array && $d_is_generic_array) {
         return $this->genericArrayElementType()->canCastToType($type->genericArrayElementType());
     }
     if ($s === 'int' && $d === 'float') {
         return true;
         // int->float is ok
     }
     if (($s === 'array' || $s === 'string' || $s_is_generic_array || $s === 'closure') && $d === 'callable') {
         return true;
     }
     if ($s === 'object' && !$type->isScalar() && $d !== 'array') {
         return true;
     }
     if ($d === 'object' && !$this->isScalar() && $s !== 'array') {
         return true;
     }
     if ($s_is_generic_array && ($d == 'array' || $d == 'arrayaccess')) {
         return true;
     }
     if ($d_is_generic_array && $s === 'array') {
         return true;
     }
     if ($s === 'callable' && $d === 'closure') {
         return true;
     }
     if (($pos = strrpos($d, '\\')) !== false) {
         if ('\\' !== $this->getNamespace()) {
             if (trim($this->getNamespace() . '\\' . $s, '\\') == $d) {
                 return true;
             }
         } else {
             if (substr($d, $pos + 1) === $s) {
                 return true;
                 // Lazy hack, but...
             }
         }
     }
     if (($pos = strrpos($s, '\\')) !== false) {
         if ('\\' !== $type->getNamespace()) {
             if (trim($type->getNamespace() . '\\' . $d, '\\') == $s) {
                 return true;
             }
         } else {
             if (substr($s, $pos + 1) === $d) {
                 return true;
                 // Lazy hack, but...
             }
         }
     }
     return false;
 }
Beispiel #5
0
 /**
  * Visit a node with kind `\ast\AST_UNARY_MINUS`
  *
  * @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 visitUnaryMinus(Node $node) : UnionType
 {
     return Type::fromObject($node->children['expr'])->asUnionType();
 }
 /**
  * @return Type
  * The type of this class
  */
 public function asType() : Type
 {
     return Type::fromFullyQualifiedString((string) $this);
 }
Beispiel #7
0
 /**
  * @param Type|null $parent_type
  * The type of the parent (extended) class of this class.
  *
  * @return void
  */
 public function setParentType(Type $parent_type = null)
 {
     if ($this->getInternalScope()->hasAnyTemplateType()) {
         // Get a reference to the local list of templated
         // types. We'll use this to map templated types on the
         // parent to locally templated types.
         $template_type_map = $this->getInternalScope()->getTemplateTypeMap();
         // Figure out if the given parent type contains any template
         // types.
         $contains_templated_type = false;
         foreach ($parent_type->getTemplateParameterTypeList() as $i => $union_type) {
             foreach ($union_type->getTypeSet() as $type) {
                 if (isset($template_type_map[$type->getName()])) {
                     $contains_templated_type = true;
                     break 2;
                 }
             }
         }
         // If necessary, map the template parameter type list through the
         // local list of templated types.
         if ($contains_templated_type) {
             $parent_type = Type::fromType($parent_type, array_map(function (UnionType $union_type) use($template_type_map) : UnionType {
                 return new UnionType(array_map(function (Type $type) use($template_type_map) : Type {
                     return $template_type_map[$type->getName()] ?? $type;
                 }, $union_type->getTypeSet()->toArray()));
             }, $parent_type->getTemplateParameterTypeList()));
         }
     }
     $this->parent_type = $parent_type;
     // Add the parent to the union type of this
     // class
     $this->getUnionType()->addUnionType($parent_type->asUnionType());
 }
Beispiel #8
0
 /**
  * As per the Serializable interface
  *
  * @param string $serialized
  * A serialized UnionType
  *
  * @return void
  *
  * @see \Serializable
  */
 public function unserialize($serialized)
 {
     $this->type_set = new Set(array_map(function (string $type_name) {
         return Type::fromFullyQualifiedString($type_name);
     }, explode('|', $serialized ?? '')));
 }
Beispiel #9
0
 /**
  * @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'];
     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->hasParentType()) {
             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();
         }
     }
     // We're going to convert the class reference to a type
     $type = null;
     // 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;
         }
         $type = Type::fromFullyQualifiedString($class_name);
     } else {
         $type = Type::fromStringInContext($class_name, $context);
     }
     return $type->asUnionType();
 }
Beispiel #10
0
 /**
  * @param string $type_string
  * A '|' delimited string representing a type in the form
  * 'int|string|null|ClassName'.
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return UnionType
  */
 public static function fromStringInContext(string $type_string, Context $context) : UnionType
 {
     if (empty($type_string)) {
         return new UnionType();
     }
     return new UnionType(array_map(function (string $type_name) use($context, $type_string) {
         assert($type_name !== '', "Type cannot be empty. Type '{$type_name}' given as part of the union type '{$type_string}' in {$context}.");
         return Type::fromStringInContext($type_name, $context);
     }, array_filter(array_map(function (string $type_name) {
         return trim($type_name);
     }, explode('|', $type_string)))));
 }
Beispiel #11
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitReturn(Node $node) : Context
 {
     // Don't check return types in traits
     if ($this->context->isInClassScope()) {
         $clazz = $this->context->getClassInScope($this->code_base);
         if ($clazz->isTrait()) {
             return $this->context;
         }
     }
     // Make sure we're actually returning from a method.
     if (!$this->context->isInFunctionLikeScope()) {
         return $this->context;
     }
     // Get the method/function/closure we're in
     $method = $this->context->getFunctionLikeInScope($this->code_base);
     assert(!empty($method), "We're supposed to be in either method or closure scope.");
     // Figure out what we intend to return
     $method_return_type = $method->getUnionType();
     // Figure out what is actually being returned
     $expression_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
     if ($expression_type->hasStaticType()) {
         $expression_type = $expression_type->withStaticResolvedInContext($this->context);
     }
     // If there is no declared type, see if we can deduce
     // what it should be based on the return type
     if ($method_return_type->isEmpty() || $method->isReturnTypeUndefined()) {
         $method->setIsReturnTypeUndefined(true);
         // Set the inferred type of the method based
         // on what we're returning
         $method->getUnionType()->addUnionType($expression_type);
         // No point in comparing this type to the
         // type we just set
         return $this->context;
     }
     if (!$method->isReturnTypeUndefined() && !$expression_type->canCastToExpandedUnionType($method_return_type, $this->code_base) && !$method->getUnionType()->canCastToExpandedUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType(), $this->code_base)) {
         $this->emitIssue(Issue::TypeMismatchReturn, $node->lineno ?? 0, (string) $expression_type, $method->getName(), (string) $method_return_type);
     }
     if ($method->isReturnTypeUndefined()) {
         // Add the new type to the set of values returned by the
         // method
         $method->getUnionType()->addUnionType($expression_type);
     }
     // Mark the method as returning something
     $method->setHasReturn(($node->children['expr'] ?? null) !== null);
     return $this->context;
 }
Beispiel #12
0
 public function testGenericArrayTypeFromString()
 {
     $type = Type::fromFullyQualifiedString("int[][]");
     $this->assertEquals($type->genericArrayElementType()->__toString(), "int[]");
 }
Beispiel #13
0
 /**
  * @param CodeBase $code_base
  * The global code base
  *
  * @param FunctionInterface $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arglist_type_check
  * Formerly `function arglist_type_check`
  */
 private static function analyzeParameterList(CodeBase $code_base, FunctionInterface $method, Node $node, Context $context)
 {
     foreach ($node->children ?? [] as $i => $argument) {
         // Get the parameter associated with this argument
         $parameter = $method->getParameterList()[$i] ?? null;
         // This issue should be caught elsewhere
         if (!$parameter) {
             continue;
         }
         // If this is a pass-by-reference parameter, make sure
         // we're passing an allowable argument
         if ($parameter->isPassByReference()) {
             if (!$argument instanceof \ast\Node || $argument->kind != \ast\AST_VAR && $argument->kind != \ast\AST_DIM && $argument->kind != \ast\AST_PROP && $argument->kind != \ast\AST_STATIC_PROP) {
                 Issue::maybeEmit($code_base, $context, Issue::TypeNonVarPassByRef, $node->lineno ?? 0, $i + 1, (string) $method->getFQSEN());
             } else {
                 $variable_name = (new ContextNode($code_base, $context, $argument))->getVariableName();
                 if (Type::isSelfTypeString($variable_name) && !$context->isInClassScope() && $argument->kind == \ast\AST_STATIC_PROP && $argument->kind == \ast\AST_PROP) {
                     Issue::maybeEmit($code_base, $context, Issue::ContextNotObject, $node->lineno ?? 0, "{$variable_name}");
                 }
             }
         }
         // Get the type of the argument. We'll check it against
         // the parameter in a moment
         $argument_type = UnionType::fromNode($context, $code_base, $argument);
         // Expand it to include all parent types up the chain
         $argument_type_expanded = $argument_type->asExpandedTypes($code_base);
         // Check the method to see if it has the correct
         // parameter types. If not, keep hunting through
         // alternates of the method until we find one that
         // takes the correct types
         $alternate_parameter = null;
         $alternate_found = false;
         foreach ($method->alternateGenerator($code_base) as $alternate_id => $alternate_method) {
             if (empty($alternate_method->getParameterList()[$i])) {
                 continue;
             }
             // Get the parameter associated with this argument
             $alternate_parameter = $alternate_method->getParameterList()[$i] ?? null;
             // Expand the types to find all parents and traits
             $alternate_parameter_type_expanded = $alternate_parameter->getUnionType()->asExpandedTypes($code_base);
             // See if the argument can be cast to the
             // parameter
             if ($argument_type_expanded->canCastToUnionType($alternate_parameter_type_expanded)) {
                 $alternate_found = true;
                 break;
             }
         }
         if (!$alternate_found) {
             $parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown';
             $parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown';
             if ($method->isInternal()) {
                 Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgumentInternal, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type);
             } else {
                 Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgument, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
             }
         }
     }
 }
Beispiel #14
0
 /**
  * @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;
 }
Beispiel #15
0
 /**
  * The return type of the given FunctionInterface to a Generator.
  * Emit an Issue if the documented return type is incompatible with that.
  * @return void
  */
 private function setReturnTypeOfGenerator(FunctionInterface $func, Node $node)
 {
     // Currently, there is no way to describe the types passed to
     // a Generator in phpdoc.
     // So, nothing bothers recording the types beyond \Generator.
     $func->setHasReturn(true);
     // Returns \Generator, technically
     $func->setHasYield(true);
     if ($func->getUnionType()->isEmpty()) {
         $func->setIsReturnTypeUndefined(true);
         $func->getUnionType()->addUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType());
     }
     if (!$func->isReturnTypeUndefined()) {
         $func_return_type = $func->getUnionType();
         if (!$func_return_type->canCastToExpandedUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType(), $this->code_base)) {
             // At least one of the documented return types must
             // be Generator, Iterable, or Traversable.
             // Check for the issue here instead of in visitReturn/visitYield so that
             // the check is done exactly once.
             $this->emitIssue(Issue::TypeMismatchReturn, $node->lineno ?? 0, '\\Generator', $func->getName(), (string) $func_return_type);
         }
     }
 }
Beispiel #16
0
 /**
  * @return bool
  * True if this Type can be cast to the given Type
  * cleanly
  */
 public function canCastToType(Type $type) : bool
 {
     if ($this === $type) {
         return true;
     }
     $s = (string) $this;
     $d = (string) $type;
     if ($s[0] == '\\') {
         $s = substr($s, 1);
     }
     if ($d[0] == '\\') {
         $d = substr($d, 1);
     }
     if ($s === $d) {
         return true;
     }
     if ($s === 'int' && $d === 'float') {
         return true;
         // int->float is ok
     }
     if (($s === 'array' || $s === 'string' || strpos($s, '[]') !== false) && $d === 'callable') {
         return true;
     }
     if ($s === 'object' && !$type->isScalar() && $d !== 'array') {
         return true;
     }
     if ($d === 'object' && !$this->isScalar() && $s !== 'array') {
         return true;
     }
     if (strpos($s, '[]') !== false && $d === 'array') {
         return true;
     }
     if (strpos($d, '[]') !== false && $s === 'array') {
         return true;
     }
     if ($s === 'callable' && $d === 'closure') {
         return true;
     }
     if (($pos = strrpos($d, '\\')) !== false) {
         if ('\\' !== $this->getNamespace()) {
             if (trim($this->getNamespace() . '\\' . $s, '\\') == $d) {
                 return true;
             }
         } else {
             if (substr($d, $pos + 1) === $s) {
                 return true;
                 // Lazy hack, but...
             }
         }
     }
     if (($pos = strrpos($s, '\\')) !== false) {
         if ('\\' !== $type->getNamespace()) {
             if (trim($type->getNamespace() . '\\' . $d, '\\') == $s) {
                 return true;
             }
         } else {
             if (substr($s, $pos + 1) === $d) {
                 return true;
                 // Lazy hack, but...
             }
         }
     }
     return false;
 }
Beispiel #17
0
 /**
  * @param CodeBase $code_base
  * The global code base
  *
  * @param FunctionInterface $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arglist_type_check
  * Formerly `function arglist_type_check`
  */
 private static function analyzeParameterList(CodeBase $code_base, FunctionInterface $method, Node $node, Context $context)
 {
     // There's nothing reasonable we can do here
     if ($method instanceof Method) {
         if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
             return;
         }
     }
     foreach ($node->children ?? [] as $i => $argument) {
         // Get the parameter associated with this argument
         $parameter = $method->getParameterForCaller($i);
         // This issue should be caught elsewhere
         if (!$parameter) {
             continue;
         }
         // If this is a pass-by-reference parameter, make sure
         // we're passing an allowable argument
         if ($parameter->isPassByReference()) {
             if (!$argument instanceof \ast\Node || $argument->kind != \ast\AST_VAR && $argument->kind != \ast\AST_DIM && $argument->kind != \ast\AST_PROP && $argument->kind != \ast\AST_STATIC_PROP) {
                 Issue::maybeEmit($code_base, $context, Issue::TypeNonVarPassByRef, $node->lineno ?? 0, $i + 1, (string) $method->getFQSEN());
             } else {
                 $variable_name = (new ContextNode($code_base, $context, $argument))->getVariableName();
                 if (Type::isSelfTypeString($variable_name) && !$context->isInClassScope() && $argument->kind == \ast\AST_STATIC_PROP && $argument->kind == \ast\AST_PROP) {
                     Issue::maybeEmit($code_base, $context, Issue::ContextNotObject, $node->lineno ?? 0, "{$variable_name}");
                 }
             }
         }
         // Get the type of the argument. We'll check it against
         // the parameter in a moment
         $argument_type = UnionType::fromNode($context, $code_base, $argument);
         // Expand it to include all parent types up the chain
         $argument_type_expanded = $argument_type->asExpandedTypes($code_base);
         // Check the method to see if it has the correct
         // parameter types. If not, keep hunting through
         // alternates of the method until we find one that
         // takes the correct types
         $alternate_parameter = null;
         $alternate_found = false;
         foreach ($method->alternateGenerator($code_base) as $alternate_id => $alternate_method) {
             // Get the parameter associated with this argument
             $candidate_alternate_parameter = $alternate_method->getParameterForCaller($i);
             if (is_null($candidate_alternate_parameter)) {
                 continue;
             }
             $alternate_parameter = $candidate_alternate_parameter;
             // See if the argument can be cast to the
             // parameter
             if ($argument_type_expanded->canCastToUnionType($alternate_parameter->getUnionType())) {
                 $alternate_found = true;
                 break;
             }
         }
         if (!$alternate_found) {
             $parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown';
             $parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown';
             if (is_object($parameter_type) && $parameter_type->hasTemplateType()) {
                 // Don't worry about template types
             } elseif ($method->isInternal()) {
                 // If we are not in strict mode and we accept a string parameter
                 // and the argument we are passing has a __toString method then it is ok
                 if (!$context->getIsStrictTypes() && $parameter_type->hasType(StringType::instance())) {
                     try {
                         foreach ($argument_type_expanded->asClassList($code_base, $context) as $clazz) {
                             if ($clazz->hasMethodWithName($code_base, "__toString")) {
                                 return;
                             }
                         }
                     } catch (CodeBaseException $e) {
                         // Swallow "Cannot find class", go on to emit issue
                     }
                 }
                 Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgumentInternal, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type);
             } else {
                 Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgument, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
             }
         }
     }
 }