Skip to content

Commit

Permalink
Fix mixin logic to preserve at least one constructor type even when the
Browse files Browse the repository at this point in the history
intersection also contains non-constructor types.

Fixes microsoft#17388.
  • Loading branch information
mattmccutchen committed Feb 9, 2019
1 parent bbf559b commit a724427
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 8 deletions.
27 changes: 19 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6987,13 +6987,23 @@ namespace ts {
getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly);
}

function includeMixinType(type: Type, types: ReadonlyArray<Type>, index: number): Type {
function findMixins(types: ReadonlyArray<Type>): ReadonlyArray<boolean> {
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<Type>, mixinFlags: ReadonlyArray<boolean>, 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]));
}
}
Expand All @@ -7008,20 +7018,21 @@ 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
// those types are discarded and their return types are mixed into the return types of all
// 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;
});
}
Expand Down Expand Up @@ -20901,12 +20912,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;
Expand All @@ -20916,6 +20926,7 @@ namespace ts {
}
}
}
i++;
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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)

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Repro for #17388

declare let x: {foo: undefined} & {new(...args: any[]): any};
new x();

0 comments on commit a724427

Please sign in to comment.