private translateAnonymousType()

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();
    }
  }