Skip to content

Commit a26acf4

Browse files
authored
Template literal types for contextually typed template literal expressions (#43376)
* Template literal types for contextually typed template literal expressions * Accept new baselines * Add regression test * Add a few more tests
1 parent b34e705 commit a26acf4

9 files changed

+199
-87
lines changed

src/compiler/checker.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -31824,7 +31824,12 @@ namespace ts {
3182431824
texts.push(span.literal.text);
3182531825
types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType);
3182631826
}
31827-
return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType;
31827+
return isConstContext(node) || someType(getContextualType(node) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType;
31828+
}
31829+
31830+
function isTemplateLiteralContextualType(type: Type): boolean {
31831+
return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) ||
31832+
type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike));
3182831833
}
3182931834

3183031835
function getContextNode(node: Expression): Node {
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithTemplateStrings02.ts(1,5): error TS2322: Type '"AB\nC"' is not assignable to type '"AB\r\nC"'.
2-
tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithTemplateStrings02.ts(3,5): error TS2322: Type 'string' is not assignable to type '"DE\nF"'.
32

43

5-
==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithTemplateStrings02.ts (2 errors) ====
4+
==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithTemplateStrings02.ts (1 errors) ====
65
let abc: "AB\r\nC" = `AB
76
~~~
87
!!! error TS2322: Type '"AB\nC"' is not assignable to type '"AB\r\nC"'.
98
C`;
10-
let de_NEWLINE_f: "DE\nF" = `DE${"\n"}F`;
11-
~~~~~~~~~~~~
12-
!!! error TS2322: Type 'string' is not assignable to type '"DE\nF"'.
9+
let de_NEWLINE_f: "DE\nF" = `DE${"\n"}F`;

tests/baselines/reference/stringLiteralTypesWithTemplateStrings02.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ let abc: "AB\r\nC" = `AB
66
C`;
77
let de_NEWLINE_f: "DE\nF" = `DE${"\n"}F`;
88
>de_NEWLINE_f : "DE\nF"
9-
>`DE${"\n"}F` : string
9+
>`DE${"\n"}F` : "DE\nF"
1010
>"\n" : "\n"
1111

tests/baselines/reference/templateLiteralTypes1.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const createScopedActionType = <S extends string>(scope: S) => <T extends string
88
><T extends string>(type: T) => `${scope}/${type}` as `${S}/${T}` : <T extends string>(type: T) => `${S}/${T}`
99
>type : T
1010
>`${scope}/${type}` as `${S}/${T}` : `${S}/${T}`
11-
>`${scope}/${type}` : string
11+
>`${scope}/${type}` : `${S}/${T}`
1212
>scope : S
1313
>type : T
1414

tests/baselines/reference/templateLiteralTypes2.errors.txt

+15-22
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,22 @@
1-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(6,11): error TS2322: Type 'string' is not assignable to type '`abc${string}`'.
2-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(7,11): error TS2322: Type 'string' is not assignable to type '`abc${number}`'.
3-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(8,11): error TS2322: Type 'string' is not assignable to type '"abcfoo" | "abcbar" | "abcbaz"'.
4-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(9,11): error TS2322: Type 'string' is not assignable to type '`abc${T}`'.
5-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(21,11): error TS2322: Type 'string' is not assignable to type '`abc${string}`'.
61
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(23,11): error TS2322: Type 'string' is not assignable to type '`abc${string}`'.
72
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(29,11): error TS2322: Type 'string' is not assignable to type '`foo${string}` | `bar${string}`'.
83
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(32,11): error TS2322: Type 'string' is not assignable to type '`foo${string}` | `bar${string}` | `baz${string}`'.
9-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(43,11): error TS2322: Type 'string' is not assignable to type '`foo${string}`'.
104
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(67,9): error TS2322: Type '`foo${number}`' is not assignable to type 'String'.
115
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(68,9): error TS2322: Type '`foo${number}`' is not assignable to type 'Object'.
126
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(69,9): error TS2322: Type '`foo${number}`' is not assignable to type '{}'.
137
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(70,9): error TS2322: Type '`foo${number}`' is not assignable to type '{ length: number; }'.
14-
tests/cases/conformance/types/literal/templateLiteralTypes2.ts(98,7): error TS2322: Type 'string' is not assignable to type '`${number}px`'.
158

169

17-
==== tests/cases/conformance/types/literal/templateLiteralTypes2.ts (14 errors) ====
10+
==== tests/cases/conformance/types/literal/templateLiteralTypes2.ts (7 errors) ====
1811
function ft1<T extends string>(s: string, n: number, u: 'foo' | 'bar' | 'baz', t: T) {
1912
const c1 = `abc${s}`; // `abc${string}`
2013
const c2 = `abc${n}`; // `abc${number}`
2114
const c3 = `abc${u}`; // "abcfoo" | "abcbar" | "abcbaz"
2215
const c4 = `abc${t}`; // `abc${T}
2316
const d1: `abc${string}` = `abc${s}`;
24-
~~
25-
!!! error TS2322: Type 'string' is not assignable to type '`abc${string}`'.
2617
const d2: `abc${number}` = `abc${n}`;
27-
~~
28-
!!! error TS2322: Type 'string' is not assignable to type '`abc${number}`'.
2918
const d3: `abc${'foo' | 'bar' | 'baz'}` = `abc${u}`;
30-
~~
31-
!!! error TS2322: Type 'string' is not assignable to type '"abcfoo" | "abcbar" | "abcbaz"'.
3219
const d4: `abc${T}` = `abc${t}`;
33-
~~
34-
!!! error TS2322: Type 'string' is not assignable to type '`abc${T}`'.
3520
}
3621

3722
function ft2(s: string) {
@@ -44,8 +29,6 @@ tests/cases/conformance/types/literal/templateLiteralTypes2.ts(98,7): error TS23
4429
const c2 = c1; // Widening type `abc${string}`
4530
let v2 = c2; // Type string
4631
const c3: `abc${string}` = `abc${s}`;
47-
~~
48-
!!! error TS2322: Type 'string' is not assignable to type '`abc${string}`'.
4932
let v3 = c3; // Type `abc${string}`
5033
const c4: `abc${string}` = c1; // Type `abc${string}`
5134
~~
@@ -74,8 +57,6 @@ tests/cases/conformance/types/literal/templateLiteralTypes2.ts(98,7): error TS23
7457
const c1 = `foo${s}`;
7558
let v1 = c1;
7659
const c2: `foo${string}` = `foo${s}`;
77-
~~
78-
!!! error TS2322: Type 'string' is not assignable to type '`foo${string}`'.
7960
let v2 = c2;
8061
const c3 = `foo${s}` as `foo${string}`;
8162
let v3 = c3;
@@ -113,6 +94,14 @@ tests/cases/conformance/types/literal/templateLiteralTypes2.ts(98,7): error TS23
11394
!!! error TS2322: Type '`foo${number}`' is not assignable to type '{ length: number; }'.
11495
}
11596

97+
declare function g1<T>(x: T): T;
98+
declare function g2<T extends string>(x: T): T;
99+
100+
function ft20(s: string) {
101+
let x1 = g1(`xyz-${s}`); // string
102+
let x2 = g2(`xyz-${s}`); // `xyz-${string}`
103+
}
104+
116105
// Repro from #41631
117106

118107
declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;
@@ -139,6 +128,10 @@ tests/cases/conformance/types/literal/templateLiteralTypes2.ts(98,7): error TS23
139128
const pixelString: PixelValueType = `22px`;
140129

141130
const pixelStringWithTemplate: PixelValueType = `${pixelValue}px`;
142-
~~~~~~~~~~~~~~~~~~~~~~~
143-
!!! error TS2322: Type 'string' is not assignable to type '`${number}px`'.
131+
132+
// Repro from #43143
133+
134+
function getCardTitle(title: string): `test-${string}` {
135+
return `test-${title}`;
136+
}
144137

tests/baselines/reference/templateLiteralTypes2.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ function ft14(t: `foo${number}`) {
7171
let x6: { length: number } = t;
7272
}
7373

74+
declare function g1<T>(x: T): T;
75+
declare function g2<T extends string>(x: T): T;
76+
77+
function ft20(s: string) {
78+
let x1 = g1(`xyz-${s}`); // string
79+
let x2 = g2(`xyz-${s}`); // `xyz-${string}`
80+
}
81+
7482
// Repro from #41631
7583

7684
declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;
@@ -97,6 +105,12 @@ type PixelValueType = `${number}px`;
97105
const pixelString: PixelValueType = `22px`;
98106

99107
const pixelStringWithTemplate: PixelValueType = `${pixelValue}px`;
108+
109+
// Repro from #43143
110+
111+
function getCardTitle(title: string): `test-${string}` {
112+
return `test-${title}`;
113+
}
100114

101115

102116
//// [templateLiteralTypes2.js]
@@ -161,6 +175,10 @@ function ft14(t) {
161175
var x4 = t;
162176
var x6 = t;
163177
}
178+
function ft20(s) {
179+
var x1 = g1("xyz-" + s); // string
180+
var x2 = g2("xyz-" + s); // `xyz-${string}`
181+
}
164182
var t1 = takesLiteral("foo.bar.baz"); // "baz"
165183
var id2 = "foo.bar.baz";
166184
var t2 = takesLiteral(id2); // "baz"
@@ -172,6 +190,10 @@ var t5 = takesLiteral("foo.bar." + someUnion); // "abc" | "def" | "ghi"
172190
var pixelValue = 22;
173191
var pixelString = "22px";
174192
var pixelStringWithTemplate = pixelValue + "px";
193+
// Repro from #43143
194+
function getCardTitle(title) {
195+
return "test-" + title;
196+
}
175197

176198

177199
//// [templateLiteralTypes2.d.ts]
@@ -185,17 +207,21 @@ declare function nonWidening<T extends string | number | symbol>(x: T): T;
185207
declare function ft13(s: string, cond: boolean): void;
186208
declare type T0 = string | `${number}px`;
187209
declare function ft14(t: `foo${number}`): void;
210+
declare function g1<T>(x: T): T;
211+
declare function g2<T extends string>(x: T): T;
212+
declare function ft20(s: string): void;
188213
declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;
189214
declare const t1: "baz";
190215
declare const id2 = "foo.bar.baz";
191216
declare const t2: "baz";
192217
declare const someString: string;
193-
declare const t3: unknown;
218+
declare const t3: string;
194219
declare const id4: string;
195220
declare const t4: unknown;
196221
declare const someUnion: 'abc' | 'def' | 'ghi';
197-
declare const t5: unknown;
222+
declare const t5: "abc" | "def" | "ghi";
198223
declare const pixelValue: number;
199224
declare type PixelValueType = `${number}px`;
200225
declare const pixelString: PixelValueType;
201226
declare const pixelStringWithTemplate: PixelValueType;
227+
declare function getCardTitle(title: string): `test-${string}`;

tests/baselines/reference/templateLiteralTypes2.symbols

+72-33
Original file line numberDiff line numberDiff line change
@@ -257,68 +257,107 @@ function ft14(t: `foo${number}`) {
257257
>t : Symbol(t, Decl(templateLiteralTypes2.ts, 64, 14))
258258
}
259259

260+
declare function g1<T>(x: T): T;
261+
>g1 : Symbol(g1, Decl(templateLiteralTypes2.ts, 70, 1))
262+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 72, 20))
263+
>x : Symbol(x, Decl(templateLiteralTypes2.ts, 72, 23))
264+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 72, 20))
265+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 72, 20))
266+
267+
declare function g2<T extends string>(x: T): T;
268+
>g2 : Symbol(g2, Decl(templateLiteralTypes2.ts, 72, 32))
269+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 73, 20))
270+
>x : Symbol(x, Decl(templateLiteralTypes2.ts, 73, 38))
271+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 73, 20))
272+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 73, 20))
273+
274+
function ft20(s: string) {
275+
>ft20 : Symbol(ft20, Decl(templateLiteralTypes2.ts, 73, 47))
276+
>s : Symbol(s, Decl(templateLiteralTypes2.ts, 75, 14))
277+
278+
let x1 = g1(`xyz-${s}`); // string
279+
>x1 : Symbol(x1, Decl(templateLiteralTypes2.ts, 76, 7))
280+
>g1 : Symbol(g1, Decl(templateLiteralTypes2.ts, 70, 1))
281+
>s : Symbol(s, Decl(templateLiteralTypes2.ts, 75, 14))
282+
283+
let x2 = g2(`xyz-${s}`); // `xyz-${string}`
284+
>x2 : Symbol(x2, Decl(templateLiteralTypes2.ts, 77, 7))
285+
>g2 : Symbol(g2, Decl(templateLiteralTypes2.ts, 72, 32))
286+
>s : Symbol(s, Decl(templateLiteralTypes2.ts, 75, 14))
287+
}
288+
260289
// Repro from #41631
261290

