public function visitNew(Node $node) : UnionType
{
$union_type = $this->visitClassNode($node->children['class']);
// For any types that are templates, map them to concrete
// types based on the parameters passed in.
return new UnionType(array_map(function (Type $type) use($node) {
// Get a fully qualified name for the type
$fqsen = $type->asFQSEN();
// If this isn't a class, its fine as is
if (!$fqsen instanceof FullyQualifiedClassName) {
return $type;
}
assert($fqsen instanceof FullyQualifiedClassName);
// If we don't have the class, we'll catch that problem
// elsewhere
if (!$this->code_base->hasClassWithFQSEN($fqsen)) {
return $type;
}
$class = $this->code_base->getClassByFQSEN($fqsen);
// If this class doesn't have any generics on it, we're
// fine as we are with this Type
if (!$class->isGeneric()) {
return $type;
}
// Now things are interesting. We need to map the
// arguments to the generic types and return a special
// kind of type.
// Get the constructor so that we can figure out what
// template types we're going to be mapping
$constructor_method = $class->getMethodByName($this->code_base, '__construct');
// Map each argument to its type
$arg_type_list = array_map(function ($arg_node) {
return UnionType::fromNode($this->context, $this->code_base, $arg_node);
}, $node->children['args']->children ?? []);
// Map each template type o the argument's concrete type
$template_type_list = [];
foreach ($constructor_method->getParameterList() as $i => $parameter) {
if (isset($arg_type_list[$i])) {
$template_type_list[] = $arg_type_list[$i];
}
}
// Create a new type that assigns concrete
// types to template type identifiers.
return Type::fromType($type, $template_type_list);
}, $union_type->getTypeSet()->toArray()));
}