Skip to content

Infinite loop -- narrowed down to single file with no imports #26681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
UppaJung opened this issue Aug 27, 2018 · 2 comments
Closed

Infinite loop -- narrowed down to single file with no imports #26681

UppaJung opened this issue Aug 27, 2018 · 2 comments
Assignees
Labels
Bug A bug in TypeScript

Comments

@UppaJung
Copy link

UppaJung commented Aug 27, 2018

TypeScript Version: [email protected]

I did.

Search Terms: infinite loop

I found one other recent report of an infinite loop, but that reporter wasn't able to isolate the problem. I've got this one nailed to a file with one important and one line that can be commented out to make the infinite loop go away.

Code

// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc --target es6` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.

export enum PubSubRecordIsStoredInRedisAsA {
  redisHash = "redisHash",
  jsonEncodedRedisString = "jsonEncodedRedisString"
}

export interface PubSubRecord<NAME extends string, RECORD, IDENTIFIER extends Partial<RECORD>> {
  name: NAME;
  record: RECORD;
  identifier: IDENTIFIER;
  storedAs: PubSubRecordIsStoredInRedisAsA;
  maxMsToWaitBeforePublishing: number;
}

type NameFieldConstructor<SO_FAR> =
  SO_FAR extends {name: any} ? {} : {
    name: <TYPE>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {name: TYPE}>
  }

const buildNameFieldConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "name" in soFar ? {} : {
    name: <TYPE>(instance: TYPE = undefined) =>
      buildPubSubRecordType(Object.assign({}, soFar, {name: instance as TYPE}) as SO_FAR & {name: TYPE}) as BuildPubSubRecordType<SO_FAR & {name: TYPE}>
  }
);

type StoredAsConstructor<SO_FAR> =
  SO_FAR extends {storedAs: any} ? {} : {
    storedAsJsonEncodedRedisString: () => BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}>;
    storedRedisHash: () => BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}>;
  }

const buildStoredAsConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "storedAs" in soFar ? {} : {
    storedAsJsonEncodedRedisString: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as
        BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString}>,
    storedAsRedisHash: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as
        BuildPubSubRecordType<SO_FAR & {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash}>,
  }
);

type IdentifierFieldConstructor<SO_FAR> =
  SO_FAR extends {identifier: any} ? {} :
  SO_FAR extends {record: any} ? {
    identifier: <TYPE extends Partial<SO_FAR["record"]>>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {identifier: TYPE}>
  } : {}

const buildIdentifierFieldConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "identifier" in soFar || (!("record" in soFar)) ? {} : {
    identifier: <TYPE>(instance: TYPE = undefined) =>
      buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) as BuildPubSubRecordType<SO_FAR & {identifier: TYPE}>
  }
);

type RecordFieldConstructor<SO_FAR> =
  SO_FAR extends {record: any} ? {} : {
    record: <TYPE>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {record: TYPE}>
  }

const buildRecordFieldConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "record" in soFar ? {} : {
    record: <TYPE>(instance: TYPE = undefined) =>
      buildPubSubRecordType(Object.assign({}, soFar, {record: instance as TYPE}) as SO_FAR & {record: TYPE}) as BuildPubSubRecordType<SO_FAR & {record: TYPE}>
  }
);

type MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> =
  SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : {
    maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: number}>,
    neverDelayPublishing: () => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }

const buildMaxMsToWaitBeforePublishingFieldConstructor = <SO_FAR>(soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> => (
  "maxMsToWaitBeforePublishing" in soFar ? {} : {
    maxMsToWaitBeforePublishing: (instance: number = 0) =>
      buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: instance})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: number}>,
    neverDelayPublishing: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }
) as MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR>;

type TypeConstructor<SO_FAR> =
  SO_FAR extends {identifier: any, record: any, maxMsToWaitBeforePublishing: number, storedAs: PubSubRecordIsStoredInRedisAsA} ? {
    type: SO_FAR,
    fields: Set<keyof SO_FAR>,
    hasField: (fieldName: string | number | symbol) => fieldName is keyof SO_FAR
  } : {}

