Allow identical type parameter lists to merge in union signatures (#31023)

* Have signature identity checks look at the base constraint of type parameters, allow identical type parameter lists to merge in union signatures

* Update text in fourslash test

* Add whitespace to fix lint, remove duplicate impl

* Consolidate names

* Remove comparisons of type parameter defaults, add more test cases
This commit is contained in:
Wesley Wigham 2020-12-16 13:17:57 -08:00 committed by GitHub
parent 675cd4d7ce
commit b217f22e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 765 additions and 11 deletions

View File

@ -10274,7 +10274,7 @@ namespace ts {
if (signatures !== masterList) {
const signature = signatures[0];
Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass");
results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature));
results = signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters!, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature));
if (!results) {
break;
}
@ -10285,18 +10285,39 @@ namespace ts {
return result || emptyArray;
}
function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined {
function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[], targetParams: readonly TypeParameter[]): boolean {
if (sourceParams.length !== targetParams.length) {
return false;
}
const mapper = createTypeMapper(targetParams, sourceParams);
for (let i = 0; i < sourceParams.length; i++) {
const source = sourceParams[i];
const target = targetParams[i];
if (source === target) continue;
// We instantiate the target type parameter constraints into the source types so we can recognize `<T, U extends T>` as the same as `<A, B extends A>`
if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false;
// We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match.
// It might make sense to combine these defaults in the future, but doing so intelligently requires knowing
// if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type)
// and, since it's just an inference _default_, just picking one arbitrarily works OK.
}
return true;
}
function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined {
if (!left || !right) {
return left || right;
}
// A signature `this` type might be a read or a write position... It's very possible that it should be invariant
// and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
// permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures.
const thisType = getIntersectionType([getTypeOfSymbol(left), getTypeOfSymbol(right)]);
const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]);
return createSymbolWithType(left, thisType);
}
function combineUnionParameters(left: Signature, right: Signature) {
function combineUnionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) {
const leftCount = getParameterCount(left);
const rightCount = getParameterCount(right);
const longest = leftCount >= rightCount ? left : right;
@ -10306,8 +10327,14 @@ namespace ts {
const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest);
const params = new Array<Symbol>(longestCount + (needsExtraRestElement ? 1 : 0));
for (let i = 0; i < longestCount; i++) {
const longestParamType = tryGetTypeAtPosition(longest, i)!;
const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
let longestParamType = tryGetTypeAtPosition(longest, i)!;
if (longest === right) {
longestParamType = instantiateType(longestParamType, mapper);
}
let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
if (shorter === right) {
shorterParamType = instantiateType(shorterParamType, mapper);
}
const unionParamType = getIntersectionType([longestParamType, shorterParamType]);
const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1);
const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter);
@ -10328,19 +10355,28 @@ namespace ts {
if (needsExtraRestElement) {
const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String);
restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount));
if (shorter === right) {
restParamSymbol.type = instantiateType(restParamSymbol.type, mapper);
}
params[longestCount] = restParamSymbol;
}
return params;
}
function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature {
const typeParams = left.typeParameters || right.typeParameters;
let paramMapper: TypeMapper | undefined;
if (left.typeParameters && right.typeParameters) {
paramMapper = createTypeMapper(right.typeParameters, left.typeParameters);
// We just use the type parameter defaults from the first signature
}
const declaration = left.declaration;
const params = combineUnionParameters(left, right);
const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter);
const params = combineUnionParameters(left, right, paramMapper);
const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter, paramMapper);
const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
const result = createSignature(
declaration,
left.typeParameters || right.typeParameters,
typeParams,
thisParam,
params,
/*resolvedReturnType*/ undefined,
@ -10349,6 +10385,9 @@ namespace ts {
(left.flags | right.flags) & SignatureFlags.PropagatingFlags
);
result.unionSignatures = concatenate(left.unionSignatures || [left], [right]);
if (paramMapper) {
result.mapper = left.mapper && left.unionSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper;
}
return result;
}
@ -11882,7 +11921,7 @@ namespace ts {
return errorType;
}
let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) :
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
signature.unionSignatures ? instantiateType(getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype), signature.mapper) :
getReturnTypeFromAnnotation(signature.declaration!) ||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
if (signature.flags & SignatureFlags.IsInnerCallChain) {

View File

@ -0,0 +1,85 @@
tests/cases/compiler/unionOfClassCalls.ts(28,5): error TS2349: This expression is not callable.
Each member of the union type '{ (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }' has signatures, but none of those signatures are compatible with each other.
==== tests/cases/compiler/unionOfClassCalls.ts (1 errors) ====
// from https://github.com/microsoft/TypeScript/issues/30717
declare class Test<T> {
obj: T;
get<K extends keyof T>(k: K): T[K];
}
interface A { t: "A" }
interface B { t: "B" }
declare const tmp: Test<A> | Test<B>;
switch (tmp.get('t')) {
case 'A': break;
case 'B': break;
}
// from https://github.com/microsoft/TypeScript/issues/36390
const arr: number[] | string[] = []; // Works with Array<number | string>
const arr1: number[] = [];
const arr2: string[] = [];
arr.map((a: number | string, index: number) => {
return index
})
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
~~~~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Each member of the union type '{ (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }' has signatures, but none of those signatures are compatible with each other.
return []
}, [])
arr.forEach((a: number | string, index: number) => {
return index
})
arr1.map((a: number, index: number) => {
return index
})
arr1.reduce((acc: number[], a: number, index: number) => {
return [a]
}, [])
arr1.forEach((a: number, index: number) => {
return index
})
arr2.map((a: string, index: number) => {
return index
})
arr2.reduce((acc: string[], a: string, index: number) => {
return []
}, [])
arr2.forEach((a: string, index: number) => {
return index
})
// from https://github.com/microsoft/TypeScript/issues/36307
declare class Foo {
doThing(): Promise<this>
}
declare class Bar extends Foo {
bar: number;
}
declare class Baz extends Foo {
baz: number;
}
declare var a: Bar | Baz;
// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
// whatever
});

