Skip to content

Commit 200386f

Browse files
committed
Expand the tuple contextual type to the target's shape when it comes from a binding pattern
1 parent 1ed0a5a commit 200386f

11 files changed

+299
-39
lines changed

src/compiler/checker.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15968,6 +15968,46 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1596815968
/*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex));
1596915969
}
1597015970

15971+
function expandTupleShape(source: TupleTypeReference, target: TupleTypeReference): Type {
15972+
const sourceArity = getTypeReferenceArity(source);
15973+
const targetArity = getTypeReferenceArity(target);
15974+
const sourceElementTypes = getTypeArguments(source);
15975+
const targetElementTypes = getTypeArguments(target);
15976+
15977+
let resultTypes: Type[] | undefined;
15978+
let resultFlags: ElementFlags[] | undefined;
15979+
15980+
for (let i = 0; i < targetArity; i++) {
15981+
if (i >= sourceArity) {
15982+
if (!resultTypes) {
15983+
resultTypes = sourceElementTypes.slice();
15984+
resultFlags = source.target.elementFlags.slice();
15985+
}
15986+
const targetFlag = target.target.elementFlags[i];
15987+
resultTypes.push(anyType);
15988+
resultFlags!.push(targetFlag);
15989+
}
15990+
else if (isTupleType(sourceElementTypes[i]) && isTupleType(targetElementTypes[i])) {
15991+
const result = expandTupleShape(sourceElementTypes[i] as TupleTypeReference, targetElementTypes[i] as TupleTypeReference);
15992+
if (result === sourceElementTypes[i]) {
15993+
continue;
15994+
}
15995+
if (!resultTypes) {
15996+
resultTypes = sourceElementTypes.slice(0, i);
15997+
resultFlags = source.target.elementFlags.slice(0, i);
15998+
}
15999+
resultTypes.push(result);
16000+
resultFlags!.push(source.target.elementFlags[i]);
16001+
}
16002+
else if (resultTypes) {
16003+
resultTypes.push(sourceElementTypes[i]);
16004+
resultFlags!.push(source.target.elementFlags[i]);
16005+
}
16006+
};
16007+
16008+
return resultTypes ? createTupleType(resultTypes, resultFlags) : source;
16009+
}
16010+
1597116011
function getKnownKeysOfTupleType(type: TupleTypeReference) {
1597216012
return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)),
1597316013
getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType)));
@@ -31929,7 +31969,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3192931969
// return type of 'wrap'.
3193031970
if (node.kind !== SyntaxKind.Decorator) {
3193131971
const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p));
31932-
const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
31972+
let contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
3193331973
if (contextualType) {
3193431974
const inferenceTargetType = getReturnTypeOfSignature(signature);
3193531975
if (couldContainTypeVariables(inferenceTargetType)) {
@@ -31965,6 +32005,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3196532005
// Inferences made from return types have lower priority than all other inferences.
3196632006
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
3196732007
}
32008+
// if the contextual type is a tuple and it's is coming from a binding pattern
32009+
// then we might need to expand it to match the arity of the target type
32010+
// this allows us to infer tuples even if the binding pattern is shorter than the target type, like here:
32011+
// declare function fn<T>(t: T) => [T, string[]];
32012+
// const [[x]] = fn(['hi']);
32013+
else if (isTupleType(contextualType) && isTupleType(inferenceTargetType)) {
32014+
contextualType = expandTupleShape(contextualType, inferenceTargetType);
32015+
}
3196832016
// Create a type mapper for instantiating generic contextual types using the inferences made
3196932017
// from the return type. We need a separate inference pass here because (a) instantiation of
3197032018
// the source type uses the outer context's return mapper (which excludes inferences made from
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [inferTupleFromBindingPattern.ts]
2+
declare function f<T>(cb: () => T): T;
3+
const [e1, e2, e3] = f(() => [1, "hi", true]);
4+
5+
// repro from #42969
6+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
7+
const [[f2e1]] = f2(['1']);
8+
f2e1.toLowerCase();
9+
10+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
11+
const [[[f3e1]]] = f3(['1']);
12+
f3e1.toLowerCase();
13+
14+
//// [inferTupleFromBindingPattern.js]
15+
"use strict";
16+
var _a = f(function () { return [1, "hi", true]; }), e1 = _a[0], e2 = _a[1], e3 = _a[2];
17+
var f2e1 = f2(['1'])[0][0];
18+
f2e1.toLowerCase();
19+
var f3e1 = f3(['1'])[0][0][0];
20+
f3e1.toLowerCase();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/compiler/inferTupleFromBindingPattern.ts ===
2+
declare function f<T>(cb: () => T): T;
3+
>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0))
4+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
5+
>cb : Symbol(cb, Decl(inferTupleFromBindingPattern.ts, 0, 22))
6+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
7+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
8+
9+
const [e1, e2, e3] = f(() => [1, "hi", true]);
10+
>e1 : Symbol(e1, Decl(inferTupleFromBindingPattern.ts, 1, 7))
11+
>e2 : Symbol(e2, Decl(inferTupleFromBindingPattern.ts, 1, 10))
12+
>e3 : Symbol(e3, Decl(inferTupleFromBindingPattern.ts, 1, 14))
13+
>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0))
14+
15+
// repro from #42969
16+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
17+
>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13))
18+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
19+
>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 4, 39))
20+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
21+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
22+
23+
const [[f2e1]] = f2(['1']);
24+
>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8))
25+
>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13))
26+
27+
f2e1.toLowerCase();
28+
>f2e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
29+
>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8))
30+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
31+
32+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
33+
>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13))
34+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
35+
>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 8, 39))
36+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
37+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
38+
39+
const [[[f3e1]]] = f3(['1']);
40+
>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9))
41+
>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13))
42+
43+
f3e1.toLowerCase();
44+
>f3e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
45+
>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9))
46+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
47+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/inferTupleFromBindingPattern.ts ===
2+
declare function f<T>(cb: () => T): T;
3+
>f : <T>(cb: () => T) => T
4+
>cb : () => T
5+
6+
const [e1, e2, e3] = f(() => [1, "hi", true]);
7+
>e1 : number
8+
>e2 : string
9+
>e3 : boolean
10+
>f(() => [1, "hi", true]) : [number, string, boolean]
11+
>f : <T>(cb: () => T) => T
12+
>() => [1, "hi", true] : () => [number, string, boolean]
13+
>[1, "hi", true] : [number, string, true]
14+
>1 : 1
15+
>"hi" : "hi"
16+
>true : true
17+
18+
// repro from #42969
19+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
20+
>f2 : <T extends string[]>(t: T) => [T, string[]]
21+
>t : T
22+
23+
const [[f2e1]] = f2(['1']);
24+
>f2e1 : string
25+
>f2(['1']) : [[string], string[]]
26+
>f2 : <T extends string[]>(t: T) => [T, string[]]
27+
>['1'] : [string]
28+
>'1' : "1"
29+
30+
f2e1.toLowerCase();
31+
>f2e1.toLowerCase() : string
32+
>f2e1.toLowerCase : () => string
33+
>f2e1 : string
34+
>toLowerCase : () => string
35+
36+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
37+
>f3 : <T extends string[]>(t: T) => [[T, string[]]]
38+
>t : T
39+
40+
const [[[f3e1]]] = f3(['1']);
41+
>f3e1 : string
42+
>f3(['1']) : [[[string], string[]]]
43+
>f3 : <T extends string[]>(t: T) => [[T, string[]]]
44+
>['1'] : [string]
45+
>'1' : "1"
46+
47+
f3e1.toLowerCase();
48+
>f3e1.toLowerCase() : string
49+
>f3e1.toLowerCase : () => string
50+
>f3e1 : string
51+
>toLowerCase : () => string
52+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [inferTupleFromBindingPattern.ts]
2+
declare function f<T>(cb: () => T): T;
3+
const [e1, e2, e3] = f(() => [1, "hi", true]);
4+
5+
// repro from #42969
6+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
7+
const [[f2e1]] = f2(['1']);
8+
f2e1.toLowerCase();
9+
10+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
11+
const [[[f3e1]]] = f3(['1']);
12+
f3e1.toLowerCase();
13+
14+
//// [inferTupleFromBindingPattern.js]
15+
"use strict";
16+
var _a = f(function () { return [1, "hi", true]; }), e1 = _a[0], e2 = _a[1], e3 = _a[2];
17+
var f2e1 = f2(['1'])[0][0];
18+
f2e1.toLowerCase();
19+
var f3e1 = f3(['1'])[0][0][0];
20+
f3e1.toLowerCase();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/compiler/inferTupleFromBindingPattern.ts ===
2+
declare function f<T>(cb: () => T): T;
3+
>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0))
4+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
5+
>cb : Symbol(cb, Decl(inferTupleFromBindingPattern.ts, 0, 22))
6+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
7+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19))
8+
9+
const [e1, e2, e3] = f(() => [1, "hi", true]);
10+
>e1 : Symbol(e1, Decl(inferTupleFromBindingPattern.ts, 1, 7))
11+
>e2 : Symbol(e2, Decl(inferTupleFromBindingPattern.ts, 1, 10))
12+
>e3 : Symbol(e3, Decl(inferTupleFromBindingPattern.ts, 1, 14))
13+
>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0))
14+
15+
// repro from #42969
16+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
17+
>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13))
18+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
19+
>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 4, 39))
20+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
21+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19))
22+
23+
const [[f2e1]] = f2(['1']);
24+
>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8))
25+
>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13))
26+
27+
f2e1.toLowerCase();
28+
>f2e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
29+
>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8))
30+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
31+
32+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
33+
>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13))
34+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
35+
>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 8, 39))
36+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
37+
>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19))
38+
39+
const [[[f3e1]]] = f3(['1']);
40+
>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9))
41+
>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13))
42+
43+
f3e1.toLowerCase();
44+
>f3e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
45+
>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9))
46+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
47+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/inferTupleFromBindingPattern.ts ===
2+
declare function f<T>(cb: () => T): T;
3+
>f : <T>(cb: () => T) => T
4+
>cb : () => T
5+
6+
const [e1, e2, e3] = f(() => [1, "hi", true]);
7+
>e1 : number
8+
>e2 : string
9+
>e3 : boolean
10+
>f(() => [1, "hi", true]) : [number, string, boolean]
11+
>f : <T>(cb: () => T) => T
12+
>() => [1, "hi", true] : () => [number, string, boolean]
13+
>[1, "hi", true] : [number, string, true]
14+
>1 : 1
15+
>"hi" : "hi"
16+
>true : true
17+
18+
// repro from #42969
19+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
20+
>f2 : <T extends string[]>(t: T) => [T, string[]]
21+
>t : T
22+
23+
const [[f2e1]] = f2(['1']);
24+
>f2e1 : string
25+
>f2(['1']) : [[string], string[]]
26+
>f2 : <T extends string[]>(t: T) => [T, string[]]
27+
>['1'] : [string]
28+
>'1' : "1"
29+
30+
f2e1.toLowerCase();
31+
>f2e1.toLowerCase() : string
32+
>f2e1.toLowerCase : () => string
33+
>f2e1 : string
34+
>toLowerCase : () => string
35+
36+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
37+
>f3 : <T extends string[]>(t: T) => [[T, string[]]]
38+
>t : T
39+
40+
const [[[f3e1]]] = f3(['1']);
41+
>f3e1 : string
42+
>f3(['1']) : [[[string], string[]]]
43+
>f3 : <T extends string[]>(t: T) => [[T, string[]]]
44+
>['1'] : [string]
45+
>'1' : "1"
46+
47+
f3e1.toLowerCase();
48+
>f3e1.toLowerCase() : string
49+
>f3e1.toLowerCase : () => string
50+
>f3e1 : string
51+
>toLowerCase : () => string
52+

tests/baselines/reference/inferTupleFromBindingPattern.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/baselines/reference/inferTupleFromBindingPattern.symbols

Lines changed: 0 additions & 14 deletions
This file was deleted.

tests/baselines/reference/inferTupleFromBindingPattern.types

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1+
// @strict: true
2+
// @noUncheckedIndexedAccess: true, false
3+
14
declare function f<T>(cb: () => T): T;
25
const [e1, e2, e3] = f(() => [1, "hi", true]);
6+
7+
// repro from #42969
8+
declare const f2: <T extends string[]>(t: T) => [T, string[]];
9+
const [[f2e1]] = f2(['1']);
10+
f2e1.toLowerCase();
11+
12+
declare const f3: <T extends string[]>(t: T) => [[T, string[]]];
13+
const [[[f3e1]]] = f3(['1']);
14+
f3e1.toLowerCase();

0 commit comments

Comments
 (0)