Skip to content

Commit a64ea3a

Browse files
authored
Reduce template literal types with a single placeholder and no extra texts (#55371)
1 parent d64646b commit a64ea3a

12 files changed

+289
-13
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17984,6 +17984,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1798417984
if (contains(types, wildcardType)) {
1798517985
return wildcardType;
1798617986
}
17987+
if (
17988+
texts.length === 2 && texts[0] === "" && texts[1] === ""
17989+
// literals (including string enums) are stringified below
17990+
&& !(types[0].flags & TypeFlags.Literal)
17991+
// infer T extends StringLike can't be unwrapped eagerly
17992+
&& !types[0].symbol?.declarations?.some(d => d.parent.kind === SyntaxKind.InferType)
17993+
&& isTypeAssignableTo(types[0], stringType)
17994+
) {
17995+
return types[0];
17996+
}
1798717997
const newTypes: Type[] = [];
1798817998
const newTexts: string[] = [];
1798917999
let text = texts[0];

tests/baselines/reference/numericStringLiteralTypes.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ type T2 = string & `${bigint}`; // `${bigint}
1111
>T2 : `${bigint}`
1212

1313
type T3<T extends string> = string & `${T}`; // `${T}
14-
>T3 : `${T}`
14+
>T3 : T
1515

1616
type T4<T extends string> = string & `${Capitalize<`${T}`>}`; // `${Capitalize<T>}`
17-
>T4 : `${Capitalize<T>}`
17+
>T4 : Capitalize<T>
1818

1919
function f1(a: boolean[], x: `${number}`) {
2020
>f1 : (a: boolean[], x: `${number}`) => void

tests/baselines/reference/recursiveConditionalCrash3.types

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

tests/baselines/reference/templateLiteralIntersection.types

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type OriginA1 = `${A}`
1818
>OriginA1 : "a"
1919

2020
type OriginA2 = `${MixA}`
21-
>OriginA2 : `${MixA}`
21+
>OriginA2 : MixA
2222

2323
type B = `${typeof a}`
2424
>B : "a"
@@ -32,23 +32,23 @@ type OriginB1 = `${B}`
3232
>OriginB1 : "a"
3333

3434
type OriginB2 = `${MixB}`
35-
>OriginB2 : `${MixB}`
35+
>OriginB2 : MixB
3636

3737
type MixC = { foo: string } & A
3838
>MixC : { foo: string; } & "a"
3939
>foo : string
4040

4141
type OriginC = `${MixC}`
42-
>OriginC : `${MixC}`
42+
>OriginC : MixC
4343

4444
type MixD<T extends string> =
45-
>MixD : `${T & { foo: string; }}`
45+
>MixD : T & { foo: string; }
4646

4747
`${T & { foo: string }}`
4848
>foo : string
4949

5050
type OriginD = `${MixD<A & { foo: string }> & { foo: string }}`;
51-
>OriginD : `${`${"a" & { foo: string; } & { foo: string; }}` & { foo: string; }}`
51+
>OriginD : "a" & { foo: string; } & { foo: string; } & { foo: string; }
5252
>foo : string
5353
>foo : string
5454

tests/baselines/reference/templateLiteralIntersection3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ options1[`foo/${path}`] = false;
3131

3232
// Lowercase<`foo/${Path}`> => `foo/${Lowercase<Path>}`
3333
declare const lowercasePath: Lowercase<`foo/${Path}`>;
34-
>lowercasePath : `foo/${Lowercase<`${Path}`>}`
34+
>lowercasePath : `foo/${Lowercase<Path>}`
3535

3636
options1[lowercasePath] = false;
3737
>options1[lowercasePath] = false : false
3838
>options1[lowercasePath] : boolean
3939
>options1 : { prop: number; } & { [k: string]: boolean; }
40-
>lowercasePath : `foo/${Lowercase<`${Path}`>}`
40+
>lowercasePath : `foo/${Lowercase<Path>}`
4141
>false : false
4242

tests/baselines/reference/templateLiteralTypes3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ function f1<T extends string>(s: string, n: number, b: boolean, t: T) {
9393
>b : boolean
9494

9595
let x7 = foo1(`*${t}*` as const);
96-
>x7 : `${T}`
97-
>foo1(`*${t}*` as const) : `${T}`
96+
>x7 : T
97+
>foo1(`*${t}*` as const) : T
9898
>foo1 : <V extends string>(arg: `*${V}*`) => V
9999
>`*${t}*` as const : `*${T}*`
100100
>`*${t}*` : `*${T}*`
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//// [tests/cases/conformance/types/literal/templateLiteralTypes5.ts] ////
2+
3+
=== templateLiteralTypes5.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/55364
5+
interface TypeMap {
6+
>TypeMap : Symbol(TypeMap, Decl(templateLiteralTypes5.ts, 0, 0))
7+
8+
a: "A";
9+
>a : Symbol(TypeMap.a, Decl(templateLiteralTypes5.ts, 1, 19))
10+
11+
b: "B";
12+
>b : Symbol(TypeMap.b, Decl(templateLiteralTypes5.ts, 2, 9))
13+
}
14+
declare const f: <T0 extends "a" | "b">(x: `${T0}`) => TypeMap[T0];
15+
>f : Symbol(f, Decl(templateLiteralTypes5.ts, 5, 13))
16+
>T0 : Symbol(T0, Decl(templateLiteralTypes5.ts, 5, 18))
17+
>x : Symbol(x, Decl(templateLiteralTypes5.ts, 5, 40))
18+
>T0 : Symbol(T0, Decl(templateLiteralTypes5.ts, 5, 18))
19+
>TypeMap : Symbol(TypeMap, Decl(templateLiteralTypes5.ts, 0, 0))
20+
>T0 : Symbol(T0, Decl(templateLiteralTypes5.ts, 5, 18))
21+
22+
type F1 = <T1 extends "a" | "b">(x: `${T1}`) => TypeMap[T1];
23+
>F1 : Symbol(F1, Decl(templateLiteralTypes5.ts, 5, 67))
24+
>T1 : Symbol(T1, Decl(templateLiteralTypes5.ts, 6, 11))
25+
>x : Symbol(x, Decl(templateLiteralTypes5.ts, 6, 33))
26+
>T1 : Symbol(T1, Decl(templateLiteralTypes5.ts, 6, 11))
27+
>TypeMap : Symbol(TypeMap, Decl(templateLiteralTypes5.ts, 0, 0))
28+
>T1 : Symbol(T1, Decl(templateLiteralTypes5.ts, 6, 11))
29+
30+
const f1: F1 = f;
31+
>f1 : Symbol(f1, Decl(templateLiteralTypes5.ts, 7, 5))
32+
>F1 : Symbol(F1, Decl(templateLiteralTypes5.ts, 5, 67))
33+
>f : Symbol(f, Decl(templateLiteralTypes5.ts, 5, 13))
34+
35+
type F2 = <T2 extends 'a' | 'b'>(x: `${T2}`) => TypeMap[`${T2}`]
36+
>F2 : Symbol(F2, Decl(templateLiteralTypes5.ts, 7, 17))
37+
>T2 : Symbol(T2, Decl(templateLiteralTypes5.ts, 8, 11))
38+
>x : Symbol(x, Decl(templateLiteralTypes5.ts, 8, 33))
39+
>T2 : Symbol(T2, Decl(templateLiteralTypes5.ts, 8, 11))
40+
>TypeMap : Symbol(TypeMap, Decl(templateLiteralTypes5.ts, 0, 0))
41+
>T2 : Symbol(T2, Decl(templateLiteralTypes5.ts, 8, 11))
42+
43+
const f2: F2 = f
44+
>f2 : Symbol(f2, Decl(templateLiteralTypes5.ts, 9, 5))
45+
>F2 : Symbol(F2, Decl(templateLiteralTypes5.ts, 7, 17))
46+
>f : Symbol(f, Decl(templateLiteralTypes5.ts, 5, 13))
47+
48+
function f3<T3 extends "a" | "b">(x: T3) {
49+
>f3 : Symbol(f3, Decl(templateLiteralTypes5.ts, 9, 16))
50+
>T3 : Symbol(T3, Decl(templateLiteralTypes5.ts, 11, 12))
51+
>x : Symbol(x, Decl(templateLiteralTypes5.ts, 11, 34))
52+
>T3 : Symbol(T3, Decl(templateLiteralTypes5.ts, 11, 12))
53+
54+
const test1: `${T3}` = x
55+
>test1 : Symbol(test1, Decl(templateLiteralTypes5.ts, 12, 9))
56+
>T3 : Symbol(T3, Decl(templateLiteralTypes5.ts, 11, 12))
57+
>x : Symbol(x, Decl(templateLiteralTypes5.ts, 11, 34))
58+
59+
const test2: T3 = "" as `${T3}`;
60+
>test2 : Symbol(test2, Decl(templateLiteralTypes5.ts, 13, 9))
61+
>T3 : Symbol(T3, Decl(templateLiteralTypes5.ts, 11, 12))
62+
>T3 : Symbol(T3, Decl(templateLiteralTypes5.ts, 11, 12))
63+
}
64+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/conformance/types/literal/templateLiteralTypes5.ts] ////
2+
3+
=== templateLiteralTypes5.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/55364
5+
interface TypeMap {
6+
a: "A";
7+
>a : "A"
8+
9+
b: "B";
10+
>b : "B"
11+
}
12+
declare const f: <T0 extends "a" | "b">(x: `${T0}`) => TypeMap[T0];
13+
>f : <T0 extends "a" | "b">(x: `${T0}`) => TypeMap[T0]
14+
>x : T0
15+
16+
type F1 = <T1 extends "a" | "b">(x: `${T1}`) => TypeMap[T1];
17+
>F1 : <T1 extends "a" | "b">(x: `${T1}`) => TypeMap[T1]
18+
>x : T1
19+
20+
const f1: F1 = f;
21+
>f1 : F1
22+
>f : <T0 extends "a" | "b">(x: T0) => TypeMap[T0]
23+
24+
type F2 = <T2 extends 'a' | 'b'>(x: `${T2}`) => TypeMap[`${T2}`]
25+
>F2 : <T2 extends "a" | "b">(x: `${T2}`) => TypeMap[`${T2}`]
26+
>x : T2
27+
28+
const f2: F2 = f
29+
>f2 : F2
30+
>f : <T0 extends "a" | "b">(x: T0) => TypeMap[T0]
31+
32+
function f3<T3 extends "a" | "b">(x: T3) {
33+
>f3 : <T3 extends "a" | "b">(x: T3) => void
34+
>x : T3
35+
36+
const test1: `${T3}` = x
37+
>test1 : T3
38+
>x : T3
39+
40+
const test2: T3 = "" as `${T3}`;
41+
>test2 : T3
42+
>"" as `${T3}` : T3
43+
>"" : ""
44+
}
45+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//// [tests/cases/conformance/types/literal/templateLiteralTypes6.ts] ////
2+
3+
=== templateLiteralTypes6.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/56659
5+
6+
type Registry = {
7+
>Registry : Symbol(Registry, Decl(templateLiteralTypes6.ts, 0, 0))
8+
9+
a: { a1: {} };
10+
>a : Symbol(a, Decl(templateLiteralTypes6.ts, 2, 17))
11+
>a1 : Symbol(a1, Decl(templateLiteralTypes6.ts, 3, 6))
12+
13+
b: { b1: {} };
14+
>b : Symbol(b, Decl(templateLiteralTypes6.ts, 3, 16))
15+
>b1 : Symbol(b1, Decl(templateLiteralTypes6.ts, 4, 6))
16+
17+
};
18+
19+
type Keyof<T> = keyof T & string;
20+
>Keyof : Symbol(Keyof, Decl(templateLiteralTypes6.ts, 5, 2))
21+
>T : Symbol(T, Decl(templateLiteralTypes6.ts, 7, 11))
22+
>T : Symbol(T, Decl(templateLiteralTypes6.ts, 7, 11))
23+
24+
declare function f1<
25+
>f1 : Symbol(f1, Decl(templateLiteralTypes6.ts, 7, 33))
26+
27+
Scope extends Keyof<Registry>,
28+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 9, 20))
29+
>Keyof : Symbol(Keyof, Decl(templateLiteralTypes6.ts, 5, 2))
30+
>Registry : Symbol(Registry, Decl(templateLiteralTypes6.ts, 0, 0))
31+
32+
Event extends Keyof<Registry[Scope]>,
33+
>Event : Symbol(Event, Decl(templateLiteralTypes6.ts, 10, 32))
34+
>Keyof : Symbol(Keyof, Decl(templateLiteralTypes6.ts, 5, 2))
35+
>Registry : Symbol(Registry, Decl(templateLiteralTypes6.ts, 0, 0))
36+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 9, 20))
37+
38+
>(eventPath: `${Scope}:${Event}`): void;
39+
>eventPath : Symbol(eventPath, Decl(templateLiteralTypes6.ts, 12, 2))
40+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 9, 20))
41+
>Event : Symbol(Event, Decl(templateLiteralTypes6.ts, 10, 32))
42+
43+
function f2<
44+
>f2 : Symbol(f2, Decl(templateLiteralTypes6.ts, 12, 40))
45+
46+
Scope extends Keyof<Registry>,
47+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 14, 12))
48+
>Keyof : Symbol(Keyof, Decl(templateLiteralTypes6.ts, 5, 2))
49+
>Registry : Symbol(Registry, Decl(templateLiteralTypes6.ts, 0, 0))
50+
51+
Event extends Keyof<Registry[Scope]>,
52+
>Event : Symbol(Event, Decl(templateLiteralTypes6.ts, 15, 32))
53+
>Keyof : Symbol(Keyof, Decl(templateLiteralTypes6.ts, 5, 2))
54+
>Registry : Symbol(Registry, Decl(templateLiteralTypes6.ts, 0, 0))
55+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 14, 12))
56+
57+
>(scope: Scope, event: Event) {
58+
>scope : Symbol(scope, Decl(templateLiteralTypes6.ts, 17, 2))
59+
>Scope : Symbol(Scope, Decl(templateLiteralTypes6.ts, 14, 12))
60+
>event : Symbol(event, Decl(templateLiteralTypes6.ts, 17, 15))
61+
>Event : Symbol(Event, Decl(templateLiteralTypes6.ts, 15, 32))
62+
63+
f1(`${scope}:${event}`);
64+
>f1 : Symbol(f1, Decl(templateLiteralTypes6.ts, 7, 33))
65+
>scope : Symbol(scope, Decl(templateLiteralTypes6.ts, 17, 2))
66+
>event : Symbol(event, Decl(templateLiteralTypes6.ts, 17, 15))
67+
}
68+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/conformance/types/literal/templateLiteralTypes6.ts] ////
2+
3+
=== templateLiteralTypes6.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/56659
5+
6+
type Registry = {
7+
>Registry : { a: { a1: {};}; b: { b1: {};}; }
8+
9+
a: { a1: {} };
10+
>a : { a1: {}; }
11+
>a1 : {}
12+
13+
b: { b1: {} };
14+
>b : { b1: {}; }
15+
>b1 : {}
16+
17+
};
18+
19+
type Keyof<T> = keyof T & string;
20+
>Keyof : Keyof<T>
21+
22+
declare function f1<
23+
>f1 : <Scope extends Keyof<Registry>, Event extends Keyof<Registry[Scope]>>(eventPath: `${Scope}:${Event}`) => void
24+
25+
Scope extends Keyof<Registry>,
26+
Event extends Keyof<Registry[Scope]>,
27+
>(eventPath: `${Scope}:${Event}`): void;
28+
>eventPath : `${Scope}:${Event}`
29+
30+
function f2<
31+
>f2 : <Scope extends Keyof<Registry>, Event extends Keyof<Registry[Scope]>>(scope: Scope, event: Event) => void
32+
33+
Scope extends Keyof<Registry>,
34+
Event extends Keyof<Registry[Scope]>,
35+
>(scope: Scope, event: Event) {
36+
>scope : Scope
37+
>event : Event
38+
39+
f1(`${scope}:${event}`);
40+
>f1(`${scope}:${event}`) : void
41+
>f1 : <Scope_1 extends Keyof<Registry>, Event_1 extends Keyof<Registry[Scope_1]>>(eventPath: `${Scope_1}:${Event_1}`) => void
42+
>`${scope}:${event}` : `${Scope}:${Event}`
43+
>scope : Scope
44+
>event : Event
45+
}
46+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strict: true
2+
// @target: esnext
3+
// @noEmit: true
4+
5+
// https://github.com/microsoft/TypeScript/issues/55364
6+
interface TypeMap {
7+
a: "A";
8+
b: "B";
9+
}
10+
declare const f: <T0 extends "a" | "b">(x: `${T0}`) => TypeMap[T0];
11+
type F1 = <T1 extends "a" | "b">(x: `${T1}`) => TypeMap[T1];
12+
const f1: F1 = f;
13+
type F2 = <T2 extends 'a' | 'b'>(x: `${T2}`) => TypeMap[`${T2}`]
14+
const f2: F2 = f
15+
16+
function f3<T3 extends "a" | "b">(x: T3) {
17+
const test1: `${T3}` = x
18+
const test2: T3 = "" as `${T3}`;
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @strict: true
2+
// @target: esnext
3+
// @noEmit: true
4+
5+
// https://github.com/microsoft/TypeScript/issues/56659
6+
7+
type Registry = {
8+
a: { a1: {} };
9+
b: { b1: {} };
10+
};
11+
12+
type Keyof<T> = keyof T & string;
13+
14+
declare function f1<
15+
Scope extends Keyof<Registry>,
16+
Event extends Keyof<Registry[Scope]>,
17+
>(eventPath: `${Scope}:${Event}`): void;
18+
19+
function f2<
20+
Scope extends Keyof<Registry>,
21+
Event extends Keyof<Registry[Scope]>,
22+
>(scope: Scope, event: Event) {
23+
f1(`${scope}:${event}`);
24+
}

0 commit comments

Comments
 (0)