View File

@ -0,0 +1,121 @@
//// [unionOfClassCalls.ts]
// from https://github.com/microsoft/TypeScript/issues/30717
declare class Test<T> {
obj: T;
get<K extends keyof T>(k: K): T[K];
}
interface A { t: "A" }
interface B { t: "B" }
declare const tmp: Test<A> | Test<B>;
switch (tmp.get('t')) {
case 'A': break;
case 'B': break;
}
// from https://github.com/microsoft/TypeScript/issues/36390
const arr: number[] | string[] = []; // Works with Array<number | string>
const arr1: number[] = [];
const arr2: string[] = [];
arr.map((a: number | string, index: number) => {
return index
})
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
return []
}, [])
arr.forEach((a: number | string, index: number) => {
return index
})
arr1.map((a: number, index: number) => {
return index
})
arr1.reduce((acc: number[], a: number, index: number) => {
return [a]
}, [])
arr1.forEach((a: number, index: number) => {
return index
})
arr2.map((a: string, index: number) => {
return index
})
arr2.reduce((acc: string[], a: string, index: number) => {
return []
}, [])
arr2.forEach((a: string, index: number) => {
return index
})
// from https://github.com/microsoft/TypeScript/issues/36307
declare class Foo {
doThing(): Promise<this>
}
declare class Bar extends Foo {
bar: number;
}
declare class Baz extends Foo {
baz: number;
}
declare var a: Bar | Baz;
// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
// whatever
});
//// [unionOfClassCalls.js]
"use strict";
switch (tmp.get('t')) {
case 'A': break;
case 'B': break;
}
// from https://github.com/microsoft/TypeScript/issues/36390
var arr = []; // Works with Array<number | string>
var arr1 = [];
var arr2 = [];
arr.map(function (a, index) {
return index;
});
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce(function (acc, a, index) {
return [];
}, []);
arr.forEach(function (a, index) {
return index;
});
arr1.map(function (a, index) {
return index;
});
arr1.reduce(function (acc, a, index) {
return [a];
}, []);
arr1.forEach(function (a, index) {
return index;
});
arr2.map(function (a, index) {
return index;
});
arr2.reduce(function (acc, a, index) {
return [];
}, []);
arr2.forEach(function (a, index) {
return index;
});
// note, you must annotate `result` for now
a.doThing().then(function (result) {
// whatever
});

View File

