Skip to content

Commit 12dbb98

Browse files
committed
Improved relationship checking for template literal types with a single placeholder and no extra texts
1 parent fd390e7 commit 12dbb98

File tree

4 files changed

+144
-3
lines changed

4 files changed

+144
-3
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21746,6 +21746,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2174621746
// For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string.
2174721747
instantiateType(source, reportUnreliableMapper);
2174821748
}
21749+
if (isTemplateLiteralTypeWithSinglePlaceholderAndNoExtraTexts(target) && isTypeAssignableTo(source, stringType) && isTypeAssignableTo(target.types[0], stringType)) {
21750+
return isRelatedTo(source, target.types[0], RecursionFlags.Both, reportErrors);
21751+
}
2174921752
if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) {
2175021753
return Ternary.True;
2175121754
}
@@ -21789,6 +21792,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2178921792
}
2179021793
else if (sourceFlags & TypeFlags.TemplateLiteral && !(targetFlags & TypeFlags.Object)) {
2179121794
if (!(targetFlags & TypeFlags.TemplateLiteral)) {
21795+
if (isTemplateLiteralTypeWithSinglePlaceholderAndNoExtraTexts(source) && isTypeAssignableTo(source.types[0], stringType) && isTypeAssignableTo(target, stringType)) {
21796+
return isRelatedTo(source.types[0], target, RecursionFlags.Both, reportErrors);
21797+
}
2179221798
const constraint = getBaseConstraintOfType(source);
2179321799
if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) {
2179421800
return result;
@@ -24366,6 +24372,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2436624372
return false;
2436724373
}
2436824374

24375+
function isTemplateLiteralTypeWithSinglePlaceholderAndNoExtraTexts(type: Type): type is TemplateLiteralType {
24376+
if (!(type.flags & TypeFlags.TemplateLiteral)) {
24377+
return false;
24378+
}
24379+
const texts = (type as TemplateLiteralType).texts;
24380+
return texts.length === 2 && texts[0] === "" && texts[1] === "";
24381+
}
24382+
2436924383
function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
2437024384
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
2437124385
return true;
@@ -24381,9 +24395,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2438124395
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target) ||
2438224396
target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType));
2438324397
}
24384-
if (source.flags & TypeFlags.TemplateLiteral) {
24385-
const texts = (source as TemplateLiteralType).texts;
24386-
return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target);
24398+
if (isTemplateLiteralTypeWithSinglePlaceholderAndNoExtraTexts(source)) {
24399+
return isTypeAssignableTo(source.types[0], target);
2438724400
}
2438824401
return isTypeAssignableTo(source, target);
2438924402
}
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: 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+
}

0 commit comments

Comments
 (0)