262291
declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;
263-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
264-
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 74, 30))
265-
>literal : Symbol(literal, Decl(templateLiteralTypes2.ts, 74, 48))
266-
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 74, 30))
267-
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 74, 30))
268-
>R : Symbol(R, Decl(templateLiteralTypes2.ts, 74, 87))
269-
>R : Symbol(R, Decl(templateLiteralTypes2.ts, 74, 87))
292+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
293+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 82, 30))
294+
>literal : Symbol(literal, Decl(templateLiteralTypes2.ts, 82, 48))
295+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 82, 30))
296+
>T : Symbol(T, Decl(templateLiteralTypes2.ts, 82, 30))
297+
>R : Symbol(R, Decl(templateLiteralTypes2.ts, 82, 87))
298+
>R : Symbol(R, Decl(templateLiteralTypes2.ts, 82, 87))
270299

271300
const t1 = takesLiteral("foo.bar.baz"); // "baz"
272-
>t1 : Symbol(t1, Decl(templateLiteralTypes2.ts, 76, 5))
273-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
301+
>t1 : Symbol(t1, Decl(templateLiteralTypes2.ts, 84, 5))
302+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
274303

275304
const id2 = "foo.bar.baz";
276-
>id2 : Symbol(id2, Decl(templateLiteralTypes2.ts, 77, 5))
305+
>id2 : Symbol(id2, Decl(templateLiteralTypes2.ts, 85, 5))
277306

278307
const t2 = takesLiteral(id2); // "baz"
279-
>t2 : Symbol(t2, Decl(templateLiteralTypes2.ts, 78, 5))
280-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
281-
>id2 : Symbol(id2, Decl(templateLiteralTypes2.ts, 77, 5))
308+
>t2 : Symbol(t2, Decl(templateLiteralTypes2.ts, 86, 5))
309+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
310+
>id2 : Symbol(id2, Decl(templateLiteralTypes2.ts, 85, 5))
282311

283312
declare const someString: string;
284-
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 80, 13))
313+
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 88, 13))
285314

286315
const t3 = takesLiteral(`foo.bar.${someString}`); // string
287-
>t3 : Symbol(t3, Decl(templateLiteralTypes2.ts, 81, 5))
288-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
289-
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 80, 13))
316+
>t3 : Symbol(t3, Decl(templateLiteralTypes2.ts, 89, 5))
317+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
318+
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 88, 13))
290319

291320
const id4 = `foo.bar.${someString}`;
292-
>id4 : Symbol(id4, Decl(templateLiteralTypes2.ts, 83, 5))
293-
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 80, 13))
321+
>id4 : Symbol(id4, Decl(templateLiteralTypes2.ts, 91, 5))
322+
>someString : Symbol(someString, Decl(templateLiteralTypes2.ts, 88, 13))
294323