@ -0,0 +1,207 @@
=== tests/cases/compiler/unionOfClassCalls.ts ===
// from https://github.com/microsoft/TypeScript/issues/30717
declare class Test<T> {
>Test : Symbol(Test, Decl(unionOfClassCalls.ts, 0, 0))
>T : Symbol(T, Decl(unionOfClassCalls.ts, 1, 19))
obj: T;
>obj : Symbol(Test.obj, Decl(unionOfClassCalls.ts, 1, 23))
>T : Symbol(T, Decl(unionOfClassCalls.ts, 1, 19))
get<K extends keyof T>(k: K): T[K];
>get : Symbol(Test.get, Decl(unionOfClassCalls.ts, 2, 11))
>K : Symbol(K, Decl(unionOfClassCalls.ts, 3, 8))
>T : Symbol(T, Decl(unionOfClassCalls.ts, 1, 19))
>k : Symbol(k, Decl(unionOfClassCalls.ts, 3, 27))
>K : Symbol(K, Decl(unionOfClassCalls.ts, 3, 8))
>T : Symbol(T, Decl(unionOfClassCalls.ts, 1, 19))
>K : Symbol(K, Decl(unionOfClassCalls.ts, 3, 8))
}
interface A { t: "A" }
>A : Symbol(A, Decl(unionOfClassCalls.ts, 4, 1))
>t : Symbol(A.t, Decl(unionOfClassCalls.ts, 6, 13))
interface B { t: "B" }
>B : Symbol(B, Decl(unionOfClassCalls.ts, 6, 22))
>t : Symbol(B.t, Decl(unionOfClassCalls.ts, 7, 13))
declare const tmp: Test<A> | Test<B>;
>tmp : Symbol(tmp, Decl(unionOfClassCalls.ts, 9, 13))
>Test : Symbol(Test, Decl(unionOfClassCalls.ts, 0, 0))
>A : Symbol(A, Decl(unionOfClassCalls.ts, 4, 1))
>Test : Symbol(Test, Decl(unionOfClassCalls.ts, 0, 0))
>B : Symbol(B, Decl(unionOfClassCalls.ts, 6, 22))
switch (tmp.get('t')) {
>tmp.get : Symbol(Test.get, Decl(unionOfClassCalls.ts, 2, 11), Decl(unionOfClassCalls.ts, 2, 11))
>tmp : Symbol(tmp, Decl(unionOfClassCalls.ts, 9, 13))
>get : Symbol(Test.get, Decl(unionOfClassCalls.ts, 2, 11), Decl(unionOfClassCalls.ts, 2, 11))
case 'A': break;
case 'B': break;
}
// from https://github.com/microsoft/TypeScript/issues/36390
const arr: number[] | string[] = []; // Works with Array<number | string>
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
const arr1: number[] = [];
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
const arr2: string[] = [];
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
arr.map((a: number | string, index: number) => {
>arr.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 22, 9))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 22, 28))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 22, 28))
})
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
>arr.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 27, 12))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 27, 31))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 27, 51))
return []
}, [])
arr.forEach((a: number | string, index: number) => {
>arr.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 31, 13))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 31, 32))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 31, 32))
})
arr1.map((a: number, index: number) => {
>arr1.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 35, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 35, 20))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 35, 20))
})
arr1.reduce((acc: number[], a: number, index: number) => {
>arr1.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 39, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 39, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 39, 38))
return [a]
>a : Symbol(a, Decl(unionOfClassCalls.ts, 39, 27))
}, [])
arr1.forEach((a: number, index: number) => {
>arr1.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 43, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 43, 24))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 43, 24))
})
arr2.map((a: string, index: number) => {
>arr2.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 46, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 46, 21))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 46, 21))
})
arr2.reduce((acc: string[], a: string, index: number) => {
>arr2.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 50, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 50, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 50, 38))
return []
}, [])
arr2.forEach((a: string, index: number) => {
>arr2.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 54, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 54, 24))
return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 54, 24))
})
// from https://github.com/microsoft/TypeScript/issues/36307
declare class Foo {
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
doThing(): Promise<this>
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
}
declare class Bar extends Foo {
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
bar: number;
>bar : Symbol(Bar.bar, Decl(unionOfClassCalls.ts, 64, 31))
}
declare class Baz extends Foo {
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
baz: number;
>baz : Symbol(Baz.baz, Decl(unionOfClassCalls.ts, 67, 31))
}
declare var a: Bar | Baz;
>a : Symbol(a, Decl(unionOfClassCalls.ts, 71, 11))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
>a.doThing().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a.doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19), Decl(unionOfClassCalls.ts, 60, 19))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 71, 11))
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19), Decl(unionOfClassCalls.ts, 60, 19))
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>result : Symbol(result, Decl(unionOfClassCalls.ts, 73, 18))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
// whatever
});

