本文整理汇总了PHP中Phan\Language\UnionType类的典型用法代码示例。如果您正苦于以下问题:PHP UnionType类的具体用法?PHP UnionType怎么用?PHP UnionType使用的例子?那么, 这里精选的类代码示例或许可以为您提供帮助。
在下文中一共展示了UnionType类的15个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于系统推荐出更棒的PHP代码示例。
示例1: visitProp
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitProp(Node $node) : Context
{
$property_name = $node->children['prop'];
// Things like $foo->$bar
if (!is_string($property_name)) {
return $this->context;
}
assert(is_string($property_name), "Property must be string in context {$this->context}");
try {
$class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
} catch (CodeBaseException $exception) {
// This really shouldn't happen since the code
// parsed cleanly. This should fatal.
// throw $exception;
return $this->context;
} catch (\Exception $exception) {
// If we can't figure out what kind of a class
// this is, don't worry about it
return $this->context;
}
foreach ($class_list as $clazz) {
// Check to see if this class has the property or
// a setter
if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
continue;
}
}
try {
$property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
} catch (IssueException $exception) {
$exception->getIssueInstance()();
return $this->context;
}
if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
Issue::emit(Issue::TypeMismatchProperty, $this->context->getFile(), $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
return $this->context;
}
// After having checked it, add this type to it
$property->getUnionType()->addUnionType($this->right_type);
return $this->context;
}
if (Config::get()->allow_missing_properties) {
try {
// Create the property
(new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
} catch (\Exception $exception) {
// swallow it
}
} else {
if (!empty($class_list)) {
Issue::emit(Issue::UndeclaredProperty, $this->context->getFile(), $node->lineno ?? 0, $property_name);
} else {
// If we hit this part, we couldn't figure out
// the class, so we ignore the issue
}
}
return $this->context;
}
示例2: fromNodeInContext
/**
* @param Node $node
* An AST_VAR node
*
* @param Context $context
* The context in which the variable is found
*
* @param CodeBase $code_base
*
* @return Variable
* A variable begotten from a node
*/
public static function fromNodeInContext(Node $node, Context $context, CodeBase $code_base, bool $should_check_type = true) : Variable
{
$variable_name = AST::variableName($node);
// Get the type of the assignment
$union_type = $should_check_type ? UnionType::fromNode($context, $code_base, $node) : new UnionType();
$variable = new Variable($context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $variable_name, $union_type, $node->flags);
return $variable;
}
示例3: functionListFromFunction
/**
* @param FunctionInterface $function
* Get a list of methods hydrated with type information
* for the given partial method
*
* @param CodeBase $code_base
* The global code base holding all state
*
* @return Method[]
* A list of typed methods based on the given method
*/
private static function functionListFromFunction(FunctionInterface $function, CodeBase $code_base) : array
{
// See if we have any type information for this
// internal function
$map_list = UnionType::internalFunctionSignatureMapForFQSEN($function->getFQSEN());
if (!$map_list) {
return [$function];
}
$alternate_id = 0;
return array_map(function ($map) use($function, &$alternate_id) : FunctionInterface {
$alternate_function = clone $function;
$alternate_function->setFQSEN($alternate_function->getFQSEN()->withAlternateId($alternate_id++));
// Set the return type if one is defined
if (!empty($map['return_type'])) {
$alternate_function->setUnionType($map['return_type']);
}
// Load properties if defined
foreach ($map['property_name_type_map'] ?? [] as $parameter_name => $parameter_type) {
$flags = 0;
$is_optional = false;
// Check to see if its a pass-by-reference parameter
if (strpos($parameter_name, '&') === 0) {
$flags |= \ast\flags\PARAM_REF;
$parameter_name = substr($parameter_name, 1);
}
// Check to see if its variadic
if (strpos($parameter_name, '...') !== false) {
$flags |= \ast\flags\PARAM_VARIADIC;
$parameter_name = str_replace('...', '', $parameter_name);
}
// Check to see if its an optional parameter
if (strpos($parameter_name, '=') !== false) {
$is_optional = true;
$parameter_name = str_replace('=', '', $parameter_name);
}
$parameter = new Parameter($function->getContext(), $parameter_name, $parameter_type, $flags);
if ($is_optional) {
$parameter->setDefaultValueType(NullType::instance()->asUnionType());
}
// Add the parameter
$alternate_function->appendParameter($parameter);
}
$alternate_function->setNumberOfRequiredParameters(array_reduce($alternate_function->getParameterList(), function (int $carry, Parameter $parameter) : int {
return $carry + ($parameter->isOptional() ? 0 : 1);
}, 0));
$alternate_function->setNumberOfOptionalParameters(count($alternate_function->getParameterList()) - $alternate_function->getNumberOfRequiredParameters());
if ($alternate_function instanceof Method) {
if ($alternate_function->getIsMagicCall() || $alternate_function->getIsMagicCallStatic()) {
$alternate_function->setNumberOfOptionalParameters(999);
$alternate_function->setNumberOfRequiredParameters(0);
}
}
return $alternate_function;
}, $map_list);
}
示例4: fromRow
/**
* @param array
* A map from column name to value
*
* @return Model
* An instance of the model derived from row data
*/
public static function fromRow(array $row) : Clazz
{
$parent_fqsen = $row['parent_class_fqsen'] ? FullyQualifiedClassName::fromFullyQualifiedString($row['parent_class_fqsen']) : null;
$interface_fqsen_list = array_map(function (string $fqsen_string) {
return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
}, array_filter(explode('|', $row['interface_fqsen_list'])));
$trait_fqsen_list = array_map(function (string $fqsen_string) {
return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
}, array_filter(explode('|', $row['trait_fqsen_list'])));
$clazz = new ClazzElement(unserialize(base64_decode($row['context'])), $row['name'], UnionType::fromFullyQualifiedString($row['type']), (int) $row['flags'], $parent_fqsen, $interface_fqsen_list, $trait_fqsen_list);
return new Clazz($clazz);
}
示例5: addUndefinedFunctionSignatures
/**
* Add any functions from the FunctionSignatureMap that aren't
* defined in this version of PHP to the code base
*
* @return void
*/
private function addUndefinedFunctionSignatures()
{
$function_signature_map = UnionType::internalFunctionSignatureMap();
foreach ($function_signature_map as $function_name => $signature) {
$fqsen = FullyQualifiedFunctionName::make('\\', $function_name);
// If we already loaded the function, skip it
if ($this->hasMethod($fqsen)) {
continue;
}
// Add each method returned for the signature
foreach (Method::methodListFromSignature($this, $fqsen, $signature) as $method) {
$this->addMethod($method);
}
}
}
示例6: visitProp
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitProp(Node $node) : Context
{
$property_name = $node->children['prop'];
// Things like $foo->$bar
if (!is_string($property_name)) {
return $this->context;
}
assert(is_string($property_name), "Property must be string in context {$this->context}");
try {
$clazz = (new ContextNode($this->code_base, $this->context, $node))->getClass();
} catch (CodeBaseException $exception) {
Log::err(Log::EFATAL, $exception->getMessage(), $this->context->getFile(), $node->lineno);
} catch (\Exception $exception) {
// If we can't figure out what kind of a class
// this is, don't worry about it
return $this->context;
}
if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
// Check to see if the class has a __set method
if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
if (Config::get()->allow_missing_properties) {
try {
// Create the property
(new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
} catch (\Exception $exception) {
// swallow it
}
} else {
Log::err(Log::EAVAIL, "Missing property with name '{$property_name}'", $this->context->getFile(), $node->lineno);
}
}
return $this->context;
}
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 $this->context;
}
if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
Log::err(Log::ETYPE, "assigning {$this->right_type} to property but {$clazz->getFQSEN()}::{$property->getName()} is {$property->getUnionType()}", $this->context->getFile(), $node->lineno);
return $this->context;
}
// After having checked it, add this type to it
$property->getUnionType()->addUnionType($this->right_type);
return $this->context;
}
示例7: unionTypeFromClassNode
/**
* @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);
}
示例8: typeStringFromCode
/**
* @return string
* A string representation of the union type begotten from
* the first statement in the statement list in the given
* code.
*/
private function typeStringFromCode(string $code) : string
{
return UnionType::fromNode($this->context, $this->code_base, \ast\parse_code($code, Config::get()->ast_version)->children[0])->asExpandedTypes($this->code_base)->__toString();
}
示例9: get
/**
* Force the future to figure out the type of the
* given object or throw an IssueException if it
* is unable to do so
*
* @return UnionType
* The type of the future
*
* @throws IssueException
* An exception is thrown if we are unable to determine
* the type at the time this method is called
*/
public function get() : UnionType
{
return UnionType::fromNode($this->context, $this->code_base, $this->node, false);
}
示例10: hasMethodWithScopeAndName
/**
* @param string $scope
* The scope of the method or function
*
* @param string $name
* The name of the method (with an optional alternate id)
*
* @return bool
*/
private function hasMethodWithScopeAndName(string $scope, string $name)
{
if (!empty($this->method_map[$scope][$name])) {
return true;
}
// For elements in the root namespace, check to see if
// there's a static method signature for something that
// hasn't been loaded into memory yet and create a
// method out of it as its requested
if ('\\' == $scope) {
$function_signature_map = UnionType::internalFunctionSignatureMap();
$fqsen = FullyQualifiedFunctionName::make($scope, $name);
if (!empty($function_signature_map[$name])) {
$signature = $function_signature_map[$name];
// Add each method returned for the signature
foreach (FunctionFactory::functionListFromSignature($this, $fqsen, $signature) as $method) {
$this->addMethod($method);
}
return true;
}
}
if (Database::isEnabled()) {
// Otherwise, check the database
try {
MethodModel::read(Database::get(), $scope . '|' . $name);
return true;
} catch (NotFoundException $exception) {
return false;
}
} else {
return false;
}
}
示例11: fromNode
/**
* @param Context $context
* The context in which the node appears
*
* @param CodeBase $code_base
*
* @param Node $node
* An AST node representing a function
*
* @return Func
* A Func representing the AST node in the
* given context
*/
public static function fromNode(Context $context, CodeBase $code_base, Decl $node) : Func
{
// Parse the comment above the function to get
// extra meta information about the function.
$comment = Comment::fromStringInContext($node->docComment ?? '', $context);
// @var Parameter[]
// The list of parameters specified on the
// function
$parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
// Add each parameter to the scope of the function
foreach ($parameter_list as $parameter) {
$context = $context->withScopeVariable($parameter);
}
// Create the skeleton function object from what
// we know so far
$func = new Func($context, (string) $node->name, new UnionType(), $node->flags ?? 0);
// If the function is Analyzable, set the node so that
// we can come back to it whenever we like and
// rescan it
$func->setNode($node);
// Set the parameter list on the function
$func->setParameterList($parameter_list);
$func->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
return $carry + ($parameter->isRequired() ? 1 : 0);
}, 0));
$func->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
return $carry + ($parameter->isOptional() ? 1 : 0);
}, 0));
// Check to see if the comment specifies that the
// function is deprecated
$func->setIsDeprecated($comment->isDeprecated());
$func->setSuppressIssueList($comment->getSuppressIssueList());
// Take a look at function return types
if ($node->children['returnType'] !== null) {
// Get the type of the parameter
$union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
$func->getUnionType()->addUnionType($union_type);
}
if ($comment->hasReturnUnionType()) {
// See if we have a return type specified in the comment
$union_type = $comment->getReturnType();
assert(!$union_type->hasSelfType(), "Function referencing self in {$context}");
$func->getUnionType()->addUnionType($union_type);
}
// Add params to local scope for user functions
if (!$func->isInternal()) {
$parameter_offset = 0;
foreach ($func->getParameterList() as $i => $parameter) {
if ($parameter->getUnionType()->isEmpty()) {
// If there is no type specified in PHP, check
// for a docComment with @param declarations. We
// assume order in the docComment matches the
// parameter order in the code
if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
$comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
$parameter->getUnionType()->addUnionType($comment_type);
}
}
// If there's a default value on the parameter, check to
// see if the type of the default is cool with the
// specified type.
if ($parameter->hasDefaultValue()) {
$default_type = $parameter->getDefaultValueType();
if (!$default_type->isEqualTo(NullType::instance()->asUnionType())) {
if (!$default_type->isEqualTo(NullType::instance()->asUnionType()) && !$default_type->canCastToUnionType($parameter->getUnionType())) {
Issue::maybeEmit($code_base, $context, Issue::TypeMismatchDefault, $node->lineno ?? 0, (string) $parameter->getUnionType(), $parameter->getName(), (string) $default_type);
}
$parameter->getUnionType()->addUnionType($default_type);
}
// If we have no other type info about a parameter,
// just because it has a default value of null
// doesn't mean that is its type. Any type can default
// to null
if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
$parameter->getUnionType()->addType(NullType::instance());
}
}
++$parameter_offset;
}
}
return $func;
}
示例12: unionTypeFromClassNode
/**
* @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);
}
示例13: visitBinaryAdd
/**
* @param Node $node
* A node to check types on
*
* @return UnionType
* The resulting type(s) of the binary operation
*/
public function visitBinaryAdd(Node $node) : UnionType
{
$left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
$right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
// fast-track common cases
if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
return IntType::instance()->asUnionType();
}
// If both left and right are arrays, then this is array
// concatenation.
if ($left->isGenericArray() && $right->isGenericArray()) {
if ($left->isEqualTo($right)) {
return $left;
}
return ArrayType::instance()->asUnionType();
}
if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
return FloatType::instance()->asUnionType();
}
$left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes()) || $left->isType(ArrayType::instance());
$right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes()) || $right->isType(ArrayType::instance());
if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidRightOperand, $node->lineno ?? 0);
return new UnionType();
} elseif ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidLeftOperand, $node->lineno ?? 0);
return new UnionType();
} elseif ($left_is_array || $right_is_array) {
// If it is a '+' and we know one side is an array
// and the other is unknown, assume array
return ArrayType::instance()->asUnionType();
}
return new UnionType([IntType::instance(), FloatType::instance()]);
}
示例14: fromNode
/**
* @param Context $context
* The context in which the node appears
*
* @param CodeBase $code_base
*
* @param Node $node
* An AST node representing a method
*
* @return Method
* A Method representing the AST node in the
* given context
*/
public static function fromNode(Context $context, CodeBase $code_base, Decl $node, FullyQualifiedMethodName $fqsen) : Method
{
// Create the skeleton method object from what
// we know so far
$method = new Method($context, (string) $node->name, new UnionType(), $node->flags ?? 0, $fqsen);
// Parse the comment above the method to get
// extra meta information about the method.
$comment = Comment::fromStringInContext($node->docComment ?? '', $context);
// @var Parameter[]
// The list of parameters specified on the
// method
$parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
// Add each parameter to the scope of the function
foreach ($parameter_list as $parameter) {
$method->getInternalScope()->addVariable($parameter);
}
// If the method is Analyzable, set the node so that
// we can come back to it whenever we like and
// rescan it
$method->setNode($node);
// Set the parameter list on the method
$method->setParameterList($parameter_list);
$method->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
return $carry + ($parameter->isRequired() ? 1 : 0);
}, 0));
$method->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
return $carry + ($parameter->isOptional() ? 1 : 0);
}, 0));
// Check to see if the comment specifies that the
// method is deprecated
$method->setIsDeprecated($comment->isDeprecated());
$method->setSuppressIssueList($comment->getSuppressIssueList());
if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
$method->setNumberOfOptionalParameters(999);
$method->setNumberOfRequiredParameters(0);
}
// Take a look at method return types
if ($node->children['returnType'] !== null) {
// Get the type of the parameter
$union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
$method->getUnionType()->addUnionType($union_type);
}
if ($comment->hasReturnUnionType()) {
// See if we have a return type specified in the comment
$union_type = $comment->getReturnType();
if ($union_type->hasSelfType()) {
// We can't actually figure out 'static' at this
// point, but fill it in regardless. It will be partially
// correct
if ($context->isInClassScope()) {
// n.b.: We're leaving the reference to self, static
// or $this in the type because I'm guessing
// it doesn't really matter. Apologies if it
// ends up being an issue.
$union_type->addUnionType($context->getClassFQSEN()->asUnionType());
}
}
$method->getUnionType()->addUnionType($union_type);
}
// Add params to local scope for user functions
if (!$method->isInternal()) {
$parameter_offset = 0;
foreach ($method->getParameterList() as $i => $parameter) {
if ($parameter->getUnionType()->isEmpty()) {
// If there is no type specified in PHP, check
// for a docComment with @param declarations. We
// assume order in the docComment matches the
// parameter order in the code
if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
$comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
$parameter->getUnionType()->addUnionType($comment_type);
}
}
// If there's a default value on the parameter, check to
// see if the type of the default is cool with the
// specified type.
if ($parameter->hasDefaultValue()) {
$default_type = $parameter->getDefaultValueType();
if (!$default_type->isEqualTo(NullType::instance()->asUnionType())) {
if (!$default_type->isEqualTo(NullType::instance()->asUnionType()) && !$default_type->canCastToUnionType($parameter->getUnionType())) {
Issue::maybeEmit($code_base, $context, Issue::TypeMismatchDefault, $node->lineno ?? 0, (string) $parameter->getUnionType(), $parameter->getName(), (string) $default_type);
}
$parameter->getUnionType()->addUnionType($default_type);
}
// If we have no other type info about a parameter,
// just because it has a default value of null
// doesn't mean that is its type. Any type can default
//.........这里部分代码省略.........
示例15: visitMethodCall
/**
* @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
{
// Get the type returned by the first method
// call.
$union_type = UnionType::fromNode($this->context, $this->code_base, $node);
// 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();
}