@@ -421,16 +421,42 @@ export class TypeTable {
421421 return id ;
422422 }
423423
424+ /**
425+ * Caches the result of `getId`: `type -> [id (not unfolded), id (unfolded)]`.
426+ *
427+ * A value of `undefined` means the value is not yet computed,
428+ * and `number | null` corresponds to the return value of `getId`.
429+ */
430+ private idCache = new WeakMap < ts . Type , [ number | null | undefined , number | null | undefined ] > ( ) ;
431+
424432 /**
425433 * Gets the canonical ID for the given type, generating a fresh ID if necessary.
426434 *
427435 * Returns `null` if we do not support extraction of this type.
428436 */
429437 public getId ( type : ts . Type , unfoldAlias : boolean ) : number | null {
438+ let cached = this . idCache . get ( type ) ?? [ undefined , undefined ] ;
439+ let cachedValue = cached [ unfoldAlias ? 1 : 0 ] ;
440+ if ( cachedValue !== undefined ) return cachedValue ;
441+
442+ let result = this . getIdRaw ( type , unfoldAlias ) ;
443+ cached [ unfoldAlias ? 1 : 0 ] = result ;
444+ return result ;
445+ }
446+
447+ /**
448+ * Gets the canonical ID for the given type, generating a fresh ID if necessary.
449+ *
450+ * Returns `null` if we do not support extraction of this type.
451+ */
452+ public getIdRaw ( type : ts . Type , unfoldAlias : boolean ) : number | null {
430453 if ( this . typeRecursionDepth > 100 ) {
431454 // Ignore infinitely nested anonymous types, such as `{x: {x: {x: ... }}}`.
432455 // Such a type can't be written directly with TypeScript syntax (as it would need to be named),
433456 // but it can occur rarely as a result of type inference.
457+
458+ // Caching this value is technically incorrect, as a type might be seen at depth 101 and then we cache the fact that it can't be extracted.
459+ // Then later the type is seen at a lower depth and could be extracted, but then we immediately give up because of the cached failure.
434460 return null ;
435461 }
436462 // Replace very long string literal types with `string`.
0 commit comments