Skip to content

Commit eb7ff43

Browse files
authored
Merge pull request microsoft#25408 from Microsoft/contextualTupleTypes
Infer tuple types for contextually typed rest parameters
2 parents 064ecd4 + 17e9594 commit eb7ff43

13 files changed

+1125
-104
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12467,22 +12467,7 @@ namespace ts {
1246712467
callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i));
1246812468
}
1246912469
if (targetRestTypeVariable) {
12470-
const sourceRestTypeVariable = getRestTypeParameter(source);
12471-
if (sourceRestTypeVariable && paramCount === sourceCount - 1) {
12472-
callback(sourceRestTypeVariable, targetRestTypeVariable);
12473-
}
12474-
else {
12475-
const types = [];
12476-
const names = [];
12477-
for (let i = paramCount; i < maxCount; i++) {
12478-
types.push(getTypeAtPosition(source, i));
12479-
names.push(getParameterNameAtPosition(source, i));
12480-
}
12481-
const minArgumentCount = getMinArgumentCount(source);
12482-
const minLength = minArgumentCount < paramCount ? 0 : minArgumentCount - paramCount;
12483-
const rest = createTupleType(types, minLength, sourceHasRest, names);
12484-
callback(rest, targetRestTypeVariable);
12485-
}
12470+
callback(getRestTypeAtPosition(source, paramCount), targetRestTypeVariable);
1248612471
}
1248712472
}
1248812473

