Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17196,7 +17196,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 ? applyTemplateStringMapping(symbol, type as TemplateLiteralType) :
// Mapping<Mapping<T>> === Mapping<T>
type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type :
type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
Expand All @@ -17215,14 +17215,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 {
Expand Down
81 changes: 81 additions & 0 deletions tests/baselines/reference/genericStringMappings.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//// [tests/cases/conformance/types/literal/genericStringMappings.ts] ////

=== 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<S>,
>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<Res>}_end`
>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --))
>Res : Symbol(Res, Decl(genericStringMappings.ts, 4, 34))

: CamelCase1<never, "", `${Res}${Capitalize<L>}`>;
>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<S>,
>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<Res>
>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<never, T, `${Res}${Capitalize<H>}`>
>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<never, "", `${Res}${Capitalize<L>}`>;
>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))

35 changes: 35 additions & 0 deletions tests/baselines/reference/genericStringMappings.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//// [tests/cases/conformance/types/literal/genericStringMappings.ts] ////

=== genericStringMappings.ts ===
// repro from #52102#issuecomment-1371248589

type CamelCase1<
>CamelCase1 : CamelCase1<S, L, Res>

S extends string,
L extends string = Lowercase<S>,
Res extends string = ""
> = L extends ""
? `start_${Uncapitalize<Res>}_end`
: CamelCase1<never, "", `${Res}${Capitalize<L>}`>;

type Test1 = CamelCase1<"ABC">
>Test1 : "start_abc_end"

// repro from #52102

type CamelCase2<
>CamelCase2 : CamelCase2<S, L, Res>

S extends string,
L extends string = Lowercase<S>,
Res extends string = ""
> = L extends ""
? Uncapitalize<Res>
: L extends `${infer H}_${infer T}`
? CamelCase2<never, T, `${Res}${Capitalize<H>}`>
: CamelCase2<never, "", `${Res}${Capitalize<L>}`>;

type Test2 = CamelCase2<"FOOBAR">
>Test2 : "foobar"

28 changes: 28 additions & 0 deletions tests/cases/conformance/types/literal/genericStringMappings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @strict: true
// @noEmit: true

// repro from #52102#issuecomment-1371248589

type CamelCase1<
S extends string,
L extends string = Lowercase<S>,
Res extends string = ""
> = L extends ""
? `start_${Uncapitalize<Res>}_end`
: CamelCase1<never, "", `${Res}${Capitalize<L>}`>;

type Test1 = CamelCase1<"ABC">

// repro from #52102

type CamelCase2<
S extends string,
L extends string = Lowercase<S>,
Res extends string = ""
> = L extends ""
? Uncapitalize<Res>
: L extends `${infer H}_${infer T}`
? CamelCase2<never, T, `${Res}${Capitalize<H>}`>
: CamelCase2<never, "", `${Res}${Capitalize<L>}`>;

type Test2 = CamelCase2<"FOOBAR">