295324
const t4 = takesLiteral(id4); // string
296-
>t4 : Symbol(t4, Decl(templateLiteralTypes2.ts, 84, 5))
297-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
298-
>id4 : Symbol(id4, Decl(templateLiteralTypes2.ts, 83, 5))
325+
>t4 : Symbol(t4, Decl(templateLiteralTypes2.ts, 92, 5))
326+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
327+
>id4 : Symbol(id4, Decl(templateLiteralTypes2.ts, 91, 5))
299328

300329
declare const someUnion: 'abc' | 'def' | 'ghi';
301-
>someUnion : Symbol(someUnion, Decl(templateLiteralTypes2.ts, 86, 13))
330+
>someUnion : Symbol(someUnion, Decl(templateLiteralTypes2.ts, 94, 13))
302331

303332
const t5 = takesLiteral(`foo.bar.${someUnion}`); // "abc" | "def" | "ghi"
304-
>t5 : Symbol(t5, Decl(templateLiteralTypes2.ts, 87, 5))
305-
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 70, 1))
306-
>someUnion : Symbol(someUnion, Decl(templateLiteralTypes2.ts, 86, 13))
333+
>t5 : Symbol(t5, Decl(templateLiteralTypes2.ts, 95, 5))
334+
>takesLiteral : Symbol(takesLiteral, Decl(templateLiteralTypes2.ts, 78, 1))
335+
>someUnion : Symbol(someUnion, Decl(templateLiteralTypes2.ts, 94, 13))
307336

