in src/type_translator.ts [619:729]
private translateObject(type: ts.ObjectType): string {
if (type.symbol && this.isAlwaysUnknownSymbol(type.symbol)) return '?';
// NOTE: objectFlags is an enum, but a given type can have multiple flags.
// Array<string> is both ts.ObjectFlags.Reference and ts.ObjectFlags.Interface.
if (type.objectFlags & ts.ObjectFlags.Class) {
if (!type.symbol) {
this.warn('class has no symbol');
return '?';
}
const name = this.symbolToString(type.symbol);
if (!name) {
// An anonymous type. Make sure not to emit '!?', as that is a syntax error in Closure
// Compiler.
return '?';
}
return '!' + name;
} else if (type.objectFlags & ts.ObjectFlags.Interface) {
// Note: ts.InterfaceType has a typeParameters field, but that
// specifies the parameters that the interface type *expects*
// when it's used, and should not be transformed to the output.
// E.g. a type like Array<number> is a TypeReference to the
// InterfaceType "Array", but the "number" type parameter is
// part of the outer TypeReference, not a typeParameter on
// the InterfaceType.
if (!type.symbol) {
this.warn('interface has no symbol');
return '?';
}
if (type.symbol.flags & ts.SymbolFlags.Value) {
// The symbol is both a type and a value.
// For user-defined types in this state, we may not have a Closure name
// for the type. See the type_and_value test.
if (!typeValueConflictHandled(type.symbol)) {
this.warn(`type/symbol conflict for ${type.symbol.name}, using {?} for now`);
return '?';
}
}
return '!' + this.symbolToString(type.symbol);
} else if (type.objectFlags & ts.ObjectFlags.Reference) {
// A reference to another type, e.g. Array<number> refers to Array.
// Emit the referenced type and any type arguments.
const referenceType = type as ts.TypeReference;
// A tuple is a ReferenceType where the target is flagged Tuple and the
// typeArguments are the tuple arguments. Closure Compiler does not
// support tuple types, so tsickle emits this as `Array<?>`.
// It would also be possible to emit an Array of the union of the
// constituent types. In experimentation, this however does not seem to
// improve optimization compatibility much, as long as destructuring
// assignments are aliased.
if (referenceType.target.objectFlags & ts.ObjectFlags.Tuple) {
return '!Array<?>';
}
let typeStr = '';
if (referenceType.target === referenceType) {
// We get into an infinite loop here if the inner reference is
// the same as the outer; this can occur when this function
// fails to translate a more specific type before getting to
// this point.
throw new Error(
`reference loop in ${typeToDebugString(referenceType)} ${referenceType.flags}`);
}
typeStr += this.translate(referenceType.target);
// Translate can return '?' for a number of situations, e.g. type/value conflicts.
// `?<?>` is illegal syntax in Closure Compiler, so just return `?` here.
if (typeStr === '?') return '?';
let typeArgs: readonly ts.Type[] =
this.typeChecker.getTypeArguments(referenceType) ?? [];
// Nested types have references to type parameters of all enclosing types.
// Those are always at the beginning of the list of type arguments.
const outerTypeParameters = referenceType.target.outerTypeParameters;
if (outerTypeParameters) {
typeArgs = typeArgs.slice(outerTypeParameters.length);
}
if (this.dropFinalTypeArgument) {
typeArgs = typeArgs.slice(0, typeArgs.length - 1);
}
if (typeArgs.length > 0) {
// If a type references itself recursively, such as in `type A = B<A>`,
// the type parameter will resolve to itself. In the example above B's
// type parameter will be B<B<B<...>>> and just go on indefinitely. To
// prevent this we mark the type as seen and if this type comes up again
// `?` will be used in its place. Note this won't trigger for something
// like `Node<Node<number>>` because this is comparing the types, not
// the symbols. In the nested nodes case the symbols are the same, but
// `Node<Node<number>> !== Node<number>`. if (t === referenceType)
// return '?';
this.seenTypes.push(referenceType);
const params = typeArgs.map(t => this.translate(t));
this.seenTypes.pop();
typeStr += `<${params.join(', ')}>`;
}
return typeStr;
} else if (type.objectFlags & ts.ObjectFlags.Anonymous) {
return this.translateAnonymousType(type);
}
/*
TODO(ts2.1): more unhandled object type flags:
Mapped
Instantiated
ObjectLiteral
EvolvingArray
ObjectLiteralPatternWithComputedProperties
*/
this.warn(`unhandled type ${typeToDebugString(type)}`);
return '?';
}