diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 62756388dc409..8a70c2a3c0f70 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6639,13 +6639,23 @@ namespace ts { getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly); } - function includeMixinType(type: Type, types: ReadonlyArray, index: number): Type { + function findMixins(types: ReadonlyArray): ReadonlyArray { + const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); + const mixinFlags = map(types, isMixinConstructorType); + if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { + const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); + mixinFlags[firstMixinIndex] = false; + } + return mixinFlags; + } + + function includeMixinType(type: Type, types: ReadonlyArray, mixinFlags: ReadonlyArray, index: number): Type { const mixedTypes: Type[] = []; for (let i = 0; i < types.length; i++) { if (i === index) { mixedTypes.push(type); } - else if (isMixinConstructorType(types[i])) { + else if (mixinFlags[i]) { mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); } } @@ -6660,7 +6670,8 @@ namespace ts { let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; const types = type.types; - const mixinCount = countWhere(types, isMixinConstructorType); + const mixinFlags = findMixins(types); + const mixinCount = countWhere(mixinFlags, (b) => b); for (let i = 0; i < types.length; i++) { const t = type.types[i]; // When an intersection type contains mixin constructor types, the construct signatures from @@ -6668,12 +6679,12 @@ namespace ts { // other construct signatures in the intersection type. For example, the intersection type // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature // 'new(s: string) => A & B'. - if (mixinCount === 0 || mixinCount === types.length && i === 0 || !isMixinConstructorType(t)) { + if (!mixinFlags[i]) { let signatures = getSignaturesOfType(t, SignatureKind.Construct); if (signatures.length && mixinCount > 0) { signatures = map(signatures, s => { const clone = cloneSignature(s); - clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, i); + clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); return clone; }); } @@ -20169,12 +20180,11 @@ namespace ts { const firstBase = baseTypes[0]; if (firstBase.flags & TypeFlags.Intersection) { const types = (firstBase as IntersectionType).types; - const mixinCount = countWhere(types, isMixinConstructorType); + const mixinFlags = findMixins(types); let i = 0; for (const intersectionMember of (firstBase as IntersectionType).types) { - i++; // We want to ignore mixin ctors - if (mixinCount === 0 || mixinCount === types.length && i === 0 || !isMixinConstructorType(intersectionMember)) { + if (!mixinFlags[i]) { if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { if (intersectionMember.symbol === target) { return true; @@ -20184,6 +20194,7 @@ namespace ts { } } } + i++; } return false; } diff --git a/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.js b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.js new file mode 100644 index 0000000000000..ecef6cca636d6 --- /dev/null +++ b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.js @@ -0,0 +1,10 @@ +//// [intersectionOfMixinConstructorTypeAndNonConstructorType.ts] +// Repro for #17388 + +declare let x: {foo: undefined} & {new(...args: any[]): any}; +new x(); + + +//// [intersectionOfMixinConstructorTypeAndNonConstructorType.js] +// Repro for #17388 +new x(); diff --git a/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.symbols b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.symbols new file mode 100644 index 0000000000000..76e088c710e99 --- /dev/null +++ b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.symbols @@ -0,0 +1,11 @@ +=== tests/cases/compiler/intersectionOfMixinConstructorTypeAndNonConstructorType.ts === +// Repro for #17388 + +declare let x: {foo: undefined} & {new(...args: any[]): any}; +>x : Symbol(x, Decl(intersectionOfMixinConstructorTypeAndNonConstructorType.ts, 2, 11)) +>foo : Symbol(foo, Decl(intersectionOfMixinConstructorTypeAndNonConstructorType.ts, 2, 16)) +>args : Symbol(args, Decl(intersectionOfMixinConstructorTypeAndNonConstructorType.ts, 2, 39)) + +new x(); +>x : Symbol(x, Decl(intersectionOfMixinConstructorTypeAndNonConstructorType.ts, 2, 11)) + diff --git a/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.types b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.types new file mode 100644 index 0000000000000..c2972cfe52955 --- /dev/null +++ b/tests/baselines/reference/intersectionOfMixinConstructorTypeAndNonConstructorType.types @@ -0,0 +1,12 @@ +=== tests/cases/compiler/intersectionOfMixinConstructorTypeAndNonConstructorType.ts === +// Repro for #17388 + +declare let x: {foo: undefined} & {new(...args: any[]): any}; +>x : { foo: undefined; } & (new (...args: any[]) => any) +>foo : undefined +>args : any[] + +new x(); +>new x() : any +>x : { foo: undefined; } & (new (...args: any[]) => any) + diff --git a/tests/cases/compiler/intersectionOfMixinConstructorTypeAndNonConstructorType.ts b/tests/cases/compiler/intersectionOfMixinConstructorTypeAndNonConstructorType.ts new file mode 100644 index 0000000000000..7f043b957c0c4 --- /dev/null +++ b/tests/cases/compiler/intersectionOfMixinConstructorTypeAndNonConstructorType.ts @@ -0,0 +1,4 @@ +// Repro for #17388 + +declare let x: {foo: undefined} & {new(...args: any[]): any}; +new x();