Skip to content

Compiler runs out of memory in classes with lots of static, generic functions #7097

Closed
@sandersn

Description

@sandersn

This repro is based on ListWrapper from Angular 2, a class that contains nothing but static functions. Most of them are generic.

When I test the upcoming strictThis flag, ListWrapper causes the compiler to run out of memory. The code below is equivalent to making all the this arguments explicit as dit: typeof ListWrapper. Nobody would write code like this today, but with strictThis, static functions all get an extra argument like this: typeof ListWrapper by default.

The reason for the failure is that when inferring type arguments for a call, such as ListWrapper.clone, its signature is first retrieved:

let clone = ListWrapper.clone(ListWrapper, [1,2,3,4]);

Which gives <T>(dit: typeof ListWrapper, array: T[]): T[]. Then the signature is instantiated with the inferred type parameters: (dit: typeof ListWrapper, array: number[]): number[].

Unfortunately, this instantiation causes typeof ListWrapper to be instantiated as well since it's an anonymous type. This creates a new type whose target is the original but is otherwise identical. Now, checking that the arguments (ListWrapper, [1,2,3,4]) are applicable to this instantiated signature will check that the argument typeof ListWrapper (un-instantiated) is applicable to the parameter typeof ListWrapper (instantiated). This checks spirals out of control because the types are not identical, so all properties need to be checked. But when any generic method is checked, the same instantiated-type mismatch happens again and the process recurs until the compiler runs out of memory.

class ListWrapper {
  // JS has no way to express a statically fixed size list, but dart does so we
  // keep both methods.
  static createFixedSize(dit: typeof ListWrapper, size: number): any[] { return new Array(size); }
  static createGrowableSize(dit: typeof ListWrapper, size: number): any[] { return new Array(size); }
  static clone<T>(dit: typeof ListWrapper, array: T[]): T[] { return array.slice(0); }
  static forEachWithIndex<T>(dit: typeof ListWrapper, array: T[], fn: (t: T, n: number) => void) {
    for (var i = 0; i < array.length; i++) {
      fn(array[i], i);
    }
  }
  static first<T>(dit: typeof ListWrapper, array: T[]): T {
    if (!array) return null;
    return array[0];
  }
  static last<T>(dit: typeof ListWrapper, array: T[]): T {
    if (!array || array.length == 0) return null;
    return array[array.length - 1];
  }
  static indexOf<T>(dit: typeof ListWrapper, array: T[], value: T, startIndex: number = 0): number {
    return array.indexOf(value, startIndex);
  }
  static contains<T>(dit: typeof ListWrapper, list: T[], el: T): boolean { return list.indexOf(el) !== -1; }
  static reversed<T>(dit: typeof ListWrapper, array: T[]): T[] {
    var a = ListWrapper.clone(dit, array);
    return a.reverse();
  }
  static concat(dit: typeof ListWrapper, a: any[], b: any[]): any[] { return a.concat(b); }
  static insert<T>(dit: typeof ListWrapper, list: T[], index: number, value: T) { list.splice(index, 0, value); }
  static removeAt<T>(dit: typeof ListWrapper, list: T[], index: number): T {
    var res = list[index];
    list.splice(index, 1);
    return res;
  }
  static removeAll<T>(dit: typeof ListWrapper, list: T[], items: T[]) {
    for (var i = 0; i < items.length; ++i) {
      var index = list.indexOf(items[i]);
      list.splice(index, 1);
    }
  }
  static remove<T>(dit: typeof ListWrapper, list: T[], el: T): boolean {
    var index = list.indexOf(el);
    if (index > -1) {
      list.splice(index, 1);
      return true;
    }
    return false;
  }
  static clear(dit: typeof ListWrapper, list: any[]) { list.length = 0; }
  static isEmpty(dit: typeof ListWrapper, list: any[]): boolean { return list.length == 0; }
  static fill(dit: typeof ListWrapper, list: any[], value: any, start: number = 0, end: number = null) {
    list.fill(value, start, end === null ? list.length : end);
  }
  static equals(dit: typeof ListWrapper, a: any[], b: any[]): boolean {
    if (a.length != b.length) return false;
    for (var i = 0; i < a.length; ++i) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }
  static slice<T>(dit: typeof ListWrapper, l: T[], from: number = 0, to: number = null): T[] {
    return l.slice(from, to === null ? undefined : to);
  }
  static splice<T>(dit: typeof ListWrapper, l: T[], from: number, length: number): T[] { return l.splice(from, length); }
  static sort<T>(dit: typeof ListWrapper, l: T[], compareFn?: (a: T, b: T) => number) {
    if (isPresent(compareFn)) {
      l.sort(compareFn);
    } else {
      l.sort();
    }
  }
  static toString<T>(dit: typeof ListWrapper, l: T[]): string { return l.toString(); }
  static toJSON<T>(dit: typeof ListWrapper, l: T[]): string { return JSON.stringify(l); }

  static maximum<T>(dit: typeof ListWrapper, list: T[], predicate: (t: T) => number): T {
    if (list.length == 0) {
      return null;
    }
    var solution: T = null;
    var maxValue = -Infinity;
    for (var index = 0; index < list.length; index++) {
      var candidate = list[index];
      if (isBlank(candidate)) {
        continue;
      }
      var candidateValue = predicate(candidate);
      if (candidateValue > maxValue) {
        solution = candidate;
        maxValue = candidateValue;
      }
    }
    return solution;
  }
}
let clone = ListWrapper.clone([1,2,3,4,5]);
declare function isBlank(x: any): boolean;
declare function isPresent<T>(compareFn?: (a: T, b: T) => number): boolean;
interface Array<T> {
    fill(value: any, start: number, end: number): void;
}

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFixedA PR has been merged for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions