Skip to content

Commit d602415

Browse files
union-proof type calls, fixes awaited use-case from microsoft#17077
1 parent 5decbbc commit d602415

File tree

5 files changed

+417
-295
lines changed

5 files changed

+417
-295
lines changed

src/compiler/checker.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7587,9 +7587,52 @@ namespace ts {
75877587
function getTypeFromTypeCall(type: TypeCallType, node: TypeCallTypeNode): Type {
75887588
const fn = type.function;
75897589
const args = type.arguments;
7590-
const calls = getSignaturesOfType(fn, SignatureKind.Call);
7591-
const sig = resolveCall(node, calls, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, args);
7592-
return sig ? getReturnTypeOfSignature(sig) : unknownType;
7590+
const candidates = getSignaturesOfType(fn, SignatureKind.Call);
7591+
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
7592+
if (isSingleNonGenericCandidate) {
7593+
const sig = resolveCall(node, candidates, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, args);
7594+
return sig ? getReturnTypeOfSignature(sig) : unknownType;
7595+
}
7596+
else {
7597+
// we have unions and they matter -- separately calculate the result for each permutation
7598+
const types: Type[] = [];
7599+
const indices: number[] = map(args, (tp: Type, i: number) => tp.flags & TypeFlags.Union &&
7600+
some(candidates, (sig: Signature) => isGenericObjectType(getTypeAtPosition(sig, i))) ?
7601+
// ^ future consideration: argument/parameter i correspondence breaks down with tuple spreads
7602+
0 : undefined);
7603+
const argTypes: Array<Type[] | Type> = map(args, (tp: Type, i: number) => indices[i] !== undefined ? (<UnionType>tp).types : tp);
7604+
checkParams: while (true) {
7605+
const myArgs = map(argTypes, (tp: Type[] | Type, i: number) => {
7606+
const argIdx = indices[i];
7607+
return argIdx === undefined ? <Type>tp : (<Type[]>tp)[argIdx];
7608+
});
7609+
const sig = resolveCall(node, candidates, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, myArgs);
7610+
if (sig) {
7611+
types.push(getReturnTypeOfSignature(sig));
7612+
}
7613+
// increment the next index to try the next permutation
7614+
for (let i = 0; i < indices.length; i++) {
7615+
const idx = indices[i];
7616+
if (idx === undefined) {
7617+
// nothing to do here, try switching the next argument
7618+
continue;
7619+
}
7620+
else if (idx < (<Type[]>argTypes[i]).length - 1) {
7621+
// can increment without overflow
7622+
indices[i] = indices[i] + 1;
7623+
continue checkParams;
7624+
}
7625+
else {
7626+
// overflow, try switching the next argument as well
7627+
indices[i] = 0;
7628+
continue;
7629+
}
7630+
}
7631+
// all options tried, full overflow throughout for-loop, done
7632+
break;
7633+
}
7634+
return getUnionType(types);
7635+
}
75937636
}
75947637

75957638
function getTypeFromTypeCallNode(node: TypeCallTypeNode): Type {

tests/baselines/reference/typeCall.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const pathTest = path(obj, keys);
9090
// R extends any[],
9191
// I extends string = '0'
9292
// > = { 1: Reduce<Fn(T, R[StringToNumber[I]], I, R), R, Inc[I]>, 0: T }[TupleHasIndex<R, I>];
93-
// // fails with error: Cannot read property 'kind' of undefined at resolveCall
93+
// // fails with error: Cannot read property 'flags' of undefined
9494
// declare function reduce<
9595
// Fn extends (previousValue: any, currentValue: R[number], currentIndex?: number, array?: R) => any,
9696
// R extends any[],
@@ -108,13 +108,13 @@ type Fn2 = <T2>(v2: { [k: string]: T2 }) => ReadonlyArray<T2>;
108108
let fn1 = null! as Fn1;
109109
let fn2 = null! as Fn2;
110110
type Fn3 = <T3 extends number[]>(v3: T3) => Fn2(Fn1(T3));
111+
declare function fn3<Fun1 extends (/*...args: any[]*/ p1: P1) => T, Fun2 extends (v: T) => any, P1, T>(fun1: Fun1, fun2: Fun2): (...args: any[] /* todo: specify*/) => Fn2(Fn1(P1));
111112
let ones = null! as 1[];
112113
type Fn4b = Fn3(typeof ones);
113-
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
114114
type Fn4c = Fn3(1[]);
115-
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
116115
let y = fn2(fn1(ones));
117116
type Y = Fn2(Fn1(1[]));
117+
// let z2 = fn3(fn1, fn2)(ones); // Cannot read property 'parent' of undefined
118118

119119
interface isT<T> {
120120
(v: never): '0';
@@ -129,10 +129,16 @@ let strBool: isBool(string); // 0
129129
let anyBool: isBool(any); // 0
130130
let neverBool: isBool(never); // 0
131131

132-
type Assert<T> = (<U>(v: U | null | undefined) => U)(T);
132+
type Assert<T> = {
133+
(v: null | undefined): never;
134+
<U>(v: U): U;
135+
}(T);
133136
let assert: Assert<string | undefined>; // string
134137

135-
type Minus<A, B> = (<U>(v: U | B) => U)(A);
138+
type Minus<A, B> = {
139+
(v: B): never;
140+
<U>(v: U): U;
141+
}(A);
136142
let noNumbers: Minus<string | number, number>; // string
137143

138144
interface UnwrapPromise {
@@ -270,7 +276,6 @@ var pathTest = path(obj, keys);
270276
var fn1 = null;
271277
var fn2 = null;
272278
var ones = null;
273-
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
274279
var y = fn2(fn1(ones));
275280
var falseBool; // 1
276281
var trueBool; // 1

0 commit comments

Comments
 (0)