// @target: es6 // 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: NAME; record: RECORD; identifier: IDENTIFIER; storedAs: PubSubRecordIsStoredInRedisAsA; maxMsToWaitBeforePublishing: number; } type NameFieldConstructor = SO_FAR extends {name: any} ? {} : { name: (t?: TYPE) => BuildPubSubRecordType } const buildNameFieldConstructor = (soFar: SO_FAR) => ( "name" in soFar ? {} : { name: (instance: TYPE = undefined) => buildPubSubRecordType(Object.assign({}, soFar, {name: instance as TYPE}) as SO_FAR & {name: TYPE}) as BuildPubSubRecordType } ); type StoredAsConstructor = SO_FAR extends {storedAs: any} ? {} : { storedAsJsonEncodedRedisString: () => BuildPubSubRecordType; storedRedisHash: () => BuildPubSubRecordType; } const buildStoredAsConstructor = (soFar: SO_FAR) => ( "storedAs" in soFar ? {} : { storedAsJsonEncodedRedisString: () => buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as BuildPubSubRecordType, storedAsRedisHash: () => buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as BuildPubSubRecordType, } ); type IdentifierFieldConstructor = SO_FAR extends {identifier: any} ? {} : SO_FAR extends {record: any} ? { identifier: >(t?: TYPE) => BuildPubSubRecordType } : {} const buildIdentifierFieldConstructor = (soFar: SO_FAR) => ( "identifier" in soFar || (!("record" in soFar)) ? {} : { identifier: (instance: TYPE = undefined) => buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) as BuildPubSubRecordType } ); type RecordFieldConstructor = SO_FAR extends {record: any} ? {} : { record: (t?: TYPE) => BuildPubSubRecordType } const buildRecordFieldConstructor = (soFar: SO_FAR) => ( "record" in soFar ? {} : { record: (instance: TYPE = undefined) => buildPubSubRecordType(Object.assign({}, soFar, {record: instance as TYPE}) as SO_FAR & {record: TYPE}) as BuildPubSubRecordType } ); type MaxMsToWaitBeforePublishingFieldConstructor = SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : { maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType, neverDelayPublishing: () => BuildPubSubRecordType, } const buildMaxMsToWaitBeforePublishingFieldConstructor = (soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor => ( "maxMsToWaitBeforePublishing" in soFar ? {} : { maxMsToWaitBeforePublishing: (instance: number = 0) => buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: instance})) as BuildPubSubRecordType, neverDelayPublishing: () => buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType, } ) as MaxMsToWaitBeforePublishingFieldConstructor; type TypeConstructor = SO_FAR extends {identifier: any, record: any, maxMsToWaitBeforePublishing: number, storedAs: PubSubRecordIsStoredInRedisAsA} ? { type: SO_FAR, fields: Set, hasField: (fieldName: string | number | symbol) => fieldName is keyof SO_FAR } : {} const buildType = (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 = NameFieldConstructor & IdentifierFieldConstructor & RecordFieldConstructor & StoredAsConstructor & // infinite loop goes away when you comment out this line MaxMsToWaitBeforePublishingFieldConstructor & TypeConstructor const buildPubSubRecordType = (soFar: SO_FAR) => Object.assign( {}, buildNameFieldConstructor(soFar), buildIdentifierFieldConstructor(soFar), buildRecordFieldConstructor(soFar), buildStoredAsConstructor(soFar), buildMaxMsToWaitBeforePublishingFieldConstructor(soFar), buildType(soFar) ) as BuildPubSubRecordType; const PubSubRecordType = buildPubSubRecordType({});