Skip to content

Commit

Permalink
union-proof type calls, fixes awaited use-case from microsoft#17077
Browse files Browse the repository at this point in the history
  • Loading branch information
KiaraGrouwstra committed Sep 11, 2017
1 parent 5decbbc commit d602415
Show file tree
Hide file tree
Showing 5 changed files with 417 additions and 295 deletions.
49 changes: 46 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7587,9 +7587,52 @@ namespace ts {
function getTypeFromTypeCall(type: TypeCallType, node: TypeCallTypeNode): Type {
const fn = type.function;
const args = type.arguments;
const calls = getSignaturesOfType(fn, SignatureKind.Call);
const sig = resolveCall(node, calls, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, args);
return sig ? getReturnTypeOfSignature(sig) : unknownType;
const candidates = getSignaturesOfType(fn, SignatureKind.Call);
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
if (isSingleNonGenericCandidate) {
const sig = resolveCall(node, candidates, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, args);
return sig ? getReturnTypeOfSignature(sig) : unknownType;
}
else {
// we have unions and they matter -- separately calculate the result for each permutation
const types: Type[] = [];
const indices: number[] = map(args, (tp: Type, i: number) => tp.flags & TypeFlags.Union &&
some(candidates, (sig: Signature) => isGenericObjectType(getTypeAtPosition(sig, i))) ?
// ^ future consideration: argument/parameter i correspondence breaks down with tuple spreads
0 : undefined);
const argTypes: Array<Type[] | Type> = map(args, (tp: Type, i: number) => indices[i] !== undefined ? (<UnionType>tp).types : tp);
checkParams: while (true) {
const myArgs = map(argTypes, (tp: Type[] | Type, i: number) => {
const argIdx = indices[i];
return argIdx === undefined ? <Type>tp : (<Type[]>tp)[argIdx];
});
const sig = resolveCall(node, candidates, /*candidatesOutArray*/ [], /*fallBackError*/ undefined, myArgs);
if (sig) {
types.push(getReturnTypeOfSignature(sig));
}
// increment the next index to try the next permutation
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
if (idx === undefined) {
// nothing to do here, try switching the next argument
continue;
}
else if (idx < (<Type[]>argTypes[i]).length - 1) {
// can increment without overflow
indices[i] = indices[i] + 1;
continue checkParams;
}
else {
// overflow, try switching the next argument as well
indices[i] = 0;
continue;
}
}
// all options tried, full overflow throughout for-loop, done
break;
}
return getUnionType(types);
}
}

function getTypeFromTypeCallNode(node: TypeCallTypeNode): Type {
Expand Down
17 changes: 11 additions & 6 deletions tests/baselines/reference/typeCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const pathTest = path(obj, keys);
// R extends any[],
// I extends string = '0'
// > = { 1: Reduce<Fn(T, R[StringToNumber[I]], I, R), R, Inc[I]>, 0: T }[TupleHasIndex<R, I>];
// // fails with error: Cannot read property 'kind' of undefined at resolveCall
// // fails with error: Cannot read property 'flags' of undefined
// declare function reduce<
// Fn extends (previousValue: any, currentValue: R[number], currentIndex?: number, array?: R) => any,
// R extends any[],
Expand All @@ -108,13 +108,13 @@ type Fn2 = <T2>(v2: { [k: string]: T2 }) => ReadonlyArray<T2>;
let fn1 = null! as Fn1;
let fn2 = null! as Fn2;
type Fn3 = <T3 extends number[]>(v3: T3) => Fn2(Fn1(T3));
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));
let ones = null! as 1[];
type Fn4b = Fn3(typeof ones);
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
type Fn4c = Fn3(1[]);
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
let y = fn2(fn1(ones));
type Y = Fn2(Fn1(1[]));
// let z2 = fn3(fn1, fn2)(ones); // Cannot read property 'parent' of undefined

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

type Assert<T> = (<U>(v: U | null | undefined) => U)(T);
type Assert<T> = {
(v: null | undefined): never;
<U>(v: U): U;
}(T);
let assert: Assert<string | undefined>; // string

type Minus<A, B> = (<U>(v: U | B) => U)(A);
type Minus<A, B> = {
(v: B): never;
<U>(v: U): U;
}(A);
let noNumbers: Minus<string | number, number>; // string

interface UnwrapPromise {
Expand Down Expand Up @@ -270,7 +276,6 @@ var pathTest = path(obj, keys);
var fn1 = null;
var fn2 = null;
var ones = null;
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
var y = fn2(fn1(ones));
var falseBool; // 1
var trueBool; // 1
Expand Down
Loading

0 comments on commit d602415

Please sign in to comment.