const buildType = <SO_FAR>(soFar: SO_FAR) => (
  "identifier" in soFar && "object" in soFar && "maxMsToWaitBeforePublishing" in soFar && "PubSubRecordIsStoredInRedisAsA" in soFar ? {} : {
    type: soFar,
    fields: () => new Set(Object.keys(soFar) as (keyof SO_FAR)[]),
    hasField: (fieldName: string | number | symbol) => fieldName in soFar
  }
);

type BuildPubSubRecordType<SO_FAR> =
  NameFieldConstructor<SO_FAR> &
  IdentifierFieldConstructor<SO_FAR> &
  RecordFieldConstructor<SO_FAR> &
  StoredAsConstructor<SO_FAR> & // infinite loop goes away when you comment out this line
  MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> &
  TypeConstructor<SO_FAR>

const buildPubSubRecordType = <SO_FAR>(soFar: SO_FAR) => Object.assign(
  {},
  buildNameFieldConstructor(soFar),
  buildIdentifierFieldConstructor(soFar),
  buildRecordFieldConstructor(soFar),
  buildStoredAsConstructor(soFar),
  buildMaxMsToWaitBeforePublishingFieldConstructor(soFar),
  buildType(soFar)
) as BuildPubSubRecordType<SO_FAR>;
const PubSubRecordType = buildPubSubRecordType({});

Expected behavior:
Compiles.

Actual behavior:
The compiler fails to terminate (at least for as long as I was willing to wait.)

Playground Link:

Related Issues:

Possibly #26612

@UppaJung
Copy link
Author

UppaJung commented Aug 27, 2018

Here's a smaller (similar, but different) test case to provide another means of attack.

Note the comment that says "Replace this line with the four below and the infinite loop goes away"

type PubSubRecordIsStoredInRedisAsA = "redisHash" | "jsonEncodedRedisString";

export interface PubSubRecord<NAME extends string, RECORD, IDENTIFIER extends Partial<RECORD>> {
  name: NAME;
  record: RECORD;
  identifier: IDENTIFIER;
  storedAs: PubSubRecordIsStoredInRedisAsA;
  maxMsToWaitBeforePublishing: number;
}

type StoredAsConstructor<SO_FAR> =
  SO_FAR extends {storedAs: PubSubRecordIsStoredInRedisAsA} ? {} : {
    storedAsJsonEncodedRedisString: () => BuildPubSubRecordType<SO_FAR & {storedAs: "jsonEncodedRedisString"}>;
    storedRedisHash: () => BuildPubSubRecordType<SO_FAR & {storedAs: "redisHash"}>;
  }

const buildStoredAsConstructor = <SO_FAR>(
  soFar: SO_FAR
) => (
  "storedAs" in soFar ? {} : {
    storedAsJsonEncodedRedisString: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {storedAs: "jsonEncodedRedisString"})) as
        BuildPubSubRecordType<SO_FAR & {storedAs: "jsonEncodedRedisString"}>,
    storedAsRedisHash: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {storedAs: "redisHash"})) as
        BuildPubSubRecordType<SO_FAR & {storedAs: "redisHash"}>,
  }
); // Replace this line with the four below and the compiler's infinite loop goes away
// ) as SO_FAR extends {storedAs: PubSubRecordIsStoredInRedisAsA} ? {} : {
//     storedAsJsonEncodedRedisString: () => BuildPubSubRecordType<SO_FAR & {storedAs: "jsonEncodedRedisString"}>;
//     storedRedisHash: () => BuildPubSubRecordType<SO_FAR & {storedAs: "redisHash"}>;
// };

type IdentifierFieldConstructor<SO_FAR> =
  SO_FAR extends {identifier: any} ? {} :
  SO_FAR extends {record: any} ? {
    identifier: <TYPE extends Partial<SO_FAR["record"]>>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {identifier: TYPE}>
  } : {}

const buildIdentifierFieldConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "identifier" in soFar || (!("record" in soFar)) ? {} : {
    identifier: <TYPE>(instance: TYPE = undefined) =>
      buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) as BuildPubSubRecordType<SO_FAR & {identifier: TYPE}>
  }
);

type RecordFieldConstructor<SO_FAR> =
  SO_FAR extends {record: any} ? {} : {
    record: <TYPE>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {record: TYPE}>
  }

const buildRecordFieldConstructor = <SO_FAR>(soFar: SO_FAR) => (
  "record" in soFar ? {} : {
    record: <TYPE>(instance: TYPE = undefined) =>
      buildPubSubRecordType(Object.assign({}, soFar, {record: instance as TYPE}) as SO_FAR & {record: TYPE}) as BuildPubSubRecordType<SO_FAR & {record: TYPE}>
  }
);

type MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> =
  SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : {
    maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: number}>,
    neverDelayPublishing: () => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }

const buildMaxMsToWaitBeforePublishingFieldConstructor = <SO_FAR>(soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> => (
  "maxMsToWaitBeforePublishing" in soFar ? {} : {
    maxMsToWaitBeforePublishing: (instance: number = 0) =>
      buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: instance})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: number}>,
    neverDelayPublishing: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }
) as MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR>;

type BuildPubSubRecordType<SO_FAR> =
  IdentifierFieldConstructor<SO_FAR> &
  RecordFieldConstructor<SO_FAR> &
  StoredAsConstructor<SO_FAR> &
  MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR>

const buildPubSubRecordType = <SO_FAR>(soFar: SO_FAR) => Object.assign(
  {},
  buildIdentifierFieldConstructor(soFar),
  buildRecordFieldConstructor(soFar),
  buildStoredAsConstructor(soFar),
  buildMaxMsToWaitBeforePublishingFieldConstructor(soFar),
) as BuildPubSubRecordType<SO_FAR>;
const PubSubRecordType = buildPubSubRecordType({});

@ghost
Copy link

ghost commented Aug 27, 2018

Preliminary work on a smaller repro:

type StoredAsConstructor<SO_FAR> = {
  storedAsJsonEncodedRedisString: () => BuildPubSubRecordType<SO_FAR & {storedAs: "jsonEncodedRedisString"}>;
  storedRedisHash: () => BuildPubSubRecordType<SO_FAR & {storedAs: "redisHash"}>;
}

const buildStoredAsConstructor = <SO_FAR>(soFar: SO_FAR) => ({
  storedAsJsonEncodedRedisString: () => ({}) as BuildPubSubRecordType<SO_FAR & {storedAs: "jsonEncodedRedisString"}>,
});

type IdentifierFieldConstructor<SO_FAR> =
  SO_FAR extends {identifier: any} ? {} :
  SO_FAR extends {record: any} ? {
    identifier: <TYPE extends Partial<SO_FAR["record"]>>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {identifier: TYPE}>
  } : {}

type RecordFieldConstructor<SO_FAR> =
  SO_FAR extends {record: any} ? {}
  : { record: <TYPE>(t?: TYPE) => BuildPubSubRecordType<SO_FAR & {record: TYPE}> }

type MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> =
  SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : {
    maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: number}>,
    neverDelayPublishing: () => BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }

const buildMaxMsToWaitBeforePublishingFieldConstructor = <SO_FAR>(soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR> => (
  {
    neverDelayPublishing: () =>
      buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType<SO_FAR & {maxMsToWaitBeforePublishing: 0}>,
  }
) as MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR>;

type BuildPubSubRecordType<SO_FAR> =
  IdentifierFieldConstructor<SO_FAR> &
  RecordFieldConstructor<SO_FAR> &
  StoredAsConstructor<SO_FAR> &
  MaxMsToWaitBeforePublishingFieldConstructor<SO_FAR>

const buildPubSubRecordType = <SO_FAR>(soFar: SO_FAR) => ({}) as BuildPubSubRecordType<SO_FAR>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants