Skip to content

Commit 5c92af1

Browse files
authored
Fix contextual typing for post-spread tuple elements (#52769)
1 parent d87d0ad commit 5c92af1

File tree

6 files changed

+440
-12
lines changed

6 files changed

+440
-12
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23201,7 +23201,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2320123201
return restType && createArrayType(restType);
2320223202
}
2320323203

23204-
function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) {
23204+
function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = false) {
2320523205
const length = getTypeReferenceArity(type) - endSkipCount;
2320623206
if (index < length) {
2320723207
const typeArguments = getTypeArguments(type);
@@ -23210,7 +23210,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2321023210
const t = typeArguments[i];
2321123211
elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t);
2321223212
}
23213-
return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes);
23213+
return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
2321423214
}
2321523215
return undefined;
2321623216
}
@@ -28956,9 +28956,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2895628956
if (prop) {
2895728957
return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop);
2895828958
}
28959-
if (isTupleType(t)) {
28960-
const restType = getRestTypeOfTupleType(t);
28961-
if (restType && isNumericLiteralName(name) && +name >= 0) {
28959+
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
28960+
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
28961+
if (restType) {
2896228962
return restType;
2896328963
}
2896428964
}
@@ -29010,10 +29010,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2901029010
// type of T.
2901129011
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
2901229012
return arrayContextualType && (
29013-
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
29014-
|| mapType(
29015-
arrayContextualType,
29016-
t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
29013+
index >= 0 && getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) ||
29014+
mapType(arrayContextualType, t =>
29015+
isTupleType(t) ?
29016+
getElementTypeOfSliceOfTupleType(t, 0, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true) :
29017+
getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
2901729018
/*noReductions*/ true));
2901829019
}
2901929020

@@ -29245,7 +29246,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2924529246
case SyntaxKind.ArrayLiteralExpression: {
2924629247
const arrayLiteral = parent as ArrayLiteralExpression;
2924729248
const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
29248-
return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node));
29249+
// The index of an array literal element doesn't necessarily line up with the index of the corresponding
29250+
// element in a contextual tuple type when there are preceding spread elements in the array literal. For
29251+
// this reason we only pass indices for elements that precede the first spread element.
29252+
const spreadIndex = getNodeLinks(arrayLiteral).firstSpreadIndex ??= findIndex(arrayLiteral.elements, isSpreadElement);
29253+
const elementIndex = indexOfNode(arrayLiteral.elements, node);
29254+
return getContextualTypeForElementExpression(type, spreadIndex < 0 || elementIndex < spreadIndex ? elementIndex : -1);
2924929255
}
2925029256
case SyntaxKind.ConditionalExpression:
2925129257
return getContextualTypeForConditionalOperand(node, contextFlags);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5996,6 +5996,7 @@ export interface NodeLinks {
59965996
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
59975997
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
59985998
decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime.
5999+
firstSpreadIndex?: number; // Index of first spread element in array literal (-1 for none)
59996000
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
60006001
fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain.
60016002
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
=== tests/cases/compiler/spreadsAndContextualTupleTypes.ts ===
2+
declare function fx1<T extends [string, string, string, 'a' | 'b']>(x: T): T;
3+
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))
4+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))
5+
>x : Symbol(x, Decl(spreadsAndContextualTupleTypes.ts, 0, 68))
6+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))
7+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))
8+
9+
declare function fx2<T extends [...string[], 'a' | 'b']>(x: T): T;
10+
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))
11+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))
12+
>x : Symbol(x, Decl(spreadsAndContextualTupleTypes.ts, 1, 57))
13+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))
14+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))
15+
16+
const t3 = ['x', 'y', 'z'] as const;
17+
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))
18+
>const : Symbol(const)
19+
20+
fx1(['x', 'y', 'z', 'a']);
21+
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))
22+
23+
fx1([...t3, 'a']);
24+
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))
25+
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))
26+
27+
fx2(['x', 'y', 'z', 'a']);
28+
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))
29+
30+
fx2([...t3, 'a']);
31+
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))
32+
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))
33+
34+
const x1: [...string[], '!'] = ['!'];
35+
>x1 : Symbol(x1, Decl(spreadsAndContextualTupleTypes.ts, 11, 5))
36+
37+
const x2: [...string[], '!'] = ['a', '!'];
38+
>x2 : Symbol(x2, Decl(spreadsAndContextualTupleTypes.ts, 12, 5))
39+
40+
const x3: [...string[], '!'] = [...t3, '!'];
41+
>x3 : Symbol(x3, Decl(spreadsAndContextualTupleTypes.ts, 13, 5))
42+
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))
43+
44+
// Repro from #52684
45+
46+
const staticPath1Level = ["home"] as const;
47+
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
48+
>const : Symbol(const)
49+
50+
const staticPath2Level = ["home", "user"] as const;
51+
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
52+
>const : Symbol(const)
53+
54+
const staticPath3Level = ["home", "user", "downloads"] as const;
55+
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
56+
>const : Symbol(const)
57+
58+
const randomID = 'id' as string;
59+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
60+
61+
declare function foo<const T>(path: T): T;
62+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
63+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))
64+
>path : Symbol(path, Decl(spreadsAndContextualTupleTypes.ts, 23, 30))
65+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))
66+
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))
67+
68+
const a1 = foo([...staticPath1Level, randomID, 'doc.pdf']);
69+
>a1 : Symbol(a1, Decl(spreadsAndContextualTupleTypes.ts, 25, 5))
70+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
71+
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
72+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
73+
74+
const a2 = foo([...staticPath2Level, randomID, 'doc.pdf']);
75+
>a2 : Symbol(a2, Decl(spreadsAndContextualTupleTypes.ts, 26, 5))
76+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
77+
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
78+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
79+
80+
const a3 = foo([...staticPath3Level, randomID, 'doc.pdf']);
81+
>a3 : Symbol(a3, Decl(spreadsAndContextualTupleTypes.ts, 27, 5))
82+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
83+
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
84+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
85+
86+
const b1 = foo([...staticPath1Level, randomID, 'folder', 'doc.pdf']);
87+
>b1 : Symbol(b1, Decl(spreadsAndContextualTupleTypes.ts, 29, 5))
88+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
89+
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
90+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
91+
92+
const b2 = foo([...staticPath2Level, randomID, 'folder', 'doc.pdf']);
93+
>b2 : Symbol(b2, Decl(spreadsAndContextualTupleTypes.ts, 30, 5))
94+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
95+
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
96+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
97+
98+
const b3 = foo([...staticPath3Level, randomID, 'folder', 'doc.pdf']);
99+
>b3 : Symbol(b3, Decl(spreadsAndContextualTupleTypes.ts, 31, 5))
100+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
101+
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
102+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
103+
104+
const c1 = foo([...staticPath1Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
105+
>c1 : Symbol(c1, Decl(spreadsAndContextualTupleTypes.ts, 33, 5))
106+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
107+
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
108+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
109+
110+
const c2 = foo([...staticPath2Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
111+
>c2 : Symbol(c2, Decl(spreadsAndContextualTupleTypes.ts, 34, 5))
112+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
113+
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
114+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
115+
116+
const c3 = foo([...staticPath3Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
117+
>c3 : Symbol(c3, Decl(spreadsAndContextualTupleTypes.ts, 35, 5))
118+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
119+
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
120+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
121+
122+
const d1 = foo([...staticPath1Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
123+
>d1 : Symbol(d1, Decl(spreadsAndContextualTupleTypes.ts, 37, 5))
124+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
125+
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
126+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
127+
128+
const d2 = foo([...staticPath2Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
129+
>d2 : Symbol(d2, Decl(spreadsAndContextualTupleTypes.ts, 38, 5))
130+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
131+
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
132+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
133+
134+
const d3 = foo([...staticPath3Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
135+
>d3 : Symbol(d3, Decl(spreadsAndContextualTupleTypes.ts, 39, 5))
136+
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
137+
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
138+
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))
139+

0 commit comments

Comments
 (0)