Skip to content

Commit

Permalink
Fix mixin logic to preserve at least one constructor type even when t…
Browse files Browse the repository at this point in the history
…he (#27701)

intersection also contains non-constructor types.

Fixes #17388.
  • Loading branch information
mattmccutchen authored and weswigham committed Mar 12, 2019
1 parent 6c6f216 commit ab81536
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 @@ -7026,13 +7026,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 @@ -7047,20 +7057,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 @@ -21104,12 +21115,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 @@ -21119,6 +21129,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 ab81536

Please sign in to comment.