@@ -15284,43 +15269,35 @@ namespace ts {
1528415269
}
1528515270
const iife = getImmediatelyInvokedFunctionExpression(func);
1528615271
if (iife && iife.arguments) {
15272+
const args = getEffectiveCallArguments(iife)!;
1528715273
const indexOfParameter = func.parameters.indexOf(parameter);
1528815274
if (parameter.dotDotDotToken) {
15289-
const restTypes: Type[] = [];
15290-
for (let i = indexOfParameter; i < iife.arguments.length; i++) {
15291-
restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i])));
15292-
}
15293-
return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined;
15275+
return getSpreadArgumentType(iife, args, indexOfParameter, args.length, anyType, /*context*/ undefined);
1529415276
}
1529515277
const links = getNodeLinks(iife);
1529615278
const cached = links.resolvedSignature;
1529715279
links.resolvedSignature = anySignature;
15298-
const type = indexOfParameter < iife.arguments.length ?
15299-
getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) :
15280+
const type = indexOfParameter < args.length ?
15281+
getWidenedLiteralType(checkExpression(args[indexOfParameter])) :
1530015282
parameter.initializer ? undefined : undefinedWideningType;
1530115283
links.resolvedSignature = cached;
1530215284
return type;
1530315285
}
1530415286
const contextualSignature = getContextualSignature(func);
1530515287
if (contextualSignature) {
15306-
const funcHasRestParameters = hasRestParameter(func);
15307-
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
15288+
const funcHasRestParameter = hasRestParameter(func);
15289+
const len = func.parameters.length - (funcHasRestParameter ? 1 : 0);
1530815290
let indexOfParameter = func.parameters.indexOf(parameter);
1530915291
if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) {
1531015292
Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`.
1531115293
indexOfParameter -= 1;
1531215294
}
15313-
1531415295
if (indexOfParameter < len) {
1531515296
return getTypeAtPosition(contextualSignature, indexOfParameter);
1531615297
}
15317-
1531815298
// If last parameter is contextually rest parameter get its type
15319-
if (funcHasRestParameters &&
15320-
indexOfParameter === func.parameters.length - 1 &&
15321-
hasEffectiveRestParameter(contextualSignature) &&
15322-
func.parameters.length >= contextualSignature.parameters.length) {
15323-
return getTypeOfRestParameter(contextualSignature);
15299+
if (funcHasRestParameter && indexOfParameter === len) {
15300+
return getRestTypeAtPosition(contextualSignature, indexOfParameter);
1532415301
}
1532515302
}
1532615303
}
@@ -17895,7 +17872,7 @@ namespace ts {
1789517872
}
1789617873
}
1789717874

17898-
function isSpreadArgument(arg: Expression | undefined) {
17875+
function isSpreadArgument(arg: Expression | undefined): arg is Expression {
1789917876
return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (<SyntheticExpression>arg).isSpread);
1790017877
}
1790117878

@@ -18117,12 +18094,14 @@ namespace ts {
1811718094
}
1811818095

1811918096
function getSpreadArgumentType(node: CallLikeExpression, args: ReadonlyArray<Expression>, index: number, argCount: number, restType: TypeParameter, context: InferenceContext | undefined) {
18120-
if (index === argCount - 1) {
18121-
const arg = getEffectiveArgument(node, args, index);
18097+
if (index >= argCount - 1) {
18098+
const arg = getEffectiveArgument(node, args, argCount - 1);
1812218099
if (isSpreadArgument(arg)) {
1812318100
// We are inferring from a spread expression in the last argument position, i.e. both the parameter
1812418101
// and the argument are ...x forms.
18125-
return checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context);
18102+
return arg.kind === SyntaxKind.SyntheticExpression ?
18103+
createArrayType((<SyntheticExpression>arg).type) :
18104+
checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context);
1812618105
}
1812718106
}
1812818107
const contextualType = getIndexTypeOfType(restType, IndexKind.Number) || anyType;
@@ -19651,6 +19630,27 @@ namespace ts {
1965119630
return anyType;
1965219631
}
1965319632

19633+
function getRestTypeAtPosition(source: Signature, pos: number): Type {
19634+
const paramCount = getParameterCount(source);
19635+
const hasRest = hasEffectiveRestParameter(source);
19636+
if (hasRest && pos === paramCount - 1) {
19637+
const restTypeVariable = getRestTypeParameter(source);
19638+
if (restTypeVariable) {
19639+
return restTypeVariable;
19640+
}
19641+
}
19642+
const start = hasRest ? Math.min(pos, paramCount - 1) : pos;
19643+
const types = [];
19644+
const names = [];
19645+
for (let i = start; i < paramCount; i++) {
19646+
types.push(getTypeAtPosition(source, i));
19647+
names.push(getParameterNameAtPosition(source, i));
19648+
}
19649+
const minArgumentCount = getMinArgumentCount(source);
19650+
const minLength = minArgumentCount < start ? 0 : minArgumentCount - start;
19651+
return createTupleType(types, minLength, hasRest, names);
19652+
}
19653+
1965419654
function getTypeOfRestParameter(signature: Signature) {
1965519655
if (signature.hasRestParameter) {
1965619656
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
@@ -19744,11 +19744,11 @@ namespace ts {
1974419744
assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType);
1974519745
}
1974619746
}
19747-
if (signature.hasRestParameter && context.hasRestParameter && signature.parameters.length >= context.parameters.length) {
19747+
if (signature.hasRestParameter) {
1974819748
// parameter might be a transient symbol generated by use of `arguments` in the function body.
1974919749
const parameter = last(signature.parameters);
1975019750
if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(<ParameterDeclaration>parameter.valueDeclaration)) {
19751-
const contextualParameterType = getTypeOfSymbol(last(context.parameters));
19751+
const contextualParameterType = getRestTypeAtPosition(context, len);
1975219752
assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType);
1975319753
}
1975419754
}

tests/baselines/reference/contextuallyTypedIife.types

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@
101101
// rest parameters
102102
((...numbers) => numbers.every(n => n > 0))(5,6,7);
103103
>((...numbers) => numbers.every(n => n > 0))(5,6,7) : boolean
104-
>((...numbers) => numbers.every(n => n > 0)) : (...numbers: number[]) => boolean
105-
>(...numbers) => numbers.every(n => n > 0) : (...numbers: number[]) => boolean
106-
>numbers : number[]
104+
>((...numbers) => numbers.every(n => n > 0)) : (numbers_0: number, numbers_1: number, numbers_2: number) => boolean
105+
>(...numbers) => numbers.every(n => n > 0) : (numbers_0: number, numbers_1: number, numbers_2: number) => boolean
106+
>numbers : [number, number, number]
107107
>numbers.every(n => n > 0) : boolean
108108
>numbers.every : (callbackfn: (value: number, index: number, array: number[]) => boolean, thisArg?: any) => boolean
109-
>numbers : number[]
109+
>numbers : [number, number, number]
110110
>every : (callbackfn: (value: number, index: number, array: number[]) => boolean, thisArg?: any) => boolean
111111
>n => n > 0 : (n: number) => boolean
112112
>n : number
@@ -119,12 +119,12 @@
119119

120120
((...mixed) => mixed.every(n => !!n))(5,'oops','oh no');
121121
>((...mixed) => mixed.every(n => !!n))(5,'oops','oh no') : boolean
122-
>((...mixed) => mixed.every(n => !!n)) : (...mixed: (string | number)[]) => boolean
123-
>(...mixed) => mixed.every(n => !!n) : (...mixed: (string | number)[]) => boolean
124-
>mixed : (string | number)[]
122+
>((...mixed) => mixed.every(n => !!n)) : (mixed_0: number, mixed_1: string, mixed_2: string) => boolean
123+
>(...mixed) => mixed.every(n => !!n) : (mixed_0: number, mixed_1: string, mixed_2: string) => boolean
124+
>mixed : [number, string, string]
125125
>mixed.every(n => !!n) : boolean
126126
>mixed.every : (callbackfn: (value: string | number, index: number, array: (string | number)[]) => boolean, thisArg?: any) => boolean
127-
>mixed : (string | number)[]
127+
>mixed : [number, string, string]
128128
>every : (callbackfn: (value: string | number, index: number, array: (string | number)[]) => boolean, thisArg?: any) => boolean
129129
>n => !!n : (n: string | number) => boolean
130130
>n : string | number
@@ -137,31 +137,31 @@
137137

138138
((...noNumbers) => noNumbers.some(n => n > 0))();
139139
>((...noNumbers) => noNumbers.some(n => n > 0))() : boolean
140-
>((...noNumbers) => noNumbers.some(n => n > 0)) : (...noNumbers: any[]) => boolean
141-
>(...noNumbers) => noNumbers.some(n => n > 0) : (...noNumbers: any[]) => boolean
142-
>noNumbers : any[]
140+
>((...noNumbers) => noNumbers.some(n => n > 0)) : () => boolean
141+
>(...noNumbers) => noNumbers.some(n => n > 0) : () => boolean
142+
>noNumbers : []
143143
>noNumbers.some(n => n > 0) : boolean
144-
>noNumbers.some : (callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean
145-
>noNumbers : any[]
146-
>some : (callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean
147-
>n => n > 0 : (n: any) => boolean
148-
>n : any
144+
>noNumbers.some : (callbackfn: (value: never, index: number, array: never[]) => boolean, thisArg?: any) => boolean
145+
>noNumbers : []
146+
>some : (callbackfn: (value: never, index: number, array: never[]) => boolean, thisArg?: any) => boolean
147+
>n => n > 0 : (n: never) => boolean
148+
>n : never
149149
>n > 0 : boolean
150-
>n : any
150+
>n : never
151151
>0 : 0
152152

153153
((first, ...rest) => first ? [] : rest.map(n => n > 0))(8,9,10);
154154
>((first, ...rest) => first ? [] : rest.map(n => n > 0))(8,9,10) : boolean[]
155-
>((first, ...rest) => first ? [] : rest.map(n => n > 0)) : (first: number, ...rest: number[]) => boolean[]
156-
>(first, ...rest) => first ? [] : rest.map(n => n > 0) : (first: number, ...rest: number[]) => boolean[]
155+
>((first, ...rest) => first ? [] : rest.map(n => n > 0)) : (first: number, rest_0: number, rest_1: number) => boolean[]
156+
>(first, ...rest) => first ? [] : rest.map(n => n > 0) : (first: number, rest_0: number, rest_1: number) => boolean[]
157157
>first : number
158-
>rest : number[]
158+
>rest : [number, number]
159159
>first ? [] : rest.map(n => n > 0) : boolean[]
160160
>first : number
161161
>[] : undefined[]
162162
>rest.map(n => n > 0) : boolean[]
163163
>rest.map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
164-
>rest : number[]
164+
>rest : [number, number]
165165
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
166166
>n => n > 0 : (n: number) => boolean
167167
>n : number

tests/baselines/reference/contextuallyTypedIifeStrict.types

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@
101101
// rest parameters
102102
((...numbers) => numbers.every(n => n > 0))(5,6,7);
103103
>((...numbers) => numbers.every(n => n > 0))(5,6,7) : boolean
104-
>((...numbers) => numbers.every(n => n > 0)) : (...numbers: number[]) => boolean
105-
>(...numbers) => numbers.every(n => n > 0) : (...numbers: number[]) => boolean
106-
>numbers : number[]
104+
>((...numbers) => numbers.every(n => n > 0)) : (numbers_0: number, numbers_1: number, numbers_2: number) => boolean
105+
>(...numbers) => numbers.every(n => n > 0) : (numbers_0: number, numbers_1: number, numbers_2: number) => boolean
106+
>numbers : [number, number, number]
107107
>numbers.every(n => n > 0) : boolean
108108
>numbers.every : (callbackfn: (value: number, index: number, array: number[]) => boolean, thisArg?: any) => boolean
109-
>numbers : number[]
109+
>numbers : [number, number, number]
110110
>every : (callbackfn: (value: number, index: number, array: number[]) => boolean, thisArg?: any) => boolean
111111
>n => n > 0 : (n: number) => boolean
112112
>n : number
@@ -119,12 +119,12 @@
119119

120120
((...mixed) => mixed.every(n => !!n))(5,'oops','oh no');
121121
>((...mixed) => mixed.every(n => !!n))(5,'oops','oh no') : boolean
122-
>((...mixed) => mixed.every(n => !!n)) : (...mixed: (string | number)[]) => boolean
123-
>(...mixed) => mixed.every(n => !!n) : (...mixed: (string | number)[]) => boolean
124-
>mixed : (string | number)[]
122+
>((...mixed) => mixed.every(n => !!n)) : (mixed_0: number, mixed_1: string, mixed_2: string) => boolean
123+
>(...mixed) => mixed.every(n => !!n) : (mixed_0: number, mixed_1: string, mixed_2: string) => boolean
124+
>mixed : [number, string, string]
125125
>mixed.every(n => !!n) : boolean
126126
>mixed.every : (callbackfn: (value: string | number, index: number, array: (string | number)[]) => boolean, thisArg?: any) => boolean
127-
>mixed : (string | number)[]
127+
>mixed : [number, string, string]
128128
>every : (callbackfn: (value: string | number, index: number, array: (string | number)[]) => boolean, thisArg?: any) => boolean
129129
>n => !!n : (n: string | number) => boolean
130130
>n : string | number
@@ -137,31 +137,31 @@
137137

138138
((...noNumbers) => noNumbers.some(n => n > 0))();
139139
>((...noNumbers) => noNumbers.some(n => n > 0))() : boolean
140-
>((...noNumbers) => noNumbers.some(n => n > 0)) : (...noNumbers: any[]) => boolean
141-
>(...noNumbers) => noNumbers.some(n => n > 0) : (...noNumbers: any[]) => boolean
142-
>noNumbers : any[]
140+
>((...noNumbers) => noNumbers.some(n => n > 0)) : () => boolean
141+
>(...noNumbers) => noNumbers.some(n => n > 0) : () => boolean
142+
>noNumbers : []
143143
>noNumbers.some(n => n > 0) : boolean
144-
>noNumbers.some : (callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean
145-
>noNumbers : any[]
146-
>some : (callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean
147-
>n => n > 0 : (n: any) => boolean
148-
>n : any
144+
>noNumbers.some : (callbackfn: (value: never, index: number, array: never[]) => boolean, thisArg?: any) => boolean
145+
>noNumbers : []
146+
>some : (callbackfn: (value: never, index: number, array: never[]) => boolean, thisArg?: any) => boolean
147+
>n => n > 0 : (n: never) => boolean
148+
>n : never
149149
>n > 0 : boolean
150-
>n : any
150+
>n : never
151151
>0 : 0
152152

153153
((first, ...rest) => first ? [] : rest.map(n => n > 0))(8,9,10);
154154
>((first, ...rest) => first ? [] : rest.map(n => n > 0))(8,9,10) : boolean[]
155-
>((first, ...rest) => first ? [] : rest.map(n => n > 0)) : (first: number, ...rest: number[]) => boolean[]
156-
>(first, ...rest) => first ? [] : rest.map(n => n > 0) : (first: number, ...rest: number[]) => boolean[]
155+
>((first, ...rest) => first ? [] : rest.map(n => n > 0)) : (first: number, rest_0: number, rest_1: number) => boolean[]
156+
>(first, ...rest) => first ? [] : rest.map(n => n > 0) : (first: number, rest_0: number, rest_1: number) => boolean[]
157157
>first : number
158-
>rest : number[]
158+
>rest : [number, number]
159159
>first ? [] : rest.map(n => n > 0) : boolean[]
160160
>first : number
161161
>[] : never[]
162162
>rest.map(n => n > 0) : boolean[]
163163
>rest.map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
164-
>rest : number[]
164+
>rest : [number, number]
165165
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
166166
>n => n > 0 : (n: number) => boolean
167167
>n : number

tests/baselines/reference/emitDefaultParametersFunctionExpression.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,22 @@ var x = function (str = "hello", ...rest) { }
3737
var y = (function (num = 10, boo = false, ...rest) { })()
3838
>y : void
3939
>(function (num = 10, boo = false, ...rest) { })() : void
40-
>(function (num = 10, boo = false, ...rest) { }) : (num?: number, boo?: boolean, ...rest: any[]) => void
41-
>function (num = 10, boo = false, ...rest) { } : (num?: number, boo?: boolean, ...rest: any[]) => void
40+
>(function (num = 10, boo = false, ...rest) { }) : (num?: number, boo?: boolean) => void
41+
>function (num = 10, boo = false, ...rest) { } : (num?: number, boo?: boolean) => void
4242
>num : number
4343
>10 : 10
4444
>boo : boolean
4545
>false : false
46-
>rest : any[]
46+
>rest : []
4747

4848
var z = (function (num: number, boo = false, ...rest) { })(10)
4949
>z : void
5050
>(function (num: number, boo = false, ...rest) { })(10) : void
51-
>(function (num: number, boo = false, ...rest) { }) : (num: number, boo?: boolean, ...rest: any[]) => void
52-
>function (num: number, boo = false, ...rest) { } : (num: number, boo?: boolean, ...rest: any[]) => void
51+
>(function (num: number, boo = false, ...rest) { }) : (num: number, boo?: boolean) => void
52+
>function (num: number, boo = false, ...rest) { } : (num: number, boo?: boolean) => void
5353
>num : number
5454
>boo : boolean
5555
>false : false
56-
>rest : any[]
56+
>rest : []
5757
>10 : 10
5858

tests/baselines/reference/emitDefaultParametersFunctionExpressionES6.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,22 @@ var x = function (str = "hello", ...rest) { }
3737
var y = (function (num = 10, boo = false, ...rest) { })()
3838
>y : void
3939
>(function (num = 10, boo = false, ...rest) { })() : void
40-
>(function (num = 10, boo = false, ...rest) { }) : (num?: number, boo?: boolean, ...rest: any[]) => void
41-
>function (num = 10, boo = false, ...rest) { } : (num?: number, boo?: boolean, ...rest: any[]) => void
40+
>(function (num = 10, boo = false, ...rest) { }) : (num?: number, boo?: boolean) => void
41+
>function (num = 10, boo = false, ...rest) { } : (num?: number, boo?: boolean) => void
4242
>num : number
4343
>10 : 10
4444
>boo : boolean
4545
>false : false
46-
>rest : any[]
46+
>rest : []
4747

4848
var z = (function (num: number, boo = false, ...rest) { })(10)
4949
>z : void
5050
>(function (num: number, boo = false, ...rest) { })(10) : void
51-
>(function (num: number, boo = false, ...rest) { }) : (num: number, boo?: boolean, ...rest: any[]) => void
52-
>function (num: number, boo = false, ...rest) { } : (num: number, boo?: boolean, ...rest: any[]) => void
51+
>(function (num: number, boo = false, ...rest) { }) : (num: number, boo?: boolean) => void
52+
>function (num: number, boo = false, ...rest) { } : (num: number, boo?: boolean) => void
5353
>num : number
5454
>boo : boolean
5555
>false : false
56-
>rest : any[]
56+
>rest : []
5757
>10 : 10
5858

tests/baselines/reference/emitRestParametersFunctionExpression.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var funcExp2 = function (...rest) { }
1818
var funcExp3 = (function (...rest) { })()
1919
>funcExp3 : void
2020
>(function (...rest) { })() : void
21-
>(function (...rest) { }) : (...rest: any[]) => void
22-
>function (...rest) { } : (...rest: any[]) => void
23-
>rest : any[]
21+
>(function (...rest) { }) : () => void
22+
>function (...rest) { } : () => void
23+
>rest : []
2424

tests/baselines/reference/emitRestParametersFunctionExpressionES6.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var funcExp2 = function (...rest) { }
1818
var funcExp3 = (function (...rest) { })()
1919
>funcExp3 : void
2020
>(function (...rest) { })() : void
21-
>(function (...rest) { }) : (...rest: any[]) => void
22-
>function (...rest) { } : (...rest: any[]) => void
23-
>rest : any[]
21+
>(function (...rest) { }) : () => void
22+
>function (...rest) { } : () => void
23+
>rest : []
2424

0 commit comments

Comments
 (0)