View File

@ -0,0 +1,225 @@
=== tests/cases/compiler/unionOfClassCalls.ts ===
// from https://github.com/microsoft/TypeScript/issues/30717
declare class Test<T> {
>Test : Test<T>
obj: T;
>obj : T
get<K extends keyof T>(k: K): T[K];
>get : <K extends keyof T>(k: K) => T[K]
>k : K
}
interface A { t: "A" }
>t : "A"
interface B { t: "B" }
>t : "B"
declare const tmp: Test<A> | Test<B>;
>tmp : Test<A> | Test<B>
switch (tmp.get('t')) {
>tmp.get('t') : "A" | "B"
>tmp.get : (<K extends "t">(k: K) => A[K]) | (<K extends "t">(k: K) => B[K])
>tmp : Test<A> | Test<B>
>get : (<K extends "t">(k: K) => A[K]) | (<K extends "t">(k: K) => B[K])
>'t' : "t"
case 'A': break;
>'A' : "A"
case 'B': break;
>'B' : "B"
}
// from https://github.com/microsoft/TypeScript/issues/36390
const arr: number[] | string[] = []; // Works with Array<number | string>
>arr : number[] | string[]
>[] : never[]
const arr1: number[] = [];
>arr1 : number[]
>[] : never[]
const arr2: string[] = [];
>arr2 : string[]
>[] : never[]
arr.map((a: number | string, index: number) => {
>arr.map((a: number | string, index: number) => { return index}) : number[]
>arr.map : (<U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])
>arr : number[] | string[]
>map : (<U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])
>(a: number | string, index: number) => { return index} : (a: number | string, index: number) => number
>a : string | number
>index : number
return index
>index : number
})
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
>arr.reduce((acc: Array<string>, a: number | string, index: number) => { return []}, []) : any
>arr.reduce : { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }
>arr : number[] | string[]
>reduce : { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }
>(acc: Array<string>, a: number | string, index: number) => { return []} : (acc: Array<string>, a: number | string, index: number) => never[]
>acc : string[]
>a : string | number
>index : number
return []
>[] : never[]
}, [])
>[] : never[]
arr.forEach((a: number | string, index: number) => {
>arr.forEach((a: number | string, index: number) => { return index}) : void
>arr.forEach : ((callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void) | ((callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void)
>arr : number[] | string[]
>forEach : ((callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void) | ((callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void)
>(a: number | string, index: number) => { return index} : (a: number | string, index: number) => number
>a : string | number
>index : number
return index
>index : number
})
arr1.map((a: number, index: number) => {
>arr1.map((a: number, index: number) => { return index}) : number[]
>arr1.map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>arr1 : number[]
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>(a: number, index: number) => { return index} : (a: number, index: number) => number
>a : number
>index : number
return index
>index : number
})
arr1.reduce((acc: number[], a: number, index: number) => {
>arr1.reduce((acc: number[], a: number, index: number) => { return [a]}, []) : number[]
>arr1.reduce : { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }
>arr1 : number[]
>reduce : { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }
>(acc: number[], a: number, index: number) => { return [a]} : (acc: number[], a: number, index: number) => number[]
>acc : number[]
>a : number
>index : number
return [a]
>[a] : number[]
>a : number
}, [])
>[] : never[]
arr1.forEach((a: number, index: number) => {
>arr1.forEach((a: number, index: number) => { return index}) : void
>arr1.forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void
>arr1 : number[]
>forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void
>(a: number, index: number) => { return index} : (a: number, index: number) => number
>a : number
>index : number
return index
>index : number
})
arr2.map((a: string, index: number) => {
>arr2.map((a: string, index: number) => { return index}) : number[]
>arr2.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>arr2 : string[]
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>(a: string, index: number) => { return index} : (a: string, index: number) => number
>a : string
>index : number
return index
>index : number
})
arr2.reduce((acc: string[], a: string, index: number) => {
>arr2.reduce((acc: string[], a: string, index: number) => { return []}, []) : never[]
>arr2.reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }
>arr2 : string[]
>reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U>(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; }
>(acc: string[], a: string, index: number) => { return []} : (acc: string[], a: string, index: number) => never[]
>acc : string[]
>a : string
>index : number
return []
>[] : never[]
}, [])
>[] : never[]
arr2.forEach((a: string, index: number) => {
>arr2.forEach((a: string, index: number) => { return index}) : void
>arr2.forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void
>arr2 : string[]
>forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void
>(a: string, index: number) => { return index} : (a: string, index: number) => number
>a : string
>index : number
return index
>index : number
})
// from https://github.com/microsoft/TypeScript/issues/36307
declare class Foo {
>Foo : Foo
doThing(): Promise<this>
>doThing : () => Promise<this>
}
declare class Bar extends Foo {
>Bar : Bar
>Foo : Foo
bar: number;
>bar : number
}
declare class Baz extends Foo {
>Baz : Baz
>Foo : Foo
baz: number;
>baz : number
}
declare var a: Bar | Baz;
>a : Bar | Baz
// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
>a.doThing().then((result: Bar | Baz) => { // whatever}) : Promise<void>
>a.doThing().then : (<TResult1 = Bar, TResult2 = never>(onfulfilled?: ((value: Bar) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>) | (<TResult1 = Baz, TResult2 = never>(onfulfilled?: ((value: Baz) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>)
>a.doThing() : Promise<Bar> | Promise<Baz>
>a.doThing : (() => Promise<Bar>) | (() => Promise<Baz>)
>a : Bar | Baz
>doThing : (() => Promise<Bar>) | (() => Promise<Baz>)
>then : (<TResult1 = Bar, TResult2 = never>(onfulfilled?: ((value: Bar) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>) | (<TResult1 = Baz, TResult2 = never>(onfulfilled?: ((value: Baz) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>)
>(result: Bar | Baz) => { // whatever} : (result: Bar | Baz) => void
>result : Bar | Baz
// whatever
});

View File

@ -0,0 +1,77 @@
// @strict: true
// from https://github.com/microsoft/TypeScript/issues/30717
declare class Test<T> {
obj: T;
get<K extends keyof T>(k: K): T[K];
}
interface A { t: "A" }
interface B { t: "B" }
declare const tmp: Test<A> | Test<B>;
switch (tmp.get('t')) {
case 'A': break;
case 'B': break;
}
// from https://github.com/microsoft/TypeScript/issues/36390
const arr: number[] | string[] = []; // Works with Array<number | string>
const arr1: number[] = [];
const arr2: string[] = [];
arr.map((a: number | string, index: number) => {
return index
})
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
return []
}, [])
arr.forEach((a: number | string, index: number) => {
return index
})
arr1.map((a: number, index: number) => {
return index
})
arr1.reduce((acc: number[], a: number, index: number) => {
return [a]
}, [])
arr1.forEach((a: number, index: number) => {
return index
})
arr2.map((a: string, index: number) => {
return index
})
arr2.reduce((acc: string[], a: string, index: number) => {
return []
}, [])
arr2.forEach((a: string, index: number) => {
return index
})
// from https://github.com/microsoft/TypeScript/issues/36307
declare class Foo {
doThing(): Promise<this>
}
declare class Bar extends Foo {
bar: number;
}
declare class Baz extends Foo {
baz: number;
}
declare var a: Bar | Baz;
// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
// whatever
});

View File

@ -3,7 +3,7 @@
////var y: Array<string>|Array<number>;
////y.map/**/(
const text = "(property) Array<T>.map: (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[])";
const text = "(method) Array<T>.map<unknown>(callbackfn: ((value: string, index: number, array: string[]) => unknown) & ((value: number, index: number, array: number[]) => unknown), thisArg: any): unknown[]";
const documentation = "Calls a defined callback function on each element of an array, and returns an array that contains the results.";
verify.quickInfoAt("", text, documentation);