diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7f31e3aa57f93..772fdf9f0bb44 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10275,7 +10275,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; } @@ -10286,18 +10286,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 `` as the same as `` + 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; @@ -10307,8 +10328,14 @@ namespace ts { const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); const params = new Array(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); @@ -10329,19 +10356,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, @@ -10350,6 +10386,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; } @@ -11896,7 +11935,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((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); if (signature.flags & SignatureFlags.IsInnerCallChain) { diff --git a/tests/baselines/reference/unionOfClassCalls.errors.txt b/tests/baselines/reference/unionOfClassCalls.errors.txt new file mode 100644 index 0000000000000..386d34414bbf0 --- /dev/null +++ b/tests/baselines/reference/unionOfClassCalls.errors.txt @@ -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; (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; (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 { + obj: T; + get(k: K): T[K]; + } + + interface A { t: "A" } + interface B { t: "B" } + + declare const tmp: Test | Test; + + 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 + 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, 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; (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; (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 + } + + 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 + }); + \ No newline at end of file diff --git a/tests/baselines/reference/unionOfClassCalls.js b/tests/baselines/reference/unionOfClassCalls.js new file mode 100644 index 0000000000000..706af1b83990b --- /dev/null +++ b/tests/baselines/reference/unionOfClassCalls.js @@ -0,0 +1,121 @@ +//// [unionOfClassCalls.ts] +// from https://github.com/microsoft/TypeScript/issues/30717 +declare class Test { + obj: T; + get(k: K): T[K]; +} + +interface A { t: "A" } +interface B { t: "B" } + +declare const tmp: Test | Test; + +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 +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, 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 +} + +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 +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 +}); diff --git a/tests/baselines/reference/unionOfClassCalls.symbols b/tests/baselines/reference/unionOfClassCalls.symbols new file mode 100644 index 0000000000000..03155a66c5a10 --- /dev/null +++ b/tests/baselines/reference/unionOfClassCalls.symbols @@ -0,0 +1,207 @@ +=== tests/cases/compiler/unionOfClassCalls.ts === +// from https://github.com/microsoft/TypeScript/issues/30717 +declare class Test { +>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: 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 | Test; +>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 +>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, 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 +>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 +}); + diff --git a/tests/baselines/reference/unionOfClassCalls.types b/tests/baselines/reference/unionOfClassCalls.types new file mode 100644 index 0000000000000..043ed9826f6bd --- /dev/null +++ b/tests/baselines/reference/unionOfClassCalls.types @@ -0,0 +1,225 @@ +=== tests/cases/compiler/unionOfClassCalls.ts === +// from https://github.com/microsoft/TypeScript/issues/30717 +declare class Test { +>Test : Test + + obj: T; +>obj : T + + get(k: K): T[K]; +>get : (k: K) => T[K] +>k : K +} + +interface A { t: "A" } +>t : "A" + +interface B { t: "B" } +>t : "B" + +declare const tmp: Test | Test; +>tmp : Test | Test + +switch (tmp.get('t')) { +>tmp.get('t') : "A" | "B" +>tmp.get : ((k: K) => A[K]) | ((k: K) => B[K]) +>tmp : Test | Test +>get : ((k: K) => A[K]) | ((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 +>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 : ((callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]) | ((callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) +>arr : number[] | string[] +>map : ((callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => 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, a: number | string, index: number) => { +>arr.reduce((acc: Array, 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; (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; (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; (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; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +>(acc: Array, a: number | string, index: number) => { return []} : (acc: Array, 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 : (callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[] +>arr1 : number[] +>map : (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; (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; (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 : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>arr2 : string[] +>map : (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; (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; (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 +>doThing : () => Promise +} + +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 +>a.doThing().then : ((onfulfilled?: ((value: Bar) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise) | ((onfulfilled?: ((value: Baz) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise) +>a.doThing() : Promise | Promise +>a.doThing : (() => Promise) | (() => Promise) +>a : Bar | Baz +>doThing : (() => Promise) | (() => Promise) +>then : ((onfulfilled?: ((value: Bar) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise) | ((onfulfilled?: ((value: Baz) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise) +>(result: Bar | Baz) => { // whatever} : (result: Bar | Baz) => void +>result : Bar | Baz + + // whatever +}); + diff --git a/tests/cases/compiler/unionOfClassCalls.ts b/tests/cases/compiler/unionOfClassCalls.ts new file mode 100644 index 0000000000000..509fbb4b46a6c --- /dev/null +++ b/tests/cases/compiler/unionOfClassCalls.ts @@ -0,0 +1,77 @@ +// @strict: true +// from https://github.com/microsoft/TypeScript/issues/30717 +declare class Test { + obj: T; + get(k: K): T[K]; +} + +interface A { t: "A" } +interface B { t: "B" } + +declare const tmp: Test | Test; + +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 +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, 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 +} + +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 +}); diff --git a/tests/cases/fourslash/completionEntryForUnionMethod.ts b/tests/cases/fourslash/completionEntryForUnionMethod.ts index 80fa6d5dd6e6c..b309d453f16e2 100644 --- a/tests/cases/fourslash/completionEntryForUnionMethod.ts +++ b/tests/cases/fourslash/completionEntryForUnionMethod.ts @@ -3,7 +3,7 @@ ////var y: Array|Array; ////y.map/**/( -const text = "(property) Array.map: ((callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | ((callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[])"; +const text = "(method) Array.map(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);