308337
// Repro from #41732
309338

310339
const pixelValue: number = 22;
311-
>pixelValue : Symbol(pixelValue, Decl(templateLiteralTypes2.ts, 91, 5))
340+
>pixelValue : Symbol(pixelValue, Decl(templateLiteralTypes2.ts, 99, 5))
312341

313342
type PixelValueType = `${number}px`;
314-
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 91, 30))
343+
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 99, 30))
315344

316345
const pixelString: PixelValueType = `22px`;
317-
>pixelString : Symbol(pixelString, Decl(templateLiteralTypes2.ts, 95, 5))
318-
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 91, 30))
346+
>pixelString : Symbol(pixelString, Decl(templateLiteralTypes2.ts, 103, 5))
347+
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 99, 30))
319348

320349
const pixelStringWithTemplate: PixelValueType = `${pixelValue}px`;
321-
>pixelStringWithTemplate : Symbol(pixelStringWithTemplate, Decl(templateLiteralTypes2.ts, 97, 5))
322-
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 91, 30))
323-
>pixelValue : Symbol(pixelValue, Decl(templateLiteralTypes2.ts, 91, 5))
350+
>pixelStringWithTemplate : Symbol(pixelStringWithTemplate, Decl(templateLiteralTypes2.ts, 105, 5))
351+
>PixelValueType : Symbol(PixelValueType, Decl(templateLiteralTypes2.ts, 99, 30))
352+
>pixelValue : Symbol(pixelValue, Decl(templateLiteralTypes2.ts, 99, 5))
353+
354+
// Repro from #43143
355+
356+
function getCardTitle(title: string): `test-${string}` {
357+
>getCardTitle : Symbol(getCardTitle, Decl(templateLiteralTypes2.ts, 105, 66))
358+
>title : Symbol(title, Decl(templateLiteralTypes2.ts, 109, 22))
359+
360+
return `test-${title}`;
361+
>title : Symbol(title, Decl(templateLiteralTypes2.ts, 109, 22))
362+
}
324363

0 commit comments

Comments
 (0)