in src/type_translator.ts [739:889]
private translateAnonymousType(type: ts.Type): string {
this.seenTypes.push(type);
try {
if (!type.symbol) {
// This comes up when generating code for an arrow function as passed
// to a generic function. The passed-in type is tagged as anonymous
// and has no properties so it's hard to figure out what to generate.
// Just avoid it for now so we don't crash.
this.warn('anonymous type has no symbol');
return '?';
}
if (type.symbol.flags & ts.SymbolFlags.Function ||
type.symbol.flags & ts.SymbolFlags.Method) {
const sigs =
this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (sigs.length === 1) {
return this.signatureToClosure(sigs[0]);
}
this.warn('unhandled anonymous type with multiple call signatures');
return '?';
}
// Gather up all the named fields and whether the object is also callable.
let callable = false;
let indexable = false;
const fields: string[] = [];
if (!type.symbol.members) {
this.warn('anonymous type has no symbol');
return '?';
}
// special-case construct signatures.
const ctors = type.getConstructSignatures();
if (ctors.length) {
// TODO(martinprobst): this does not support additional properties
// defined on constructors (not expressible in Closure), nor multiple
// constructors (same).
const decl = ctors[0].declaration;
if (!decl) {
this.warn(
'unhandled anonymous type with constructor signature but no declaration');
return '?';
}
if (decl.kind === ts.SyntaxKind.JSDocSignature) {
this.warn('unhandled JSDoc based constructor signature');
return '?';
}
// new <T>(tee: T) is not supported by Closure, always set as ?.
this.markTypeParameterAsUnknown(
this.symbolsToAliasedNames, decl.typeParameters);
const params = this.convertParams(ctors[0], decl.parameters);
const paramsStr = params.length ? (', ' + params.join(', ')) : '';
const constructedType = this.translate(ctors[0].getReturnType());
const constructedTypeStr = constructedType[0] === '!' ?
constructedType.substring(1) :
constructedType;
// In the specific case of the "new" in a function, the correct Closure
// type is:
//
// function(new:Bar, ...args)
//
// Including the nullability annotation can cause the Closure compiler
// to no longer recognize the function as a constructor type in externs.
return `function(new:${constructedTypeStr}${paramsStr})`;
}
// members is an ES6 map, but the .d.ts defining it defined their own map
// type, so typescript doesn't believe that .keys() is iterable.
for (const field of (
type.symbol.members.keys() as IterableIterator<ts.__String>)) {
const fieldName = ts.unescapeLeadingUnderscores(field);
switch (field) {
case ts.InternalSymbolName.Call:
callable = true;
break;
case ts.InternalSymbolName.Index:
indexable = true;
break;
default:
if (!isValidClosurePropertyName(fieldName)) {
this.warn(`omitting inexpressible property name: ${field}`);
continue;
}
const member = type.symbol.members.get(field)!;
// optional members are handled by the type including |undefined in
// a union type.
const memberType = this.translate(
this.typeChecker.getTypeOfSymbolAtLocation(member, this.node));
fields.push(`${fieldName}: ${memberType}`);
break;
}
}
// Try to special-case plain key-value objects and functions.
if (fields.length === 0) {
if (callable && !indexable) {
// A function type.
const sigs =
this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (sigs.length === 1) {
return this.signatureToClosure(sigs[0]);
}
} else if (indexable && !callable) {
// A plain key-value map type.
let keyType = 'string';
let valType =
this.typeChecker.getIndexTypeOfType(type, ts.IndexKind.String);
if (!valType) {
keyType = 'number';
valType =
this.typeChecker.getIndexTypeOfType(type, ts.IndexKind.Number);
}
if (!valType) {
this.warn('unknown index key type');
return `!Object<?,?>`;
}
return `!Object<${keyType},${this.translate(valType)}>`;
} else if (!callable && !indexable) {
// The object has no members. This is the TS type '{}',
// which means "any value other than null or undefined".
// What is this in Closure's type system?
//
// First, {!Object} is wrong because it is not a supertype of
// {string} or {number}. This would mean you cannot assign a
// number to a variable of TS type {}.
//
// We get closer with {*}, aka the ALL type. This one better
// captures the typical use of the TS {}, which users use for
// "I don't care".
//
// {*} unfortunately does include null/undefined, so it's a closer
// match for TS 3.0's 'unknown'.
return '*';
}
}
if (!callable && !indexable) {
// Not callable, not indexable; implies a plain object with fields in
// it.
return `{${fields.join(', ')}}`;
}
this.warn('unhandled anonymous type');
return '?';
} finally {
this.seenTypes.pop();
}
}