private translateObject()

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 '?';
  }