From 7d02a77491d1abcbae3cc93464c30e6a0e2436f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 5 Jan 2023 01:09:51 +0100 Subject: [PATCH 1/3] Dont apply template string mappings on generic template literal types --- src/compiler/checker.ts | 2 +- .../reference/genericStringMappings.symbols | 79 +++++++++++++++++++ .../reference/genericStringMappings.types | 33 ++++++++ .../types/literal/genericStringMappings.ts | 28 +++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/genericStringMappings.symbols create mode 100644 tests/baselines/reference/genericStringMappings.types create mode 100644 tests/cases/conformance/types/literal/genericStringMappings.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c1ad31d43ff6..a87a0b775cb3b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16720,7 +16720,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getStringMappingType(symbol: Symbol, type: Type): Type { return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : - type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : + type.flags & TypeFlags.TemplateLiteral && !isGenericType(type) ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : // Mapping> === Mapping type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type : type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : diff --git a/tests/baselines/reference/genericStringMappings.symbols b/tests/baselines/reference/genericStringMappings.symbols new file mode 100644 index 0000000000000..081bfbba4be09 --- /dev/null +++ b/tests/baselines/reference/genericStringMappings.symbols @@ -0,0 +1,79 @@ +=== tests/cases/conformance/types/literal/genericStringMappings.ts === +// repro from #52102#issuecomment-1371248589 + +type CamelCase1< +>CamelCase1 : Symbol(CamelCase1, Decl(genericStringMappings.ts, 0, 0)) + + S extends string, +>S : Symbol(S, Decl(genericStringMappings.ts, 2, 16)) + + L extends string = Lowercase, +>L : Symbol(L, Decl(genericStringMappings.ts, 3, 19)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(genericStringMappings.ts, 2, 16)) + + Res extends string = "" +>Res : Symbol(Res, Decl(genericStringMappings.ts, 4, 34)) + +> = L extends "" +>L : Symbol(L, Decl(genericStringMappings.ts, 3, 19)) + + ? `start_${Uncapitalize}_end` +>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --)) +>Res : Symbol(Res, Decl(genericStringMappings.ts, 4, 34)) + + : CamelCase1}`>; +>CamelCase1 : Symbol(CamelCase1, Decl(genericStringMappings.ts, 0, 0)) +>Res : Symbol(Res, Decl(genericStringMappings.ts, 4, 34)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>L : Symbol(L, Decl(genericStringMappings.ts, 3, 19)) + +type Test1 = CamelCase1<"ABC"> +>Test1 : Symbol(Test1, Decl(genericStringMappings.ts, 8, 52)) +>CamelCase1 : Symbol(CamelCase1, Decl(genericStringMappings.ts, 0, 0)) + +// repro from #52102 + +type CamelCase2< +>CamelCase2 : Symbol(CamelCase2, Decl(genericStringMappings.ts, 10, 30)) + + S extends string, +>S : Symbol(S, Decl(genericStringMappings.ts, 14, 16)) + + L extends string = Lowercase, +>L : Symbol(L, Decl(genericStringMappings.ts, 15, 19)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(genericStringMappings.ts, 14, 16)) + + Res extends string = "" +>Res : Symbol(Res, Decl(genericStringMappings.ts, 16, 34)) + +> = L extends "" +>L : Symbol(L, Decl(genericStringMappings.ts, 15, 19)) + + ? Uncapitalize +>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --)) +>Res : Symbol(Res, Decl(genericStringMappings.ts, 16, 34)) + + : L extends `${infer H}_${infer T}` +>L : Symbol(L, Decl(genericStringMappings.ts, 15, 19)) +>H : Symbol(H, Decl(genericStringMappings.ts, 20, 22)) +>T : Symbol(T, Decl(genericStringMappings.ts, 20, 33)) + + ? CamelCase2}`> +>CamelCase2 : Symbol(CamelCase2, Decl(genericStringMappings.ts, 10, 30)) +>T : Symbol(T, Decl(genericStringMappings.ts, 20, 33)) +>Res : Symbol(Res, Decl(genericStringMappings.ts, 16, 34)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>H : Symbol(H, Decl(genericStringMappings.ts, 20, 22)) + + : CamelCase2}`>; +>CamelCase2 : Symbol(CamelCase2, Decl(genericStringMappings.ts, 10, 30)) +>Res : Symbol(Res, Decl(genericStringMappings.ts, 16, 34)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>L : Symbol(L, Decl(genericStringMappings.ts, 15, 19)) + +type Test2 = CamelCase2<"FOOBAR"> +>Test2 : Symbol(Test2, Decl(genericStringMappings.ts, 22, 52)) +>CamelCase2 : Symbol(CamelCase2, Decl(genericStringMappings.ts, 10, 30)) + diff --git a/tests/baselines/reference/genericStringMappings.types b/tests/baselines/reference/genericStringMappings.types new file mode 100644 index 0000000000000..c2a4db18cea71 --- /dev/null +++ b/tests/baselines/reference/genericStringMappings.types @@ -0,0 +1,33 @@ +=== tests/cases/conformance/types/literal/genericStringMappings.ts === +// repro from #52102#issuecomment-1371248589 + +type CamelCase1< +>CamelCase1 : CamelCase1 + + S extends string, + L extends string = Lowercase, + Res extends string = "" +> = L extends "" + ? `start_${Uncapitalize}_end` + : CamelCase1}`>; + +type Test1 = CamelCase1<"ABC"> +>Test1 : "start_abc_end" + +// repro from #52102 + +type CamelCase2< +>CamelCase2 : CamelCase2 + + S extends string, + L extends string = Lowercase, + Res extends string = "" +> = L extends "" + ? Uncapitalize + : L extends `${infer H}_${infer T}` + ? CamelCase2}`> + : CamelCase2}`>; + +type Test2 = CamelCase2<"FOOBAR"> +>Test2 : "foobar" + diff --git a/tests/cases/conformance/types/literal/genericStringMappings.ts b/tests/cases/conformance/types/literal/genericStringMappings.ts new file mode 100644 index 0000000000000..76752ad7b024b --- /dev/null +++ b/tests/cases/conformance/types/literal/genericStringMappings.ts @@ -0,0 +1,28 @@ +// @strict: true +// @noEmit: true + +// repro from #52102#issuecomment-1371248589 + +type CamelCase1< + S extends string, + L extends string = Lowercase, + Res extends string = "" +> = L extends "" + ? `start_${Uncapitalize}_end` + : CamelCase1}`>; + +type Test1 = CamelCase1<"ABC"> + +// repro from #52102 + +type CamelCase2< + S extends string, + L extends string = Lowercase, + Res extends string = "" +> = L extends "" + ? Uncapitalize + : L extends `${infer H}_${infer T}` + ? CamelCase2}`> + : CamelCase2}`>; + +type Test2 = CamelCase2<"FOOBAR"> \ No newline at end of file From fc81abded062cbaf1bcf91fd9854405a0b891165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 5 Jan 2023 08:29:32 +0100 Subject: [PATCH 2/3] update baselines --- tests/baselines/reference/intrinsicTypes.types | 4 ++-- .../reference/numericStringLiteralTypes.types | 2 +- tests/baselines/reference/templateLiteralTypes3.types | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/intrinsicTypes.types b/tests/baselines/reference/intrinsicTypes.types index b054269683432..b29f5b32936af 100644 --- a/tests/baselines/reference/intrinsicTypes.types +++ b/tests/baselines/reference/intrinsicTypes.types @@ -72,13 +72,13 @@ type TN6 = Uncapitalize<42>; // Error >TN6 : 42 type TX1 = Uppercase<`aB${S}`>; ->TX1 : `AB${Uppercase}` +>TX1 : Uppercase<`aB${S}`> type TX2 = TX1<'xYz'>; // "ABXYZ" >TX2 : "ABXYZ" type TX3 = Lowercase<`aB${S}`>; ->TX3 : `ab${Lowercase}` +>TX3 : Lowercase<`aB${S}`> type TX4 = TX3<'xYz'>; // "abxyz" >TX4 : "abxyz" diff --git a/tests/baselines/reference/numericStringLiteralTypes.types b/tests/baselines/reference/numericStringLiteralTypes.types index e22186758f727..245a3a674ba8e 100644 --- a/tests/baselines/reference/numericStringLiteralTypes.types +++ b/tests/baselines/reference/numericStringLiteralTypes.types @@ -12,7 +12,7 @@ type T3 = string & `${T}`; // `${T} >T3 : `${T}` type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` ->T4 : `${Capitalize}` +>T4 : `${Capitalize<`${T}`>}` function f1(a: boolean[], x: `${number}`) { >f1 : (a: boolean[], x: `${number}`) => void diff --git a/tests/baselines/reference/templateLiteralTypes3.types b/tests/baselines/reference/templateLiteralTypes3.types index 3fb7144050bcf..66a36048f07af 100644 --- a/tests/baselines/reference/templateLiteralTypes3.types +++ b/tests/baselines/reference/templateLiteralTypes3.types @@ -568,8 +568,8 @@ function ft1(t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, >ft1 : (t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) => void >t : T >u : Uppercase ->u1 : `1.${Uppercase}.3` ->u2 : `1.${Uppercase}.4` +>u1 : Uppercase<`1.${T}.3`> +>u2 : Uppercase<`1.${T}.4`> spread(`1.${t}.3`, `1.${t}.4`); >spread(`1.${t}.3`, `1.${t}.4`) : `1.${T}.3` | `1.${T}.4` @@ -588,9 +588,9 @@ function ft1(t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, >u : Uppercase spread(u1, u2); ->spread(u1, u2) : `1.${Uppercase}.3` | `1.${Uppercase}.4` +>spread(u1, u2) : Uppercase<`1.${T}.3`> | Uppercase<`1.${T}.4`> >spread :

(...args: P[]) => P ->u1 : `1.${Uppercase}.3` ->u2 : `1.${Uppercase}.4` +>u1 : Uppercase<`1.${T}.3`> +>u2 : Uppercase<`1.${T}.4`> } From e38b70e25e7b1b74774a04d7af1b7a3260b43438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 15 Jan 2023 00:43:27 +0100 Subject: [PATCH 3/3] Apply template string mappings on generic template literal types in a safe way for (Un)Capitalize Co-authored-by: Devansh Jethmalani --- src/compiler/checker.ts | 23 +++++++++++++------ .../baselines/reference/intrinsicTypes.types | 4 ++-- .../reference/numericStringLiteralTypes.types | 2 +- .../reference/templateLiteralTypes3.types | 10 ++++---- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a87a0b775cb3b..c1e90568203cc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16720,7 +16720,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getStringMappingType(symbol: Symbol, type: Type): Type { return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : - type.flags & TypeFlags.TemplateLiteral && !isGenericType(type) ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : + type.flags & TypeFlags.TemplateLiteral ? applyTemplateStringMapping(symbol, type as TemplateLiteralType) : // Mapping> === Mapping type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type : type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : @@ -16739,14 +16739,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return str; } - function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] { + function applyTemplateStringMapping(symbol: Symbol, type: TemplateLiteralType): Type { + const { texts, types } = type; switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { - case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))]; - case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))]; - case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; - case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; + case IntrinsicTypeKind.Uppercase: return getTemplateLiteralType(texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))); + case IntrinsicTypeKind.Lowercase: return getTemplateLiteralType(texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))); + case IntrinsicTypeKind.Capitalize: + case IntrinsicTypeKind.Uncapitalize: + return texts[0] === "" && types.length + ? types.length === 1 && texts[1] === "" + ? getStringMappingType(symbol, types[0]) + : getStringMappingTypeForGenericType(symbol, type) + : getTemplateLiteralType( + [applyStringMapping(symbol, texts[0]), ...texts.slice(1)], + types + ); } - return [texts, types]; + return getTemplateLiteralType(texts, types); } function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { diff --git a/tests/baselines/reference/intrinsicTypes.types b/tests/baselines/reference/intrinsicTypes.types index b29f5b32936af..b054269683432 100644 --- a/tests/baselines/reference/intrinsicTypes.types +++ b/tests/baselines/reference/intrinsicTypes.types @@ -72,13 +72,13 @@ type TN6 = Uncapitalize<42>; // Error >TN6 : 42 type TX1 = Uppercase<`aB${S}`>; ->TX1 : Uppercase<`aB${S}`> +>TX1 : `AB${Uppercase}` type TX2 = TX1<'xYz'>; // "ABXYZ" >TX2 : "ABXYZ" type TX3 = Lowercase<`aB${S}`>; ->TX3 : Lowercase<`aB${S}`> +>TX3 : `ab${Lowercase}` type TX4 = TX3<'xYz'>; // "abxyz" >TX4 : "abxyz" diff --git a/tests/baselines/reference/numericStringLiteralTypes.types b/tests/baselines/reference/numericStringLiteralTypes.types index 245a3a674ba8e..e22186758f727 100644 --- a/tests/baselines/reference/numericStringLiteralTypes.types +++ b/tests/baselines/reference/numericStringLiteralTypes.types @@ -12,7 +12,7 @@ type T3 = string & `${T}`; // `${T} >T3 : `${T}` type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` ->T4 : `${Capitalize<`${T}`>}` +>T4 : `${Capitalize}` function f1(a: boolean[], x: `${number}`) { >f1 : (a: boolean[], x: `${number}`) => void diff --git a/tests/baselines/reference/templateLiteralTypes3.types b/tests/baselines/reference/templateLiteralTypes3.types index 66a36048f07af..3fb7144050bcf 100644 --- a/tests/baselines/reference/templateLiteralTypes3.types +++ b/tests/baselines/reference/templateLiteralTypes3.types @@ -568,8 +568,8 @@ function ft1(t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, >ft1 : (t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) => void >t : T >u : Uppercase ->u1 : Uppercase<`1.${T}.3`> ->u2 : Uppercase<`1.${T}.4`> +>u1 : `1.${Uppercase}.3` +>u2 : `1.${Uppercase}.4` spread(`1.${t}.3`, `1.${t}.4`); >spread(`1.${t}.3`, `1.${t}.4`) : `1.${T}.3` | `1.${T}.4` @@ -588,9 +588,9 @@ function ft1(t: T, u: Uppercase, u1: Uppercase<`1.${T}.3`>, >u : Uppercase spread(u1, u2); ->spread(u1, u2) : Uppercase<`1.${T}.3`> | Uppercase<`1.${T}.4`> +>spread(u1, u2) : `1.${Uppercase}.3` | `1.${Uppercase}.4` >spread :

(...args: P[]) => P ->u1 : Uppercase<`1.${T}.3`> ->u2 : Uppercase<`1.${T}.4`> +>u1 : `1.${Uppercase}.3` +>u2 : `1.${Uppercase}.4` }