Template literal types and mapped type 'as' clauses

This PR implements two new features:

Template literal types, which are a form of string literals with embedded generic placeholders that can be substituted with actual string literals through type instantiation, and
Mapped type as clauses, which provide the ability to transform property names in mapped types.

Template literal types
Template literal types are the type space equivalent of template literal expressions. Similar to template literal expressions, template literal types are enclosed in backtick delimiters and can contain placeholders of the form ${T}, where T is a type that is assignable to string, number, boolean, or bigint. Template literal types provide the ability to concatenate literal strings, convert literals of non-string primitive types to their string representation, and change the capitalization or casing of string literals. Furthermore, through type inference, template literal types provide a simple form of string pattern matching and decomposition.
Template literal types are resolved as follows:

Union types in placeholders are distributed over the template literal type. For example `[${A|B|C}]` resolves to `[${A}]` | `[${B}]` | `[${C}]`. Union types in multiple placeholders resolve to the cross product. For example `[${A|B},${C|D}]` resolves to `[${A},${C}]` | `[${A},${D}]` | `[${B},${C}]` | `[${B},${D}]`.
String, number, boolean, and bigint literal types in placeholders cause the placeholder to be replaced with the string representation of the literal type. For example `[${'abc'}]` resolves to `[abc]` and `[${42}]` resolves to `[42]`.
Any one of the types any, string, number, boolean, or bigint in a placeholder causes the template literal to resolve to type string.
The type never type in a placeholder causes the template literal to resolve to never.

Some examples:
type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;
type ToString<T extends string | number | boolean | bigint> = `${T}`;
type T0 = EventName<'foo'>;  // 'fooChanged'
type T1 = EventName<'foo' | 'bar' | 'baz'>;  // 'fooChanged' | 'barChanged' | 'bazChanged'
type T2 = Concat<'Hello', 'World'>;  // 'HelloWorld'
type T3 = `${'top' | 'bottom'}-${'left' | 'right'}`;  // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
type T4 = ToString<'abc' | 42 | true | -1234n>;  // 'abc' | '42' | 'true' | '-1234'

Beware that the cross product distribution of union types can quickly escalate into very large and costly types. Also note that union types are limited to less than 100,000 constituents, and the following will cause an error:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Zip = `${Digit}${Digit}${Digit}${Digit}${Digit}`;  // Error

~A template literal placeholder may optionally specify an uppercase, lowercase, capitalize, or uncapitalize modifier before the type. This modifier changes the casing of the entire replacement string or the first character of the replacement string. For example:~
EDIT: Based on feedback, the casing modifiers have been replaced by intrinsic string types in #40580.
type GetterName<T extends string> = `get${Capitalize<T>}`;
type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`;
type T10 = GetterName<'foo'>;  // 'getFoo'
type T11 = Cases<'bar'>;  // 'BAR bar Bar bar'
type T12 = Cases<'BAR'>;  // 'BAR bar BAR bAR'

Template literal types are all assignable to and subtypes of string. Furthermore, a template literal type `${T}` is assignable to and a subtype of a template literal type `${C}`, where C is a string literal type constraint of T. For example:
function test<T extends 'foo' | 'bar'>(name: `get${Capitalize<T>}`) {
    let s1: string = name;
    let s2: 'getFoo' | 'getBar' = name;
}

Type inference supports inferring from a string literal type to a template literal type. For inference to succeed the starting and ending literal character spans (if any) of the target must exactly match the starting and ending spans of the source. Inference proceeds by matching each placeholder to a substring in the source from left to right: A placeholder followed by a literal character span is matched by inferring zero or more characters from the source until the first occurrence of that literal character span in the source. A placeholder immediately followed by another placeholder is matched by inferring a single character from the source.
Some examples:
type MatchPair<S extends string> = S extends `[${infer A},${infer B}]` ? [A, B] : unknown;

type T20 = MatchPair<'[1,2]'>;  // ['1', '2']
type T21 = MatchPair<'[foo,bar]'>;  // ['foo', 'bar']
type T22 = MatchPair<' [1,2]'>;  // unknown
type T23 = MatchPair<'[123]'>;  // unknown
type T24 = MatchPair<'[1,2,3,4]'>;  // ['1', '2,3,4']

type FirstTwoAndRest<S extends string> = S extends `${infer A}${infer B}${infer R}` ? [`${A}${B}`, R] : unknown;

type T25 = FirstTwoAndRest<'abcde'>;  // ['ab', 'cde']
type T26 = FirstTwoAndRest<'ab'>;  // ['ab', '']
type T27 = FirstTwoAndRest<'a'>;  // unknown

Template literal types can be combined with recursive conditional types to write Join and Split types that iterate over repeated patterns.
type Join<T extends unknown[], D extends string> =
    T extends [] ? '' :
    T extends [string | number | boolean | bigint] ? `${T[0]}` :
    T extends [string | number | boolean | bigint, ...infer U] ? `${T[0]}${D}${Join<U, D>}` :
    string;
type T30 = Join<[1, 2, 3, 4], '.'>;  // '<IP_ADDRESS>'
type T31 = Join<['foo', 'bar', 'baz'], '-'>;  // 'foo-bar-baz'
type T32 = Join<[], '.'>;  // ''

type Split<S extends string, D extends string> =
    string extends S ? string[] :
    S extends '' ? [] :
    S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
    [S];

type T40 = Split<'foo', '.'>;  // ['foo']
type T41 = Split<'foo.bar.baz', '.'>;  // ['foo', 'bar', 'baz']
type T42 = Split<'foo.bar', ''>;  // ['f', 'o', 'o', '.', 'b', 'a', 'r']
type T43 = Split<any, '.'>;  // string[]

The recursive inference capabilities can for example be used to strongly type functions that access properties using "dotted paths", and pattern that is sometimes used in JavaScript frameworks.
type PropType<T, Path extends string> =
    string extends Path ? unknown :
    Path extends keyof T ? T[Path] :
    Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropType<T[K], R> : unknown :
    unknown;

declare function getPropValue<T, P extends string>(obj: T, path: P): PropType<T, P>;
declare const s: string;

const obj = { a: { b: {c: 42, d: 'hello' }}};
getPropValue(obj, 'a');  // { b: {c: number, d: string } }
getPropValue(obj, 'a.b');  // {c: number, d: string }
getPropValue(obj, 'a.b.d');  // string
getPropValue(obj, 'a.b.x');  // unknown
getPropValue(obj, s);  // unknown

Mapped type as clauses
With this PR, mapped types support an optional as clause through which a transformation of the generated property names can be specified:
{ [P in K as N]: X }

where N must be a type that is assignable to string | number | symbol. Typically, N is a type that transforms P, such as a template literal type that uses P in a placeholder. For example:
type Getters<T> = { [P in keyof T & string as `get${Capitalize<P>}`]: () => T[P] };
type T50 = Getters<{ foo: string, bar: number }>;  // { getFoo: () => string, getBar: () => number }

Above, a keyof T & string intersection is required because keyof T could contain symbol types that cannot be transformed using template literal types.
When the type specified in an as clause resolves to never, no property is generated for that key. Thus, an as clause can be used as a filter:
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
type T60 = Methods<{ foo(): number, bar: boolean }>;  // { foo(): number }

When the type specified in an as clause resolves to a union of literal types, multiple properties with the same type are generated:
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
type T70 = DoubleProp<{ a: string, b: number }>;  // { a1: string, a2: string, b1: number, b2: number }

Fixes #12754.

Playground: https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-88

Looks incredible! Nice work.

very cool, thanks for doing this! It will be tricky to use this well without exceeding the "50 steps" limit with libraries like mongoose, but still will enable a lot of great things

Also fixes #31422 if I'm not mistaken?

This looks really good! Will there be a way to add more modifiers? The times I’ve wanted this feature it’s been to convert from ALL_CAPS to camelCase.

@bschlenk It seems like that should be implementable by users since split/join can be implemented as shown in the PR description

Can we get a playground for this PR?
Not sure if this will work since I'm not a part of the TS team:
@typescript-bot pack this

@typescript-bot pack this

Is it possible to split a string literal type into a tuple of its characters? If you could split "foo" into ["f", "oo"] then you wouldn't need to special case capitalize and uncapitalize. (A typed camelCase function has always been my go-to example of something that's too complex for TS to type, but it seems very, very close!)

@danvk converting between camelCase, snake-case, PascalCase etc is already possible with this PR: here is a playground link

Thanks @rickbutton! I'm wondering if it's possible to go the other way, though: FooBar → foo-bar or foo_bar.

For the mapped type as clauses, the current behavior of compiler is:
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
type T70 = DoubleProp<{ a: string, b: number }>

type Keys = keyof T70  // ==> 'a' | 'b'

Is this intended? Why isn't 'a1' | 'a2' | 'b1' | 'b2'?

Thanks @rickbutton! I'm wondering if it's possible to go the other way, though: FooBar → foo-bar or foo_bar.

Yeah totally agree. It seems if we wanna play around with characters to build literal types we need somehow to either provide the minimum operations or support custom builders somehow...

Can fields be made optional or non-optional within the as clause of mapped types? For instance, what if we want to prevent the following from producing a type error? (the runtime will––in most cases––behave the same as if the entry ["c", undefined] existed within t after all).
type T = {
  a: "a";
  b: 1;
  c: undefined;
};

// produces type error (missing field `c`)
const t: T = {
  a: "a",
  b: 1,
}

It would be nice to be able to do the following:
type MakeUndefinedsOptional<T> = {
  [P in K as P[K] extends undefined ? K+? : K]: P[K];
}

type NewT = MakeUndefinedsOptional<T>;

// no longer produces type error
const t: NewT = {
  a: "a",
  b: 1,
}

I believe this would address #12754 as well.

Can fields be made optional or non-optional within the as clause of mapped types? For instance, what if we want to prevent the following from producing a type error? (the runtime will––in most cases––behave the same as if the entry ["c", undefined] existed within t after all).
type T = {
  a: "a";
  b: 1;
  c: undefined;
};

// produces type error (missing field `c`)
const t: T = {
  a: "a",
  b: 1,
}

It would be nice to be able to do the following:
type MakeUndefinedsOptional<T> = {
  [P in K as P[K] extends undefined ? K+? : K]: P[K];
}

type NewT = MakeUndefinedsOptional<T>;

// no longer produces type error
const t: NewT = {
  a: "a",
  b: 1,
}

I believe this would address #12754 as well.

Having two ? would make it harder for parsing. Maybe adding the optional keys via wrapping it with another mapped type would be possible?

Allowing something like S extends ${infer T}${...infer U}to split off the first character of a string literal type would be nicely analogous to[infer T, ...infer U]` for tuple types. @aminpaks

Allowing something like S extends ${infer T}${...infer U}to split off the first character of a string literal type would be nicely analogous to[infer T, ...infer U]` for tuple types. @aminpaks

Right, and then how would you operate to change from lowercased to upper?

Is this intended? Why isn't 'a1' | 'a2' | 'b1' | 'b2'?

Because it isn't working right. 😮 I will get it fixed.

Can fields be made optional or non-optional within the as clause of mapped types?

No, that would be a separate feature.

Allowing something like S extends ${infer T}${...infer U} to split off the first character of a string literal type would be nicely analogous to [infer T, ...infer U] for tuple types.

It's a possibility, but it seems a arbitrary to pick the first character when there's no indication to that effect. Why not pick the entire string for T and an empty string for U? Or vice versa? Currently we make no inference because there's no single correct answer.

This is awesome ! Thanks Anders ! :D

Regarding conversion from ALL_CAPS to camelCase, here are a couple of types I was toying with:
type SnakeToCamelCase<S extends string> =
    S extends `${infer T}_${infer U}` ? `${lowercase T}${SnakeToPascalCase<U>}` :
    S extends `${infer T}` ? `${lowercase T}` :
    SnakeToPascalCase<S>;

type SnakeToPascalCase<S extends string> =
    string extends S ? string :
    S extends `${infer T}_${infer U}` ? `${capitalize `${lowercase T}`}${SnakeToPascalCase<U>}` :
    S extends `${infer T}` ? `${capitalize `${lowercase T}`}` :
    never;

type T0 = SnakeToPascalCase<'hello_world_foo'>;  // 'HelloWorldFoo'
type T1 = SnakeToPascalCase<'FOO_BAR_BAZ'>;  // 'FooBarBaz'
type T2 = SnakeToCamelCase<'hello_world_foo'>;  // 'helloWorldFoo'
type T3 = SnakeToCamelCase<'FOO_BAR_BAZ'>;  // 'fooBarBaz'

It currently isn't possible to write the opposite mappings because there's no way to match on "first uppercase letter" or "first lowercase letter" or other such patterns in inference. There's just an enormously long tail of possible patterns you might want to match on, and ultimately I think the only way to address this would be to introduce RegEx string types with an ability to turn capture groups into inferences. That's definitely beyond the scope of this PR.

@ahejlsberg thanks for clarifying the cameCase mapping! I was trying to make it work with no luck
type Split<T> = T extends `${infer A}${uppercase infer B}` ? [A, B] : [T]
type aB = Split<'aB'>

I know this is not in the scope of this PR, but what would make to-camelCase conversion possible is an extends modifier for the infer keyword. It would also enable clear indication of inferring only one letter (although not for strings with limited range of characters).
type VariableChar = 'a' | 'b' .... 'z';
type camelToSnake = S extends `${infer T extends uppercase VariableChar}${infer U}` ? `${lowercase T}${camelToSnake<U>}` :
    S extends `${infer U}${infer T extends uppercase VariableChar}${infer V}` ? `${U}$_{camelToSnake<`${T}${V}` :
    camelToSnake<S>;

If I'm not mistaken the code atm does not work on all edge cases (like singleLetterCAMEL => single_letter_c_a_m_el). But it should be easily extendable, just not on my phone...

I've created some utilities types for manipulating strings: Playground. Feel free to use.
However, I'm not able to make TrimEnd currently.

This is really cool to see!
I'm not clear whether this is expected or not, but there's something unintuitive (at least to me) going on with inference against an anchor at the end of a string.
type EndsWithS<T extends string> = T extends `${infer _}s` ? true : false;

type Egg = EndsWithS<'egg'>;   // false
type Peas = EndsWithS<'peas'>; // true
type Mess = EndsWithS<'mess'>; // false, but should be true?

https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-8#code/C4TwDgpgBAogdgEwM4HUCWwAWBlAPAFSggA9gJEkolgAnNOAcwD4oBeKQksiqAAwBIA3vQBmEGlAD6AXyS8oAfii0ArtABcUEQEMANkggBuALAAoM6EiwGDNrAroseAOQQbzpoajeA9D616Bhbg0AAKENqU7PDIjji4zpCRHl5+yjRqwVYAshBIUfaxGPHOALZ5SClQaTr6EAA0UABGKsBUmAD2KroIzdCqEApmQA

I'd like to see this feature soon ❤

It's exciting to see that @ahejlsberg and his team are still pushing like crazy and shipping one feature after the other!
However and I don't want to be that guy but what's about long-term must-haves like full ES6 module support where quite some people are waiting for, eg. https://github.com/microsoft/TypeScript/issues/18442?

Hi forks.
I have some funny try on this one:
type ParseChar<Str, Ch extends string> = Str extends `${Ch}${infer Rest}` ? [Ch, Rest] : never

type isNever<T> = [T] extends [never] ? true : false

type Alpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

type Digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"

type DoubleQuote = '"'

type Whitespace = " "

type Operator = "+"

type ParseMany<Str, Target extends string, Prev extends string = ""> =
    Str extends "" ? [Prev, ""] :
    Str extends `${Target}${infer Rest}` ?
    Str extends `${infer Ch}${Rest}` ? Ch extends string ? ParseMany<Rest, Target, `${Prev}${Ch}`> : never : never
    : [Prev, Str];

type TString<V> = { str_value: V }
type TNumber<V> = { num_value: V }
type TOp<V> = { op_value: V }
type TWhiteSpace<V> = { ws_value: V }
type TBinary<L, O, R> = { left_value: L, op_value: O, right_value: R }

type ParseCharList<Str> = ParseMany<Str, Alpha, "">
type ParseNumber<Str> = ParseMany<Str, Digit, "">
type ParseWhiteSpace<Str> = ParseMany<Str, Whitespace, "">
type ParseOp<Str> = ParseChar<Str, Operator>

type ParseString<Str> =
    isNever<ParseChar<Str, DoubleQuote>> extends false ? ParseChar<Str, DoubleQuote> extends [infer LP, infer Rest] ?
    isNever<ParseCharList<Rest>> extends false ? ParseCharList<Rest> extends [infer Result, infer SRest] ?
    isNever<ParseChar<SRest, DoubleQuote>> extends false ? ParseChar<SRest, DoubleQuote> extends [infer RP, infer FRest] ? [Result, FRest] :
    never : never : never : never : never : never

type ParseFactor<Str> =
    | ParseString<Str>
    | ParseNumber<Str>

type ParseBinary<Str> =
    isNever<ParseFactor<Str>> extends false ? ParseFactor<Str> extends [infer L, infer LRest] ?
    isNever<ParseOp<LRest>> extends false ? (
        ParseOp<LRest> extends [infer Op, infer OpRest] ?
        (
            isNever<ParseBinary<OpRest>> extends false ? (
                ParseBinary<OpRest> extends [infer R, infer RRest] ? [TBinary<L, Op, R>, RRest] : never
            ) : never
        ) : never
    ) : [L, LRest] : never : never

type FilterEmptyAndNever<T> = T extends [infer Result, infer Rest] ?
    Rest extends "" ? Result : never : never

type Parse<Str> = FilterEmptyAndNever<ParseBinary<Str>>

type toString<T> = T extends string ? T : never

type EvalBinary<L, OP, R> = OP extends "+" ? `${toString<L>}${toString<R>}` : never

type AST = string | TBinary<any, Operator, any>

type Eval<Tree> =
    Tree extends AST ?
    Tree extends TBinary<infer L, infer Op, infer R> ? EvalBinary<Eval<L>, Op, Eval<R>> : toString<Tree> : never

type Code = "1111+232+1321+3123"

type Tree = Parse<Code>

type Result = Eval<Tree> //<PHONE_NUMBER>13123

You can see here

It would be nice if a template string could infer its template string type without using a helper function or explicit annotation. Perhaps with a const assertion?
const prefix = 'FOO'; // 'foo'
const str = `${prefix}_BAR` as const; // 'FOO_BAR'

function transform<T extends string>(prefix: T) { // Return type inferred to `${T}_BAR`
  return `${prefix}_BAR` as const;
}

I suppose this PR obsolesces regex-validated string types but i ain’t complaining!

type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex,G extends Hex,B extends Hex>(color:`#${R}${G}${B}`){
    return {
        red: color[1] as R,
        green: color[2] as G,
        blue: color[3] as B,
    }
}
// Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
const c = getColorFromString('#FFF');

Playground Link

Small terminology nitpick:

`abc` is a a template literal: https://tc39.es/ecma262/#sec-template-literal-lexical-components

The term template string is used in the context of tagged templates such as tagFunc`ab${123}cd` . The input of tagFunc is:

The template strings ['ab', 'cd']. https://tc39.es/ecma262/#sec-static-semantics-templatestrings
The substitution value 123

I played with this a little bit and got close to splitting camelCase strings:
type SplitCamelCase<S extends string> =
    S extends `${infer First}A${infer Rest}` ?
    [...SplitCamelCase<First>, `A${Rest}`]
    :
    S extends `${infer First}B${infer Rest}` ?
    [...SplitCamelCase<First>, `B${Rest}`]
    :
    // all letters...
   [S]

Playground
It stops after two words and I'm getting this error:

Type instantiation is excessively deep and possibly infinite.(2589)

type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex,G extends Hex,B extends Hex>(color:`#${R}${G}${B}`){
    return {
        red: color[1] as R,
        green: color[2] as G,
        blue: color[3] as B,
    }
}
// Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
const c = getColorFromString('#FFF');

Playground Link

You need a separator to make it work

Hello,
I found a problem related to removing the trailing space. The recursive type doesn't seem to work if there is a consecutive string at the end.
type TrimL<S, T = ''> = S extends '' ? T : S extends ` ${infer R}` ? TrimL<R> : S
type TrimR<S, T = ''> = S extends '' ? T : S extends `${infer R} ` ? TrimR<R> : S

const trimL1: TrimL<' A'> = 'A' // OK
const trimL2: TrimL<'  A'> = 'A' // OK
const trimR1: TrimR<'A '> = 'A' // OK
const trimR2: TrimR<'A  '> = 'A' // Type '"A"' is not assignable to type '"A  "'

Playground

Hello,
I found a problem related to removing the trailing space. The recursive type doesn't seem to work if there is a consecutive string at the end.
type TrimL<S> = S extends ` ${infer R}` ? TrimL<R> : S
type TrimR<S> = S extends `${infer R} ` ? TrimR<R> : S

const trimL1: TrimL<' A'> = 'A' // OK
const trimL2: TrimL<'  A'> = 'A' // OK
const trimR1: TrimR<'A '> = 'A' // OK
const trimR2: TrimR<'A  '> = 'A' // Type '"A"' is not assignable to type '"A  "'

Playground

Looks like I found a bug 🤔
type A = "aa" extends `${infer X}a` ? X : never;
// Expected A to be "a" but is never

Playground

type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex,G extends Hex,B extends Hex>(color:`#${R}${G}${B}`){
    return {
        red: color[1] as R,
        green: color[2] as G,
        blue: color[3] as B,
    }
}
// Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
const c = getColorFromString('#FFF');

Playground Link

cc @xiaoxiangmoe
color("#ABC")
color("#PQR")
color("#1")
color("#E8E8")
color("#E8E8E8")
color("#111") // bug in typescript itself https://github.com/microsoft/TypeScript/pull/40336#issuecomment-684948382

declare const color:
  <S extends string, E extends (
    IsColor<S> extends true
      ? []
      : [(message: `${S} is not a valid color`) => void]
  )>(s: S, ...error: E) => void

type test = Characters<"#ABC", ColorAlphabet>

type IsColor<S extends string, Cs = Characters<S, ColorAlphabet>> =
    [Cs] extends [never] ? false :
    Prop<Cs, 0> extends "#" ?
    Prop<Cs, "length"> extends 7 ? true :
    Prop<Cs, "length"> extends 4 ? true :
    false : false;

type ColorAlphabet = "#" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F"
type Characters<S extends string, A extends string> =
  S extends "" ? [] :
  S extends `${A}${infer R}` ? S extends `${infer C}${R}`
    ? [C, ...(Characters<R, A> extends infer X ? Cast<X, string[]> : never)]
    : never : never

type Prop<T, K> = K extends keyof T ? T[K] : never;
type Tail<T> = T extends [any, ...infer U] ? U : never;
type Cast<T, U> = T extends U ? T : U;

Playground

type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex,G extends Hex,B extends Hex>(color:`#${R}${G}${B}`){
    return {
        red: color[1] as R,
        green: color[2] as G,
        blue: color[3] as B,
    }
}
// Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
const c = getColorFromString('#FFF');

Playground Link

cc @xiaoxiangmoe @aminpaks
color("#ABC")
color("#PQR")
color("#1")
color("#E8E8")
color("#E8E8E8")
color("#111") // bug in typescript itself https://github.com/microsoft/TypeScript/pull/40336#issuecomment-684948382
color("asdfsda")

declare const color:
  <S extends string, E extends (
    IsColor<S> extends true
      ? []
      : [(message: `${S} is not a valid color`) => void]
  )>(s: S, ...error: E) => void

type IsColor<S extends string, Cs = Characters<S, ColorAlphabet>> =
    [Cs] extends [never] ? false :
    Prop<Cs, 0> extends "#" ?
    Prop<Cs, "length"> extends 7 ? true :
    Prop<Cs, "length"> extends 4 ? true :
    false : false;

type ColorAlphabet = "#" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F"
type Characters<S extends string, A extends string> =
  S extends "" ? [] :
  S extends `${A}${infer R}` ? S extends `${infer C}${R}`
    ? [C, ...(Characters<R, A> extends infer X ? Cast<X, string[]> : never)]
    : never : never

type Prop<T, K> = K extends keyof T ? T[K] : never;
type Tail<T> = T extends [any, ...infer U] ? U : never;
type Cast<T, U> = T extends U ? T : U;

Playground

Don't wanna undermine your efforts here, but is this kinda solution really something that TS team suggests everyone to do?

Thank you so much for implementing this, it's going to give my team safety in places where it's currently absent.
I came across an issue when inferring parameters to satisfy generic template string types in an interface:
type Template<T extends number> = `A${T}`;

interface Example2<T extends number> {
  propertyA: Template<T>,
  propertyB: Template<T>,
}

function inferExample2<T extends number>(obj: Example2<T>): Example2<T> {
  return obj;
}

const example2 = inferExample2({
  propertyA: "A1",
  propertyB: "B1",  // no complaints here!
})

Playground here

Don't wanna undermine your efforts here @devanshj, but is this kinda solution really something that TS team suggests everyone to do?

I think you got me wrong, I'm not suggesting anyone to use this... I'm doing it just for fun haha. I don't think TS team should expect people to do this! But I don't think it would be a big deal if library authors did it to give a nice IDE experience, I'm all for that.

It currently isn't possible to write the opposite mappings because there's no way to match on "first uppercase letter" or "first lowercase letter" or other such patterns in inference. There's just an enormously long tail of possible patterns you might want to match on, and ultimately I think the only way to address this would be to introduce RegEx string types with an ability to turn capture groups into inferences. That's definitely beyond the scope of this PR.

@ahejlsberg The long tail here is long indeed! That's what I find appealing about mapping onto tuples of characters: it would let you apply all the great machinery we have with recursive conditional types and variadic tuple types to string literal types.
Here's an implementation camelCase→snake_case using tuples and the mechanisms in this PR:
type Lowercase = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
type Uppercase = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';

type ToLower<S extends string> = `${lowercase S}`;

type ToSnake<T extends string[]> =
    T extends [infer A, infer B, ...infer Rest]
    ? Rest extends string[]
        ? A extends Uppercase
            ? B extends Lowercase
                ? ['_', ToLower<A>, B, ...ToSnake<Rest>]
                : [A, ...ToSnake<[B & string, ...Rest]>]
            : [A, ...ToSnake<[B & string, ...Rest]>]
        : never
    : T;

type fooBarBaz = ['f', 'o', 'o', 'B', 'a', 'r', 'B', 'a', 'z'];
type FooBarBaz = ToSnake<fooBarBaz>;
// type FooBarBaz = ["f", "o", "o", "_", "b", "a", "r", "_", "b", "a", "z"]

type Join<T extends (string | number | boolean | bigint)[], D extends string> =
    T extends [] ? '' :
    T extends [unknown] ? `${T[0]}` :
    T extends [unknown, ...infer U] ? `${T[0]}${D}${Join<U, D>}` :
    string;

type FooBarStr = Join<FooBarBaz, ''>;
// type FooBarStr = "foo_bar_baz"

(playground)
The only missing bit is a way to go from "fooBarBaz" → ['f', 'o', 'o', 'B', 'a', 'r', 'B', 'a', 'z'].

The only missing bit is a way to go from "fooBarBaz" → ['f', 'o', 'o', 'B', 'a', 'r', 'B', 'a', 'z']

@Kingwl already did it via ParseMany the only problem is his stuff is slightly hard to read :P
type test = Characters<"fooBarBaz", Alphabet>;

type Alphabet = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';
type Characters<S extends string, A extends string> =
  S extends "" ? [] :
  S extends `${A}${infer R}` ? S extends `${infer C}${R}`
    ? [C, ...(Characters<R, A> extends infer X ? Cast<X, string[]> : never)]
    : never : never

type Cast<T, U> = T extends U ? T : U;

Playground
But @ahejlsberg please let's have this: :pray:

Allowing something like S extends ${infer T}${...infer U} to split off the first character of a string literal type would be nicely analogous to [infer T, ...infer U] for tuple types.

The proposed template string types is a quite powerful feature that will surely enhance the flexibility and expressiveness of the type system, but it doesn’t feel right to me – I think that it’s too complex, doing too much and at the same time it’s not as generic/flexible as it might be.
The main thing that crossed the line for me is the case modifiers (uppercase, capitalize…). I think that this is nonsystematic, hack-ish and it’s opening a Pandora box. There are already proposals for other modifiers in this discussion, such as conversion between camelCase, kebab-case, snake_case etc. TS type system is already Turing complete and this PR takes it even further (just look at https://github.com/microsoft/TypeScript/pull/40336#issuecomment-684784211). Wouldn’t it be better to add support for the type-generating macros, using JS/TS syntax, instead of adding more and more keywords into the TS’ type syntax?
Another thing that doesn’t feel right to me is this:

Union types in multiple placeholders resolve to the cross product. For example [${A|B},${C|D}] resolves to [${A},${C}] | [${A},${D}] | [${B},${C}] | [${B},${D}].

I think that it makes it too easy to create a (hidden) combinatorial explosion. Less experienced users will shoot themselves into the foot.

May be instead of uppercase, lowercase, capitalize or uncapitalize is better to use a (subset of) known js string methods like .toUpperCase(). Although I find this feature very nice and powerful, I agree with the comment above.

type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex,G extends Hex,B extends Hex>(color:`#${R}${G}${B}`){
    return {
        red: color[1] as R,
        green: color[2] as G,
        blue: color[3] as B,
    }
}
// Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
const c = getColorFromString('#FFF');

Playground Link

@xiaoxiangmoe
type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'

function getColorFromString<R extends Hex[],>(color:`#${R[0]}${R[1]}${R[2]}`){
    return {
        red: color[1] as R[0],
        green: color[2] as R[1],
        blue: color[3] as R[2],
    }
}

const c = getColorFromString('#FFF');

It's great that the dotted object path is possible, but from looking at this, unless I'm mistaken, this can't be extended to support true OGNL-like paths with array indices, like some.path[0].field?

@alex-sherwin It's totally possible to support [] accessors. See this Playground. It's not 100% perfect but you can start there

So string types have better arithmetic operation support now! :smile:
// "123555554"
type Res3 = Add<"123456789", "98765">;

Playground

@alex-sherwin It's totally possible to support [] accessors

I meant using [] as array indexing, not as a property name, but making a minor adjustment to your example nearly works (by simply changing ["${infer R}"] to [${infer R}])
But this only seems to works when there's a known/named type for the array of values, it doesn't seem to work when the array is of a primitive type like string[] for some reason
See Playground
Anyone have any idea why this isn't working with simple array types like string[]?

I've written a very simple URL parser:
type ParseProtocol<I, AST> =
    I extends `${infer P}://${infer Rest}` ? [AST & { protocol: P }, Rest] : never

type ParseAuth<I, AST> =
    I extends `${infer A}@${infer Rest}` ?
    A extends `${infer U}:${infer P}` ? [AST & { username: U, password: P }, Rest] :
    [AST & { username: A }, Rest] : [AST, I]

type ParseHost<I, AST> =
    I extends `${infer H}/${infer Rest}` ?
    H extends `${infer Name}:${infer Port}` ? [AST & { hostname: Name, port: Port }, Rest] :
    [AST & { hostname: H }] : never

type ParsePathname<I, AST> =
    I extends `${infer P}?${infer Rest}` ? [AST & { pathname: `/${P}` }, `?${Rest}`] :
    I extends `${infer P}#${infer Rest}` ? [AST & { pathname: `/${P}` }, `#${Rest}`] :
    I extends `${infer P}` ? [AST & { pathname: `/${P}` }, ''] : never

type ParseQuery<I, AST> =
    I extends `?${infer Q}#${infer Rest}` ? [AST & { query: ParseQueryItems<Q> }, `#${Rest}`] :
    I extends `?${infer Q}` ? [AST & { query: ParseQueryItems<Q> }, ''] : [AST, I]
type ParseQueryItems<I> =
    string extends I ? [] :
    I extends '' ? [] :
    I extends `${infer K}=${infer V}&${infer Rest}` ? [{ key: K, value: V }, ...ParseQueryItems<Rest>] :
    I extends `${infer K}&${infer Rest}` ? [{ key: K, value: null }, ...ParseQueryItems<Rest>] :
    I extends `${infer K}=${infer V}` ? [{ key: K, value: V }] :
    I extends `${infer K}` ? [{ key: K, value: null }] : []

type ParseHash<I, AST> =
    I extends `#${infer H}` ? [AST & { hash: `#${H}` }] : [AST]

type ParseURL<I extends string> =
    ParseProtocol<I, {}> extends [infer AST, infer Rest] ?
    ParseAuth<Rest, AST> extends [infer AST, infer Rest] ?
    ParseHost<Rest, AST> extends [infer AST, infer Rest] ?
    ParsePathname<Rest, AST> extends [infer AST, infer Rest] ?
    ParseQuery<Rest, AST> extends [infer AST, infer Rest] ?
    ParseHash<Rest, AST> extends [infer AST] ? AST :
    never : never : never : never : never : never

type Merge<T> = { [P in keyof T as T[P] extends '' ? never : P]: T[P] }

type Result = Merge<ParseURL<'https://username:password@example.com:443/?k1=v1&k2=v2#h'>>

Playground

The proposed template string types is a quite powerful feature that will surely enhance the flexibility and expressiveness of the type system, but it doesn’t feel right to me – I think that it’s too complex, doing too much and at the same time it’s not as generic/flexible as it might be.

I totally agree. This PR adds a complex mechanism to manipulate string, but it is not flexible enough to handle all the cases so uppercase lowercase capitalize and uncapitalize comes in.
But that's not enough, people will ask for further more "modifiers" to modify the string (like abCd to ab_cd). It's not a good idea to try to cover all possible use cases by declarative syntax. You will find you have to built-in tons of "modifiers" in the compiler itself and people still not feels ok because they have other requirements that need new "modifiers".

The main thing that crossed the line for me is the case modifiers (uppercase, capitalize…). I think that this is nonsystematic, hack-ish and it’s opening a Pandora box.

+1, bring these "magic" modifiers (it's a kind of new DSL) is not good for the language. And even more, once you released it into the language and it's getting widely used, (generally) you cannot remove it from the language forever (think about import id = expr and module x {}).

Wouldn’t it be better to add support for the type-generating macros, using JS/TS syntax, instead of adding more and more keywords into the TS’ type syntax?

+1 again. There's a strong requirement for the type generating functionality with JS/TS syntax. Carefully consider that kind of feature will bring us much more powerful things like generating types from a non-TS file (CSS, GraphQL) with a consistent & systematic way. Just an example, an isomorphic way is much better than the current PR suggests.
const type function f(x: string) { return x.reverse() }
type T = f<"abc"> // "cba"

TS type system is already Turing complete and this PR takes it even further (just look at #40336 (comment)).

Agree, since the type system is already turning complete, there is no reason to try to "limit" the ability of the language (like 50 level recursion type limitation). I think the right path is to try to isolate each part and if some path dies in an infinite loop, the other part of the type checker won't die.

Another point, I found those modifiers can't be composed.
type x = `${capitalize lowercase 'VALUE'}`

is syntax error

Macro please, not these confusing things

@Losses Actually template string type is more powerful than macro IMHO. Macro, in its board sense, transforms a list of tokens in the language to another list of tokens. For example, macro_assert(1 + 1 == 2) is transformed to if (!(1 + 1 == 1)) throw new Error(). Note macro cannot inspect type information from token list. E.g. macro does not know 1+1 == 2 in the above example has the type boolean.
On the other hand, template string type (TST) with inference / mapped type as clause can not only inspect type information but also change the type. TST is more like compile time reflection tool more than syntax tree manipulation.

The main thing that crossed the line for me is the case modifiers (uppercase, capitalize…). I think that this is nonsystematic, hack-ish and it’s opening a Pandora box.

+1, bring these "magic" modifiers (it's a kind of new DSL) is not good for the language.

type x = ${capitalize lowercase 'VALUE'}

My gut feeling towards string type manipulation modifier is similar. They add additional complexity to the language, do not compose well and do feel elegant. Some how we can make up some user land string manipulation utility.
type lowerToUpper = {
    a: 'A', b: 'B', c: 'C', d: 'D'
}
type lowerLetters = keyof lowerToUpper
type Capitalize<S, C extends lowerLetters = lowerLetters> =
    C extends lowerLetters ? (
        S extends `${C}${infer R}` ? `${lowerToUpper[C]}${R}` : never
    ) : never

type A = Capitalize<'abc'>  // Abc

The issue is the string utility only handles ascii characters. But I would definitely sacrifice unicode chars for cleaner language design.

My two cents (ik people won't like it but anyway):

Nothing here is complex, it's as simple as it gets that now you can concat and split type level strings. Both of them being primitive operations exactly how it should be. (Although the inference needs work, especially when there's no seperator) What is complex/confusing/overwhelming is the examples here that go overboard and use it in the way it's not supposed to be used. The goal of this feature is to not validate/parse strings per se (I don't see a problem if people do it anyway) it is to augment strings.

I do agree with the disliking of modifiers, but I see it as tradeoff for the fact that we can't have full-fledged language level features on type level.

TS is already turing complete, why don't we just have language level features on type level straightway within gymnastics as we can do it anyway

I don't understand this argument, the reason we have a limit is to disallow abusing the turing completeness, the goal is to not promote these things. Having it as a first-class experience promotes it, that's not what we want (I don't like linking the tweet here but did it anyway for some credibility)

Nothing here is complex

The string matching & infer part is not complex I agree, the modifiers part is complex.

the goal is to not promote these things

But we're not intended for resolving the Tower of Hanoi problem in the type system. (and I think you can already do that today). We want to do productive things! (and that requiring first-class type-level programming experience). Many times I found that "Oh TypeScript cannot express this type, I have to make it Record<string, any> or object, sad" just because there are no first-class type-level tools to use.

the modifiers part is complex
I would call it insufficient instead complex tho

We want to do productive things!
I believe you when you say that but the first class experience Ryan was talking about is indeed having language level features on type level. You don't have to go so far for being productive, features like this (with a little polish) are enough imo. And tbh if you find writing types in a marvel of type system like typescript hard it's the abstraction/api that needs work xP

I suggest being very careful about adding "modifiers" in this PR. Other parts (template string matching and as) are good but this part is harmful(you cannot implement other modifiers in the userland) to the language IMO, please be thoughtful before adding it into the language. There is (likely) no way back once it added.

We have an agreement here! We need to be careful about this tradeoff.

Hey folks! Yet another try about regex:

type Alpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

type IsFalse<T extends false> = T

type TConcat<L, R> = {
    concat_left: L,
    concat_right: R
}

type TUnion<L, R> = {
    union_left: L,
    union_right: R
}

type TClosure<V> = {
    closure_value: V
}

type TParen<V> = {
    paren_value: V
}

type TChars<V> = {
    chars_value: V
}

type LParen = "("

type RParen = ")"

type Star = "*"

type Bar = "|"

type Plus = "+"

type FilterEmptyAndNever<T> = T extends [infer Result, infer Rest] ? Rest extends "" ? Result : never : never

type ParseChar<Str, CHAR> = CHAR extends string ? Str extends `${CHAR}${infer Rest}` ? [CHAR, Rest] : never : never

type ParseMany<Str, Target extends string, Prev extends string = ""> =
    Str extends "" ? [Prev, ""] :
    Str extends `${Target}${infer Rest}` ?
    Str extends `${infer Ch}${Rest}` ? Ch extends string ? ParseMany<Rest, Target, `${Prev}${Ch}`> : never : never :
    [Prev, Str];

type ParseCharList<Str> = ParseMany<Str, Alpha, "">

type ParseChars<Str> = ParseCharList<Str> extends [infer S, infer Rest] ? [TChars<S>, Rest] : never

type ParseParen<Str> =
    [ParseChar<Str, LParen>] extends [never] ? never : ParseChar<Str, LParen> extends [infer LP, infer LPRest] ?
    [ParseUnion<LPRest>] extends [never] ? never : ParseUnion<LPRest> extends [infer Value, infer VRest] ?
    [ParseChar<VRest, RParen>] extends [never] ? never : ParseChar<VRest, RParen> extends [infer RP, infer RPRest] ? [TParen<Value>, RPRest] :
    never : never : never;

type ParseFactor<Str> =
    [ParseChar<Str, LParen>] extends [never] ?
    [ParseChar<Str, Alpha>] extends [never] ? never : ParseChars<Str> :
    ParseParen<Str>;

type ParseClosure<Str> = [ParseFactor<Str>] extends [never] ? never : ParseFactor<Str> extends [infer L, infer LRest] ?
    [ParseChar<LRest, Star>] extends [never] ? [L, LRest] : ParseChar<LRest, Star> extends [infer S, infer SRest] ? [TClosure<L>, SRest] :
    never : never;

type ParseConcat<Str> =
    [ParseClosure<Str>] extends [never] ? never : ParseClosure<Str> extends [infer L, infer LRest] ?
    [ParseChar<LRest, Plus>] extends [never] ? [L, LRest] : ParseChar<LRest, Plus> extends [infer C, infer CRest] ?
    [ParseConcat<CRest>] extends [never] ? never : ParseConcat<CRest> extends [infer R, infer RRest] ? [TConcat<L, R>, RRest] :
    never : never : never;

type ParseUnion<Str> =
    [ParseConcat<Str>] extends [never] ? never : ParseConcat<Str> extends [infer L, infer LRest] ?
    [ParseChar<LRest, Bar>] extends [never] ? [L, LRest] : ParseChar<LRest, Bar> extends [infer Bar, infer BRest] ?
    [ParseUnion<BRest>] extends [never] ? never : ParseUnion<BRest> extends [infer R, infer RRest] ? [TUnion<L, R>, RRest] :
    never : never : never;

type Parse<Str> = FilterEmptyAndNever<ParseUnion<Str>>

type MatchStr<Str, Chars> =
    Str extends "" ? never :
    Chars extends TChars<infer V> ? V extends string ? Str extends `${V}${infer Rest}` ? Rest : never : never : never;

type MatchUnion<Str, Union> = Union extends TUnion<infer L, infer R> ? Match<Str, L> | Match<Str, R> : never;

type MatchConcat<Str, Concat> =
    Str extends "" ? never :
    Concat extends TConcat<infer L, infer R> ? Match<Match<Str, L>, R> : never

type MatchClosure<Str, Closure> =
    Str extends "" ? "" :
    Closure extends TClosure<infer C> ? MatchClosure<Match<Str, C>, Closure> : never

type MatchParen<Str, Paren> = Paren extends TParen<infer P> ? Match<Str, P> : never;

type Match<Str, Tree> =
    Tree extends TChars<any> ? MatchStr<Str, Tree> :
    Tree extends TParen<any> ? MatchParen<Str, Tree> :
    Tree extends TUnion<any, any> ? MatchUnion<Str, Tree> :
    Tree extends TConcat<any, any> ? MatchConcat<Str, Tree> :
    Tree extends TClosure<any> ? MatchClosure<Str, Tree> :
    never;

type isNotNever<T> = [T] extends [never] ? false : true
type isEmptyString<T> = T extends "" ? true : false;

type IsMatched<T> = isNotNever<T> extends true ? isEmptyString<T> extends true ? true : false : false
type isTrue<T extends true> = T

type regex1 = Parse<"a+(b+c|e)|d">;
type test1 = isTrue<IsMatched<Match<"abc", regex1>>>;
type test2 = isTrue<IsMatched<Match<"ae", regex1>>>;
type test3 = isTrue<IsMatched<Match<"abc", regex1>>>;
type test4 = isTrue<IsMatched<Match<"d", regex1>>>;

type regex2 = Parse<"(a+b|c)*">;
type test5 = isTrue<IsMatched<Match<"abab", regex2>>>;
type test6 = isTrue<IsMatched<Match<"cc", regex2>>>;

You can see here

I'm not sure if this intended behavior or not:
type T0 = `{a{a1,b{b1,b2},c{c1}}, d}` extends `{${infer X}}` ? X : never;
// I expect "{a{a1,b{b1,b2}" or "{a{a1,b{b1,b2},c{c1}}, d}" but is never

type T1 = `{a{a1,b{b1,b2,c{c1, d}` extends `{${infer X}}` ? X : never;
// works "a{a1,b{b1,b2,c{c1, d"

type EndsWithS<T extends string> = T extends `${infer _}s` ? true : false;

type Egg = EndsWithS<'egg'>;   // false
type Peas = EndsWithS<'peas'>; // true
type Mess = EndsWithS<'mess'>; // false, but should be true?

@dfreeman
type EndsWithS<T extends string> = T extends `${infer _}s${infer R}`?R extends ''?true: EndsWithS<R> : false;

type Egg = EndsWithS<'egg'>;   // false
type Peas = EndsWithS<'peas'>; // true
type Mess = EndsWithS<'mess'>; // true

https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-8#code/C4TwDgpgBAogdgEwM4HUCWwAWBlAPAFSggA9gJEkolgAnNOAcwD4oBeKQksiqAAwBIA3vQBmEGlAD6AXyRDR4qACVpvAPxKipcsigByPWtoBXCAC5YFdFjxKWFkQEMANkggBuAFCfQkWAwY2S2RrHFw9CAC9JncoOIB6eKgnVwgfcGgABQhHSnZ4EIwwvUhc6NjEqBM032gAWQgkPODUIrw9AFtGpHKoSuqoTyA

@devanshj I think it's intended, since the string capturing is non-greedily.
So, for T0, the pattern {${infer X}} should capture a{a1,b{b1,b2 for X. After this, there's still ,c{c1}}, d} left in your string, which doesn't match the the pattern {${infer X}} (this pattern means } should be at the end and there're no more characters.) If you change the pattern to {${infer X}}${infer _}, it should work, and the X will be a{a1,b{b1,b2.
For T1, there is only one } and it's at the end of the input string, so it works.
T3 is similar to T0.

type EndsWithS = T extends ${infer _}s${infer R}?R extends ''?true: EndsWithS : false;

Nice! But I think this is just a workaround for this probable bug

I think it's intended, since the string capturing is non-greedily.

Yeah fair enough but just need to confirm if non-greedyness is intended

T3 is similar to T0.

So is this also not a bug? :thinking:

Non-greedyness is intended, as Anders says.

Non-greedyness is intended, as Anders says.

Oops, missed that sorry! xD

Looks like problem appears whenever two consecutive characters are same

Well,Once it matches the same character, it stops
I don't know whether it's a bug

Well,Once it matches the same character, it stops

I don't really understand you, type A = "aa" extends `${infer X}a` ? X : never; here it's not matching at all. Seems like a bug to me, I can't even think of a workaround for this case.

I don't really understand you, type A = "aa" extends `${infer X}a` ? X : never; here it's not matching at all. Seems like a bug to me, I can't even think of a workaround for this case.

Because x can be equal to an empty string  XD :

Because x can be equal to an empty string XD :

Ohh gotcha but still seems to be a bug

It is finally completed.
i think we can write more amazing code over time.
type Hex = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F'

type Format<T, Root = string, First = string> =
  T extends `#${Hex}${infer End}` ? End extends Hex ? Root extends `#${Hex}${infer Middle}${End}` ? First extends `#${infer _2}` ? { R: _2, G: Middle, B: End } : never : never : T extends `${infer _1}${End}` ? Format<`#${End}`, T, _1> : never : T

declare function format<T>(color: T): Format<T>

let result = format("#F3D")  //{R:"F",G:"3",B:"D"}

https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-8#code/C4TwDgpgBAEhAeUC8UDkAGVUA+aCMWuqATIWgMxmoAsVArFQGxUDsVAHFQJxUCCVAISoBhKgBEqAUSoAxVACh5oSFBkB7AE4BbAIbAAPABUANFABKatcGRQAzsA0BLAHYBzUzMcb7N+07cAfMjyUFCGUAjAEM4AJrZQAAYAxAAkAN5w8AC+6S4AZhAaUJKxWQlQAPzFsRHwUbHxmZXmltaR0XGJqRkIOWn5hVAAso4xMQA2EH0lMWXNnt5tdR3xybnOBUUA+sRzVWnmAFxQO6YA4scjY5OmAsczUFlQx84QAG6DL++fYbX1nQl1psTnhpqVylV1No9Po1mkZmVTCYQUEvh8isdDIoYhAAMbjHQaaB5ACuzlxwEcamcUDyml0BkMAQAFLi1ONNJiAJTHKEMowBRSTaxE2wk8bWFB06HAZkAIiSMnIYjlXNCAHp1WkzIc5TI5cYLnLyAa7nKVVl5EA

Unpopular opinion: The basic functionality of this feature is spot on (specially where we were lacking of getting this work get('a.b[0].c, obj)`, tho case modifiers and being able to parse literals feel wrong.

Hello,
I found a problem related to removing the trailing space. The recursive type doesn't seem to work if there is a consecutive string at the end.
type TrimL<S> = S extends ` ${infer R}` ? TrimL<R> : S
type TrimR<S> = S extends `${infer R} ` ? TrimR<R> : S

const trimL1: TrimL<' A'> = 'A' // OK
const trimL2: TrimL<'  A'> = 'A' // OK
const trimR1: TrimR<'A '> = 'A' // OK
const trimR2: TrimR<'A  '> = 'A' // Type '"A"' is not assignable to type '"A  "'

Playground

A workaround but only works if there's no space in between
type Trim<S extends string> =
  string extends S ? string :
  S extends ` ${infer R}` ? Trim<R> :
  // S extends `${infer R} ` ? Trim<R> :
  // can't do this coz https://github.com/microsoft/TypeScript/pull/40336#issuecomment-684948382
  S extends `${infer R} ${infer _}`
    ? _ extends ""
        ? Trim<R> :
      Trim<_> extends ""
        ? Trim<R>
    : `${R} ${_}` :
  S;

const t1: Trim<" A"> = "A"
const t2: Trim<"  A"> = "A"
const t3: Trim<"A "> = "A"
const t4: Trim<"A  "> = "A"
const t5: Trim<"A  B "> = "A  B" // error

Playground

This is so cool ❤

It currently isn't possible to write the opposite mappings because there's no way to match on "first uppercase letter" or "first lowercase letter" or other such patterns in inference. There's just an enormously long tail of possible patterns you might want to match on, and ultimately I think the only way to address this would be to introduce RegEx string types with an ability to turn capture groups into inferences. That's definitely beyond the scope of this PR.

@ahejlsberg if camelCase, pascalCase, kebabCase, snakeCase etc. were added to the TemplateCasing enum (with equivalent keywords), would it not be possible to add a few extra case expressions to this switch statement? It'd require writing utility functions to convert a string to camel-/snake-case etc. Regexes might be needed, but maybe their usage wouldn't have to leak to the type system - it could be an internal implementation detail. Even for snake-case -> camel-case, which is possible now, I can imagine it being a common-enough use case that it'd be good to have built in.

The mapped types with "as" clauses is just the feature I've been wanting, thank you! I was googling around trying to find the right search terms because I was convinced this feature already existed. :)
My use case is creating a "plural" type from a "singular" one, e.g.
interface Book {
    isbn: number
    title: string
}

interface IBook {
    book: Book
}

type Plural<T> = {
    [K in keyof T & string as `${K}s`]: Array<T[K]>
}

const books: Plural<IBook> = {
    books: [{
        isbn: 1,
        title: 'Hello World'
    }]
}

This makes it easy to have a generic type that takes a type with a "singular" property-type pair and turn it into a type with a pluralized property name and an Array of the corresponding type.
Awesome feature!

@devanshj
I fixed it for spaces in between:
type Trim<S extends string> =
	string extends S ? string :
	S extends ` ${infer R}` ? Trim<R> :
	_TrimR<S>;

/** @internal */
type _TrimR<S extends string> = S extends `${infer R} ${infer _}`
	? _ extends ""
		? _TrimR<R>
		: Trim<_> extends ""
		? _TrimR<R>
		// @ts-ignore: TS2589: Type instantiation is excessively deep and possibly infinite.
		: `${R} ${_TrimR<_>}`
	: S;

const t1: Trim<" A"> = "A"
const t2: Trim<"  A"> = "A"
const t3: Trim<"A "> = "A"
const t4: Trim<"A  "> = "A"
const t5: Trim<"A  B "> = "A  B" // works!

Playground: 🔗

Great PR! Really mind-blowing.
Find a little typo in the PR description.

`[${42}]` resolves to `42`.

while `[${42}]` should resolve to `[42]`.

I'd like to propose the new syntax to improve string inference by ....
Similar to tuple:

T extends [H, ...infer F] ? F : never

Str extends `${H}${...infer F}` ? F : never

@Kingwl that is has been already proposed, Anders even commented on it!

@devanshj Sorry i didn't notice that. That's awesome!

Single character inferences and anchored end span matching

👀 can we get a playground build? Afaik that needs to be done after each new commit.

@devanshj sure.
@typescript-bot pack this.

Based on the feedback here I have just committed two changes to template string inference:

Matching of the ending span of literal characters in the inference target is anchored to the end of the source string. For example, inferring from 'abcxxx' to `${infer T}x` now infers 'abcxx' for T.
When an inference placeholder is immediately followed by another inference placeholder, the first inference placeholder will match a single character in the source string. For example, inferring from 'abcd to `${infer H}${infer T}` now infers 'a' for H and 'def' for T. This can be used to perform single character decomposition of strings, e.g. turning a string into a tuple of single characters using a recursive conditional type. I'm still not 100% happy with the fact there is no obvious indication a single character inference is desired, but it seems better than not having the ability at all.

Some examples:
type TrimL<S extends string> = S extends ` ${infer T}` ? TrimL<T> : S;
type TrimR<S extends string> = S extends `${infer T} ` ? TrimR<T> : S;

type TR0 = TrimL<'   xx   '>;  // 'xx   '
type TR1 = TrimR<'   xx   '>;  // '   xx'
type TR2 = TrimL<TrimR<'   xx   '>>;  // 'xx'

type Capitalize<S extends string> = S extends `${infer H}${infer T}` ? `${uppercase H}${T}` : S;
type Uncapitalize<S extends string> = S extends `${infer H}${infer T}` ? `${lowercase H}${T}` : S;

type TC1 = Capitalize<'foo'>;  // 'Foo'
type TC2 = Uncapitalize<'Foo'>;  // 'foo'

type Chars<S extends string> =
    string extends S ? string[] :
    S extends `${infer C}${infer R}` ? [C, ...Chars<R>] :
    S extends '' ? [] :
    never;

type TT1 = Chars<'FooBar'>;  // ['F', 'o', 'o', 'B', 'a', 'r']

Note that these types pretty quickly run afoul of the recursion depth limiter. That's an orthogonal issue I'm still thinking about.
As illustrated by the Capitalize<S> and Uncapitalize<S> types, we could now fairly easily do away with the capitalize and uncapitalize modifiers. However, I'm going to keep them around for now.
Some have voiced their concern about the uppercase and lowercase modifiers. I continue to think these are solidly merited. While it may be possible to implement simple ASCII versions of these modifiers, a complete implementation using Unicode uppercase and lowercase code point classification is definitely not something we want to attempt in type land!

Some have voiced their concern about the uppercase and lowercase modifiers.

I'm concerned about all kinds of modifiers, not only these two (or any specific modifier). If a modifier cannot be implemented in the userland (and being used with the same syntax), then that's not a good design. The reason has been presented in the previous discussions.

I think argument inference might prove to be a point of confusion:
function test<T extends boolean>(input: `A${T}`): T;
const success = test("Atrue");
const fail = test("AB");

function test2<T extends number>(input: `A${T}`): T;
const success = test2("AB");

I suppose this is because boolean is a known union as opposed to number, but it's probably not immediately obvious why the form of the argument can be restricted for one generic type parameter but not the other.

Here's where I stand on modifiers:
Now that we're getting rid of capitalize and uncapitalize the modifiers thing no longer seem to be a "quick fix" because the remaining two ie lowercase and uppercase are primitive enough.
Moreover, now that we can implement Capitalize and Uncapitalize (thanks to head-tail inference), we also can implement other modifiers like CamelCase etc too.
If it helps think of uppercase and lowercase as an aid for not having to write { a: "A", b: "B", ... }. If we somehow had that map available in the thin air, I would have voted to remove even these two modifiers. But since we don't have one, it seems reasonable to have these two modifiers.

While it may be possible to implement simple ASCII versions of these modifiers, a complete implementation using Unicode uppercase and lowercase code point classification is definitely not something we want to attempt in type land!

Since full Unicode uppercase mapping is not what this PR expected, I think it is possible to include the { a: "A", b: "B", ... } mapping in the libes5.d.ts to get rid of the last two lowercase and uppercase primitive.

I'm up for that too. As a matter of fact it'll be better as it'll help in type gymnastics as I could get a union with keyof Ascii. But I would only want to have it if there are no performance hits because of supposedly long lookups as it'll have a lot of keys.

Since full Unicode uppercase mapping is not what this PR expected

Hell wait I think Anders means we indeed will have full unicode support with modifiers.

While it may be possible to implement simple ASCII versions of these modifiers, a complete implementation using Unicode uppercase and lowercase code point classification is definitely not something we want to attempt in type land!

When he said this he meant one can only have ASCII versions in type land (meaning user land type definitions). And obviously full unicode map is definitely impossible to have lmao.

@Kingwl could you also update the playground link in the original description? (for people who would be coming across the PR)

@devanshj Sorry I don't have permissions. /cc: @DanielRosenwasser @orta

I like the idea from @devanshj  and @Jack-Works to use plain types for uppercase and lowercase with a type mapping { a: "A", b: "B", ... } provided in lib.d.ts. What about:
type Capital = {
    f: "F",
    o: "O"
    // ... other
}

type Capitalize<S extends string> = S extends `${infer H}${infer T}` ? `${Uppercase<H>}${T}` : S;
type Uncapitalize<S extends string> = S extends `${infer H}${infer T}` ? `${Lowercase<H>}${T}` : S;
type Uppercase<S extends string> =
    string extends S ? string :
    S extends `${infer T}${infer U}` ?
    T extends keyof Capital ?
    `${Extract<Capital[T], string>}${Uppercase<U>}` : `${T}${Uppercase<U>}`
    : S
type Lowercase<S extends string> =
    string extends S ? string :
    S extends `${infer T}${infer U}` ?
    T extends Capital[keyof Capital] ?
    `${Extract<KeyFromVal<Capital, T>, string>}${Lowercase<U>}` : `${T}${Lowercase<U>}`
    : S

type KeyFromVal<T, V extends T[keyof T]> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]

// Examples
type TC1 = Capitalize<'foo'>;  // 'Foo'
type TC2 = Uncapitalize<'Foo'>;  // 'foo'

type T10 = Uppercase<'foo'>;  // 'FOO'
type T11 = Uppercase<'1foo'>;  // '1FOO'
type T12 = Uppercase<'Foo'>;  // 'FOO'

type T20 = Lowercase<'Foo'>;  // 'foo'
type T21 = Lowercase<'1Foo'>;  // '1foo'
type T22 = Lowercase<'foo'>;  // 'foo'

type LowercaseF = KeyFromVal<Capital, "F"> // "f"

Playground
Something like above would be a low-risk solution for the first stage. Library types probably can be more easily dropped than compiler modifiers, if there is gonna be a more sophisticated string processing approach in later versions.

Hi folks! It's me again!
I have wrote a type-level simple JSON parser:
type isNotNever<T> = [T] extends [never] ? false : true

type Alpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

type Digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"

type OpenBrace = "{"

type CloseBrace = "}"

type OpenBacket = "["

type CloseBacket = "]"

type DoubleQuote = '"'

type Colon = ":"

type Comma = ","

type TString<T> = {
    str_value: T
}

type TNumber<T> = {
    num_value: T
}

type TArray<T extends unknown[]> = {
    arr_value: T
}

type TPair<K, V> = {
    key_value: K,
    value_value: V
}

type TObject<T extends unknown[]> = {
    mem_value: T
}

type TNull = {
    null_value: never
}

type FilterEmptyAndNever<T> = T extends [infer Result, infer Rest] ? Rest extends "" ? Result : never : never

type ParseChar<Str, CHAR> = CHAR extends string ? Str extends `${CHAR}${infer Rest}` ? [CHAR, Rest] : never : never

type ParseMany<Str, Target extends string, Prev extends string = ""> =
    Str extends "" ? [Prev, ""] :
    Str extends `${Target}${infer Rest}` ?
    Str extends `${infer Ch}${Rest}` ? Ch extends string ? ParseMany<Rest, Target, `${Prev}${Ch}`> : never : never :
    [Prev, Str];

type ParseCharList<Str> = ParseMany<Str, Alpha, "">
type ParseDigitList<Str> = ParseMany<Str, Digit, "">

type ParseString<Str> =
    isNotNever<ParseChar<Str, DoubleQuote>> extends true ? ParseChar<Str, DoubleQuote> extends [infer LP, infer Rest] ?
    isNotNever<ParseCharList<Rest>> extends true ? ParseCharList<Rest> extends [infer Result, infer SRest] ?
    isNotNever<ParseChar<SRest, DoubleQuote>> extends true ? ParseChar<SRest, DoubleQuote> extends [infer RP, infer FRest] ? [TString<Result>, FRest] :
    never : never : never : never : never : never

type ParseNumber<Str> =
    isNotNever<ParseChar<Str, Digit>> extends true ? ParseDigitList<Str> extends [infer Result, infer Rest] ? [TNumber<Result>, Rest] :
    never : never;

type ParseNull<Str> = Str extends `null${infer Rest}` ? [TNull, Rest] : never

type ParseArrayMember<Str> = _ParseArrayMember<Str, []>

type _ParseArrayMember<Str, Prev extends unknown[]> =
    isNotNever<ParseChar<Str, CloseBacket>> extends true ? [Prev, Str] :
    ParseJson<Str> extends [infer Member, infer MemberRest] ?
    isNotNever<ParseChar<MemberRest, Comma>> extends true ? ParseChar<MemberRest, Comma> extends [infer Comma, infer CommaRest] ?
    _ParseArrayMember<CommaRest, [...Prev, Member]> : never :
    [[...Prev, Member], MemberRest] : never

type ParseArray<Str> =
    isNotNever<ParseChar<Str, OpenBacket>> extends true ? ParseChar<Str, OpenBacket> extends [infer LP, infer Rest] ?
    isNotNever<ParseArrayMember<Rest>> extends true ? ParseArrayMember<Rest> extends [infer Result, infer SRest] ?
    isNotNever<ParseChar<SRest, CloseBacket>> extends true ? ParseChar<SRest, CloseBacket> extends [infer RP, infer FRest] ?
    Result extends unknown[] ? [TArray<Result>, FRest] :
    never : never : never : never : never : never : never

type ParseObjectMember<Str> = _ParseObjectMember<Str, []>

type _ParseObjectMember<Str, Prev extends unknown[]> =
    isNotNever<ParseChar<Str, CloseBrace>> extends true ? [Prev, Str] :
    ParseString<Str> extends [infer Key, infer KeyRest] ?
    isNotNever<ParseChar<KeyRest, Colon>> extends true ?
    ParseChar<KeyRest, Colon> extends [infer Colon, infer ColonRest] ?
    ParseJson<ColonRest> extends [infer Value, infer ValueRest] ?
    isNotNever<ParseChar<ValueRest, Comma>> extends true ?
    ParseChar<ValueRest, Comma> extends [infer Comma, infer CommaRest] ? _ParseObjectMember<CommaRest, [...Prev, TPair<Key, Value>]> : never :
    [[...Prev, TPair<Key, Value>], ValueRest] :
    never : never : never : never;

type ParseObject<Str> =
    isNotNever<ParseChar<Str, OpenBrace>> extends true ? ParseChar<Str, OpenBrace> extends [infer LP, infer Rest] ?
    isNotNever<ParseObjectMember<Rest>> extends true ? ParseObjectMember<Rest> extends [infer Result, infer SRest] ?
    isNotNever<ParseChar<SRest, CloseBrace>> extends true ?
    ParseChar<SRest, CloseBrace> extends [infer RP, infer FRest] ?
    Result extends unknown[] ? [TObject<Result>, FRest] :
    never : never : never : never : never : never : never

type ParseJson<Str> =
    isNotNever<ParseNull<Str>> extends true ? ParseNull<Str> :
    isNotNever<ParseChar<Str, OpenBrace>> extends true ? ParseObject<Str> :
    isNotNever<ParseChar<Str, OpenBacket>> extends true ? ParseArray<Str> :
    isNotNever<ParseChar<Str, DoubleQuote>> extends true ? ParseString<Str> :
    ParseNumber<Str>;

type Parse<Str> = FilterEmptyAndNever<ParseJson<Str>>

type ToNumber<T> =
    T extends "1" ? 1 :
    T extends "2" ? 2 :
    T extends "3" ? 3 :
    T extends "4" ? 4 :
    T extends "5" ? 5 :
    T extends "6" ? 6 :
    T extends "7" ? 7 :
    T extends "8" ? 8 :
    T extends "9" ? 9 :
    T extends "0" ? 0 :
    never

type InstanceArrayMem<Mem extends unknown[]> =
    Mem extends [] ? [] :
    Mem extends [infer H, ...infer Tail] ? [InstanceJson<H>, ...InstanceArrayMem<Tail>] :
    never;

type InstanceObjMem<Mem extends unknown[]> =
    Mem extends [] ? {} :
    Mem extends [infer H, ...infer Tail] ?
    H extends TPair<infer K, infer V> ? [InstanceJson<K>] extends [infer Key] ? Key extends string ? { [k in Key]: InstanceJson<V> } & InstanceObjMem<Tail> :
    never : never : never : never;

type InstanceJson<T> =
    T extends TNull ? null :
    T extends TString<infer V> ? V :
    T extends TNumber<infer V> ? ToNumber<V> :
    T extends TArray<infer Mem> ? InstanceArrayMem<Mem> :
    T extends TObject<infer Mem> ? InstanceObjMem<Mem> :
    never;

type A1 = InstanceJson<Parse<"[]">>
type A2 = InstanceJson<Parse<"[1]">>
type O1 = InstanceJson<Parse<"{}">>

type O2 = InstanceJson<Parse<'{"a":1,"b":[{"c":1,"d":null},2]}'>>

const o2: O2 = {
    a: 1,
    b: [{
        c: 1,
        d: null
    }, 2]
}

You can see it here

@Kingwl I think we should stop showing parsers to avoid polluting the comments and let's focus the improvement and feedback of this PR.

@ford04 tbc I was interested in that idea assuming the modifiers only work with ASCII characters. But when I realised they work with the full unicode, I said it's impossible to have that with userland types as the case mapping is not trivial at all. Not only it's impossible it doesn't make sense to go so overboard just to avoid modifiers.

I started playing around, and it turns out going from camel-case -> snake/kebab/all-caps is possible too, at least if we're restricted to [A-Z]: playground link
I'm not sure what the performance would be in a real project, but it doesn't hit any type instantiation is excessively deep and possibly infinite errors:
type UpperChar = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'

type FromCamelCase<S extends string, Joiner extends string> = S extends `${infer Head}${UpperChar}${infer _Tail}`
    ? Head extends `${infer H}${UpperChar}${infer T}` // get rid of `Head`s that contain upper-case characters - we only want the shortest head up to the first upper-case char.
        ? never
        : S extends `${Head}${infer U}${infer Tail}`
            ? `${Head}${Head extends '' ? '' : Joiner}${lowercase U}${FromCamelCase<Tail, Joiner>}`
            : never
    : S

type ToSnakeCase<S extends string> = FromCamelCase<S, '_'>
type ToKebabCase<S extends string> = FromCamelCase<S, '-'>
type ToAllCaps<S extends string> = `${uppercase ToSnakeCase<S>}`

tests:
const expectType = <S extends string>() => ({ toEqual: (expected: S) => {} })

expectType<ToSnakeCase<'fooBarBaz'>>().toEqual('foo_bar_baz')

expectType<ToKebabCase<'fooBarBaz'>>().toEqual('foo-bar-baz')

expectType<ToAllCaps<'fooBarBaz'>>().toEqual('FOO_BAR_BAZ')

// pascal case also supported
expectType<ToSnakeCase<'FooBarBaz'>>().toEqual('foo_bar_baz')

// all-uppercase words like HTML get lowercased
expectType<ToSnakeCase<'innerHTML'>>().toEqual('inner_html')

@ahejlsberg If a mapped type has an as clause, is there a reason to continue to require that the type on the right of the in operator needs to be assignable to string | number | symbol?
I.e.,
type X<T> = { [P in MakeSomeObjectType<keyof T> as P["propWithTheKey"]]: P["other"] }

Maybe that's not useful for anything, though. It just seems weird that, in an { [P in K as N]: X } mapped type, TS would care about the type of K, given that it doesn't end up getting used for the key.
Also, in those mapped types, why does N need to be assignable to string (per your OP, as opposed to string | number | symbol)?

@mmkal, yeah that's currently better than walking through individual characters in terms of recursion limits.  I guess you can write a Break<T, D> type function that takes a string T and breaks it into two pieces: the longest prefix not ending in D, and the rest of the string:
type Break<T extends string, D extends string> = (
    string extends T ? [string, string] : (
        T extends `${infer F}${D}${infer R}` ? (
            F extends `${infer X}${D}${infer Y}` ? never : (
                T extends `${F}${infer R}` ? [F, R] : never
            )
        ) : [T, ""]
    )
);

which gives you things like
type Ex1 = Break<"this is interesting", "is"> // ["th", "is is interesting"]
type Ex2 = Break<"camelCaseIsHere", Upper>; // ["camel", "CaseIsHere"]
type Ex3 = Break<"oops", "o">; // ["", "oops"]
type Ex4 = Break<"oops", "z">; // ["oops", ""]

Playground

@mmkal Great work! Your solution also hits this error:
 ToKebabCase<'fooBarBazFooBarBazFooBarBazFooBarBazFooBarBazFooBar'>
// Type instantiation is excessively deep and possibly infinite.(2589)

Hi folks, I wrote a simple type-level html parser for fun
playground
type ParseResult = {
    success: boolean,
    data: any,
    source: string
}

type ParseFailed = {
    suceess: false,
    data: any,
    source: string
}

type ParseSuccess = {
    success: true,
    data: any,
    source: string
}

type TrimLeft<T extends string> =
    T extends `${' ' | '\n'}${infer R}`
    ? TrimLeft<R>
    : T

type TrimRight<T extends string> =
    T extends `${infer R}${' ' | '\n'}`
    ? TrimRight<R>
    : T

type Trim<T extends string> =
    TrimLeft<T> extends `${infer R}`
    ? TrimRight<R>
    : T

type LowerAlphabet = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

type UpperAlphabet = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"

type Alphabet = LowerAlphabet | UpperAlphabet

type Digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"

type Letter = Alphabet | Digit | '_' | '-' | '$'

type IsAllLetter<T extends string> =
    T extends Letter ? true :
    T extends `${Letter}${infer R}`
    ? R extends '' ? true : IsAllLetter<R>
    : false

type IsIdentifier<T extends string> =
    T extends `${Alphabet}${infer R}`
    ? R extends '' ? true : IsAllLetter<R>
    : false

type AttrObj<AttrName extends string, AttrValue, R> =
    IsIdentifier<AttrName> extends true
    ? {
        success: true,
        data: { [key in AttrName]: AttrValue },
        source: R
    }
    : {
        success: false,
        data: {
            reason: 'invalid attribute name',
            attrName: AttrName
        },
        source: R
    }

type ParseValuedAttr<T extends string> =
    T extends `${infer AttrName}="${infer AttrValue}"${infer R}`
    ? AttrObj<AttrName, AttrValue, R>
    : ParseFailed

type ParseEmptyValuedAttr<T extends string> =
    T extends `${infer AttrName}=""${infer R}`
    ? AttrObj<AttrName, '', R>
    : ParseFailed

type ParseBoolAttr<T extends string> =
    T extends `${infer AttrName} ${infer R}` ? AttrObj<AttrName, true, R> :
    T extends `${infer AttrName} ` ? AttrObj<AttrName, true, ''> :
    T extends `${infer AttrName}` ? AttrObj<AttrName, true, ''> :
    ParseFailed

type ParseAttr<T extends string> =
    TrimLeft<T> extends `${infer T}` ?
    ParseValuedAttr<T> extends ParseSuccess ? ParseValuedAttr<T>
    : ParseEmptyValuedAttr<T> extends ParseSuccess ? ParseEmptyValuedAttr<T>
    : ParseBoolAttr<T> extends ParseSuccess ? ParseBoolAttr<T>
    : {
        success: false,
        data: {
            reason: 'parse attr failed, none of valued-attr, empty-valued-attr, bool-attr is matched',
            parseValuedAttr: ParseValuedAttr<T>
            parseEmptyValuedAttr: ParseEmptyValuedAttr<T>
            parseBoolAttr: ParseBoolAttr<T>
        },
        source: T
    }
    : ParseFailed

type ParseAttrs<T extends string> =
    TrimLeft<T> extends `${infer R0}` ?

    // empty text
    R0 extends ''

    ? {
        success: true,
        data: {},
        source: ''
    } :

    // match attr
    ParseAttr<R0> extends {
        success: true,
        data: infer AttrsData,
        source: `${infer R1}`
    } ?

    // try match rest attrs

    ParseAttrs<R1> extends {
        success: true,
        data: infer RestAttrsData,
        source: `${infer R2}`
    } ?

    // has rest attrs

    // all attrs are consumed
    Trim<R2> extends ''

    ? {
        success: true,
        data: {
            [key in keyof (AttrsData & RestAttrsData)]: (AttrsData & RestAttrsData)[key]
        },
        source: R2
    }
    // some attrs failed to parse
    : {
        success: false,
        data: {
            reason: 'some attrs failed to parse',
            props: AttrsData & RestAttrsData,
        },
        source: R2
    } :

    // no rest attrs
    // source is empty, means all attrs are matched
    Trim<R1> extends ''

    ? {
        success: true,
        data: AttrsData,
        source: R1
    }

    // source is not empty, means some attrs are not matched
    : ParseAttrs<R1>

    // no attrs match
    : {
        success: false,
        data: {
            reason: 'parse attrs failed'
        },
        source: T
    } :

    ParseFailed

type ParseEmptyAttrsOpenTag<T extends string> =
    TrimLeft<T> extends `${infer T}`
    ? T extends `<${infer TagName}>${infer R0}`
    ? IsIdentifier<TagName> extends true ?
    {
        success: true,
        data: {
            tagName: TagName,
            props: {}
        },
        source: R0
    } : {
        success: false,
        data: {
            reason: 'invalid tagname',
            tagName: TagName
        },
        source: R0
    }
    : {
        success: false,
        data: {
            reason: 'parse empty-attrs-open-tag failed'
        },
        source: T
    }
    : T

type ParseNonEmptyAttrsOpenTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?

    T extends `<${infer TagName} ${infer Attrs}>${infer R0}` ?

    IsIdentifier<TagName> extends true ?

    // valid tagname, try attrs
    ParseAttrs<Attrs> extends {
        success: true,
        data: infer Props,
        source: string
    }

    // valid attrs, output
    ? {
        success: true,
        data: {
            tagName: TagName
            props: Props
        },
        source: R0
    }

    // invalid attrs, show error info
    : ParseAttrs<Attrs>

    // invalid tagname
    : {
        success: false,
        data: {
            reason: 'invalid tagname',
            tagName: TagName
        },
        source: R0
    }

    // match failed
    : {
        success: false,
        data: {
            reason: 'parse non-empty-attrs-open-tag failed'
        },
        source: T
    }
    : ParseFailed

type ParseOpenTag<T extends string> =
    TrimLeft<T> extends `${infer T}`
    ? ParseNonEmptyAttrsOpenTag<T> extends ParseSuccess ? ParseNonEmptyAttrsOpenTag<T>
    : ParseEmptyAttrsOpenTag<T> extends ParseSuccess ? ParseEmptyAttrsOpenTag<T>
    : {
        success: false,
        data: {
            reason: 'parse open-tag failed',
            parseNonEmptyAttrsOpenTag: ParseNonEmptyAttrsOpenTag<T>,
            parseEmptyAttrsOpenTag: ParseEmptyAttrsOpenTag<T>
        },
        source: T
    }
    : ParseFailed

type ParseChildren<T extends string> = ParseNode<T> extends ParseSuccess ? ParseNode<T> : {
    success: true,
    data: [],
    source: T
}

type ParseCloseTag<TagName extends string, Source extends string> =
    TrimLeft<Source> extends `${infer Source}`
    ? Source extends `</${TagName}>${infer R}`
    ? {
        success: true,
        data: {
            tagName: TagName
        },
        source: R
    }
    : {
        success: false,
        data: {
            reason: 'parse close-tag failed',
            tagName: TagName
        },
        source: Source
    }
    : ParseFailed

type ParseEmptyAttrsSelfClosingTag<T extends string> =
  TrimLeft<T> extends `${infer T}` ?
  T extends `<${infer TagName}/>${infer R0}` ?

  IsIdentifier<TagName> extends true

  ? {
      success: true,
      data: {
          tagName: TagName,
          props: {}
      },
      source: R0
  }

  : {
      success: false,
      data: {
          reason: 'invalid tagname',
          tagName: TagName
      },
      source: T
  }

  : {
      success: false,
      data: {
          reason: 'parse empty-attrs-self-closing-tag failed'
      },
      source: T
  }

  : ParseFailed

type ParseNonEmptyAttrsSelfClosingTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?
    T extends `<${infer TagName} ${infer Attrs}/>${infer R0}` ?

    IsIdentifier<TagName> extends true

    ?
    // valid tagname, try attrs
    ParseAttrs<Attrs> extends {
        success: true,
        data: infer Props,
        source: string
    }

    // valid attrs, output
    ? {
        success: true,
        data: {
            tagName: TagName
            props: Props
        },
        source: R0
    }

    // invalid attrs, show error info
    : ParseAttrs<Attrs>

    : {
        success: false,
        data: {
            reason: 'invalid tagname',
            tagName: TagName
        },
        source: T
    }

    : {
        success: false,
        data: {
            reason: 'parse empty-attrs-self-closing-tag failed'
        },
        source: T
    }

    : ParseFailed

type ParseSelfClosingTag<T extends string> =
    TrimLeft<T> extends `${infer T}`
    ? ParseNonEmptyAttrsSelfClosingTag<T> extends ParseSuccess ? ParseNonEmptyAttrsSelfClosingTag<T>
    : ParseEmptyAttrsSelfClosingTag<T> extends ParseSuccess ? ParseEmptyAttrsSelfClosingTag<T>
    : {
        success: false,
        data: {
            reason: 'parse self-closing-tag failed',
            parseNonEmptyAttrsSelfClosingTag: ParseNonEmptyAttrsSelfClosingTag<T>,
            parseEmptyAttrsSelfClosingTag: ParseEmptyAttrsSelfClosingTag<T>
        }
    }
    : ParseFailed

type ParseAnyCloseTag<Source extends string> =
    TrimLeft<Source> extends `</${infer TagName}>${infer R}` ?

    IsIdentifier<TagName> extends true

    ? {
        success: true,
        data: {
            tagName: TagName
        },
        source: R
    }

    : {
        success: false,
        data: {
            reason: 'invalid close tagname',
            tagName: TagName
        },
        source: R
    }

    : {
        success: false,
        data: {
            reason: 'parse close-tag failed'
        },
        source: Source
    }

type ParseOpenOrCloseTag<T extends string> =
  TrimLeft<T> extends `${infer T}` ?
  ParseOpenTag<T> extends ParseSuccess ? ParseOpenTag<T>
  : ParseAnyCloseTag<T> extends ParseSuccess ? ParseAnyCloseTag<T>
  : ParseSelfClosingTag<T> extends ParseSuccess ? ParseSelfClosingTag<T>
  : {
      success: false,
      data: {
          reason: 'parse open or close tag failed'
      },
      source: T
  }
  : ParseFailed

type ParseText<T extends string> =
  TrimLeft<T> extends `${infer T}` ?

  // has < found
  T extends `${infer Text}<${infer R0}` ?

  // try open-tag or close-tag
  ParseOpenOrCloseTag<`<${R0}`> extends ParseSuccess ?

  // found open or close tag

  Text extends ''

   // text is empty
  ? {
      success: false,
      data: {
          reason: 'no text found'
      },
      source: T
  }
  // text is not empty
  : {
      success: true,
      data: TrimRight<Text>,
      source: `<${R0}`
  } :

  // no open-tag or close-tag, parse text again
  ParseText<R0> extends {
      success: true,
      data: `${infer RestText}`,
      source: `${infer R1}`
  }

  // found more text
  ? {
      success: true,
      data: `${Text}<${RestText}`,
      source: R1
  }

  // no more text
  : {
      success: true,
      data: `${Text}<`,
      source: R0
  } :

  // no < found, all source is text

  Trim<T> extends ''

  // text is empty
  ? {
      success: false,
      data: {
          reason: 'no text found'
      },
      source: T
  }

  // text is not empty
  : {
      success: true,
      data: TrimRight<T>,
      source: ''
  }

  : ParseFailed

type ParseEmptyTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?

    // try parse oepn-tag
    ParseOpenTag<T> extends {
        success: true,
        data: {
            tagName: `${infer TagName}`,
            props: infer Props
        },
        source: `${infer R0}`
    } ?

    // try parse close-tag
    ParseCloseTag<TagName, R0> extends {
        success: true,
        data: any,
        source: `${infer R1}`
    }

    // found open-tag & close-tag
    ? {
        success: true,
        data: {
            tagName: TagName,
            props: Props
        },
        source: R1
    }

    // failed to match close-tag
    : {
        success: false,
        data: {
            reason: 'unmatch closed tag',
            tagName: TagName
            props: Props,
            parseCloseTag: ParseCloseTag<TagName, R0>
        },
        source: R0
    }

    : {
        success: false,
        data: {
            reason: 'failed to parse empty tag',
            parseOpenTag: ParseOpenTag<T>
        },
        source: T
    }

    : ParseFailed

type ParseNonEmptyTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?

    // try parse oepn-tag
    ParseOpenTag<T> extends {
        success: true,
        data: {
            tagName: `${infer TagName}`,
            props: infer Props
        },
        source: `${infer R0}`
    } ?

    // try parse children
    ParseNode<R0> extends {
        success: true,
        data: [...infer Children],
        source: `${infer R1}`
    } ?

    // try parse close-tag
    ParseCloseTag<TagName, R1> extends {
        success: true,
        data: any,
        source: `${infer R2}`
    }

    // found open-tag & children & close-tag
    ? {
        success: true,
        data: {
            tagName: TagName,
            props: Props,
            children: Children
        },
        source: R2
    }

    // failed to match close-tag
    : {
        success: false,
        data: {
            reason: 'unmatch closed tag',
            tagName: TagName
            props: Props,
            children: Children,
            parseCloseTag: ParseCloseTag<TagName, R1>
        },
        source: R1
    }

    // failed to match children
    : {
        success: false,
        data: {
            reason: 'parse children failed',
            tagName: TagName
            props: Props,
            parseChildren: ParseNode<R0>
        },
        source: R0
    }

    : {
        success: false,
        data: {
            reason: 'failed to parse non-empty tag',
            parseOpenTag: ParseOpenTag<T>
        },
        source: T
    }

    : ParseFailed

type ParseNormalTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?
    ParseEmptyTag<T> extends ParseSuccess ? ParseEmptyTag<T>
    : ParseNonEmptyTag<T> extends ParseSuccess ? ParseNonEmptyTag<T>
    : {
        success: false,
        data: {
            reason: 'parse normal tag',
            parseNonEmpty: ParseNonEmptyTag<T>
            parseEmpty: ParseEmptyTag<T>,
        },
        source: T
    }

    : ParseFailed

type ParseTag<T extends string> =
    TrimLeft<T> extends `${infer T}` ?

    ParseNormalTag<T> extends ParseSuccess ? ParseNormalTag<T>

    // failed to parse normal tag, then try parse self-closing-tag
    : ParseSelfClosingTag<T> extends ParseSuccess ? ParseSelfClosingTag<T>

    : {
        success: false,
        data: {
            reason: 'parse tag failed',
            parseNormalTag: ParseNormalTag<T>,
            parseSelfClosingTag: ParseSelfClosingTag<T>
        },
        source: T
    }

    : ParseFailed

type ParseTagOrText<T extends string> =
    TrimLeft<T> extends `${infer T}` ?
    ParseTag<T> extends ParseSuccess ? ParseTag<T>
    : ParseText<T> extends ParseSuccess ? ParseText<T>
    : {
        success: false,
        data: {
            reason: 'parse tag or text failed',
            parseNormalTag: ParseNormalTag<T>,
            parseSelfClosingTag: ParseSelfClosingTag<T>
            parseText: ParseText<T>
        },
        source: T
    }
    : ParseFailed

type ParseNode<T extends string> =
    TrimLeft<T> extends `${infer T}` ?

    // source is empty
    T extends '' ? {
        success: true,
        data: [],
        source: ''
    } :

    ParseTagOrText<T> extends {
        success: true,
        data: infer Node,
        source: `${infer R0}`
    } ?

    // found tag, try parse siblings

    ParseNode<R0> extends {
        success: true,
        data: [...infer NodeList],
        source: `${infer R1}`
    }

    // has siblings
    ? {
        success: true,
        data: [Node, ...NodeList],
        source: R1
    }

    // has no siblings
    : {
        success: true,
        data: [Node],
        source: R0
    }

    // no found
    : {
        success: false,
        data: {
            reason: 'parse node failed',
            parseNormalTag: ParseNormalTag<T>,
            parseSelfClosingTag: ParseSelfClosingTag<T>
            parseText: ParseText<T>
        },
        source: T
    }

    : ParseFailed

// test empty
//  {
//     success: true;
//     data: [];
//     source: '';
// }
type T0 = ParseNode<''>

// test raw text
// {
//     success: true;
//     data: ["123"];
//     source: "";
// }
type T1 = ParseNode<' 123 '>

// test self-closing-tag
// {
//     success: true;
//     data: [{
//         tagName: "input";
//         props: {
//             type: "radio";
//             checked: true;
//         };
//     }];
//     source: "";
// }
type T2 = ParseNode<`<input type="radio" checked />`>

// test normal tag without attributes
// {
//     success: true;
//     data: [{
//         tagName: "div";
//         props: {};
//         children: ["123"];
//     }];
//     source: "";
// }
type T3 = ParseNode<`<div>123</div>`>

// test normal tag with attributes
// {
//     success: true;
//     data: {
//         tagName: "div";
//         props: {
//             id: "test";
//             class: "container";
//             "data-id": "123";
//             title: "456";
//         };
//         children: ["456"];
//     };
//     source: "";
// }
type T4 = ParseNode<`<div id="test" class="container" data-id="123" title="456">456</div>`>

// test multiple nodes
// {
//     success: true;
//     data: [{
//         tagName: "input";
//         props: {
//             type: "radio";
//             checked: true;
//         };
//     }, {
//         tagName: "div";
//         props: {};
//         children: ["123"];
//     }, {
//         tagName: "div";
//         props: {
//             id: "test";
//             class: "container";
//             "data-id": "123";
//             title: "456";
//         };
//         children: ["456"];
//     }];
//     source: "";
// }
type T5 = ParseNode<`
    <input type="radio" checked />
    <div>123</div>
    <div id="test" class="container" data-id="123" title="456">456</div>
`>

// test nest tag
// {
//     success: true;
//     data: [{
//         tagName: "div";
//         props: {
//             class: "parent-0";
//         };
//         children: [{
//             tagName: "div";
//             props: {
//                 class: "child-0";
//             };
//             children: ["text 0"];
//         }, {
//             tagName: "div";
//             props: {
//                 class: "child-1";
//             };
//             children: [...];
//         }, {
//             ...;
//         }];
//     }, {
//         ...;
//     }];
//     source: "";
// }
type T6 = ParseNode<`
    <div class="parent-0">
        <div class="child-0">
            text 0
        </div>

        <div class="child-1">
            text1
        </div>

        <div class="child-2">
            text2
        </div>
    </div>
     <div class="parent-1">
        <div class="child-0">
            text 0
        </div>

        <div class="child-1">
            text1
        </div>

        <div class="child-2">
            text2
        </div>
    </div>
`>

😨

@Lucifier129 It seems that your parser is not robust enough. For example, using your parser:

<br> is parser error, but it's valid HTML.
<div id=main></div> which contains unquoted attributes is parser error, but it's valid HTML.
<div /> which means self-close tag is parser error, but it's valid HTML.

This parser should work:
Playground

type Whitespace = '\u0009' | '\u000a' | '\u000c' | '\u000d' | '\u0020'
type LowerAlpha = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type UpperAlpha = `${uppercase LowerAlpha}`
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

type TrimStart<S> =
  S extends '' ? '' :
  S extends `${Whitespace}${infer Rest}` ? TrimStart<Rest> : S

type ParseAttributes<I, Attrs extends any[]> =
  I extends ` ${infer Rest}` ? ParseAttributes<Rest, Attrs> :
  I extends `>${infer _}` ? [Attrs, I] :
  I extends `/${infer _}` ? [Attrs, I] :
  ParseAttribute<I> extends [infer Attr, infer Rest] ? ParseAttributes<Rest, [...Attrs, Attr]> : never

type ParseAttribute<I> =
  ParseAttributeName<I, ''> extends [infer Name, infer Rest] ? Name extends '' ? never :
  TrimStart<Rest> extends infer Rest ?
  Rest extends `=${infer Rest}` ?
  ParseAttributeValue<TrimStart<Rest>> extends [infer Value, infer Rest] ?
  [{ name: Name, value: Value }, Rest] : never :
  [{ name: Name, value: true }, Rest] : never : never

type ParseAttributeName<I, N extends string> =
  I extends '' ? '' :
  I extends `${infer Head}${infer Rest}` ?
  Head extends ' ' | "'" | '"' | '>' | '/' | '=' ? [N, `${Head}${Rest}`] :
  ParseAttributeName<Rest, `${N}${Head}`> : never

type ParseAttributeValue<I> =
  I extends `"${infer Value}"${infer Rest}` ? [Value, Rest] :
  I extends `'${infer Value}'${infer Rest}` ? [Value, Rest] :
  ParseAttributeValueNoQuote<I, ''> extends [infer Value, infer Rest] ?
  Value extends '' ? never :
  [Value, Rest] : never

type ParseAttributeValueNoQuote<I, O extends string> =
  I extends '' ? [O, I] :
  I extends `${infer Head}${infer Rest}` ?
  Head extends Whitespace | '"' | "'" | '=' | '<' | '>' | '`' ? [O, `${Head}${Rest}`] :
  ParseAttributeValueNoQuote<Rest, `${O}${Head}`> : never

type ParseStartTag<I> =
  I extends `<${infer Rest}` ?
  ParseTagName<Rest, ''> extends [infer TagName, infer Rest] ?
  TagName extends '' ? never : [TagName, Rest] : never : never

type ParseEndTag<I> =
  I extends `</${infer Rest}` ?
  ParseTagName<Rest, ''> extends [infer TagName, infer Rest] ?
  TrimStart<Rest> extends `>${infer Rest}` ?
  [TagName, Rest] : never : never : never

type TagNameChars = LowerAlpha | UpperAlpha | Digit
type ParseTagName<I, N extends string> =
  I extends '' ? N :
  I extends `${infer Head}${infer Rest}` ?
  N extends '' ?
  Head extends TagNameChars ?
  ParseTagName<Rest, Head> : never :
  Head extends TagNameChars | '-' ?
  ParseTagName<Rest, `${N}${Head}`> :
  [N, `${Head}${Rest}`] : never

type ParseText<I, T extends string> =
  I extends `</${infer _}` ? [TrimFirstNewline<T>, I] :
  I extends `<${TagNameChars}${infer _}` ? [TrimFirstNewline<T>, I] :
  I extends `${infer Head}${infer Rest}` ? ParseText<Rest, `${T}${Head}`> : never
type TrimFirstNewline<S> =
  S extends `\r\n${infer Rest}` ? Rest :
  S extends `\n${infer Rest}` ? Rest :
  S extends `\r${infer Rest}` ? Rest : S

type VoidElement = 'area' | 'base' | 'br' | 'col' | 'embed' | 'hr' | 'img' | 'input' | 'link' | 'meta' | 'param' | 'source' | 'track' | 'wbr'
type ParseTag<I> =
  ParseStartTag<I> extends [infer TagName, infer Rest] ?
  ParseAttributes<Rest, []> extends [infer Attributes, infer Rest] ?
  Rest extends `/>${infer Rest}` ? [{ name: TagName, attributes: Attributes, children: [] }, Rest] :
  Rest extends `>${infer Rest}` ?
  TagName extends VoidElement ? [{ name: TagName, attributes: Attributes, children: [] }, Rest] :
  ParseChildren<Rest, []> extends [infer Children, infer Rest] ?
  ParseEndTag<Rest> extends [infer EndTagName, infer Rest] ?
  EndTagName extends TagName ? [{ name: TagName, attributes: Attributes, children: Children }, Rest] :
  never : never : never : never : never : never

type ParseChildren<I, Children extends any[]> =
  string extends I ? never :
  I extends `<${TagNameChars}${infer _}` ?
  ParseTag<I> extends [infer Tag, infer Rest] ? ParseChildren<Rest, [...Children, Tag]> : never :
  I extends `</${TagNameChars}${infer _}` ? [Children, I] :
  ParseText<I, ''> extends [infer Text, infer Rest] ? ParseChildren<Rest, [...Children, Text]> : never

type Html = `
  <div id=main>
    <span class="text-center">
      HTML parser!
    </span>
    <br>
  </div>
`
type AST = ParseTag<TrimStart<Html>>[0]
type VoidElementExample = ParseTag<'<br>'>
type InvalidVoidElementExample = ParseTag<'<div>'>  // invalid HTML, so it's `never`
type UnquotedAttributeExample = ParseTag<'<div id=main></div>'>
type SingleQuoteAttributeExample = ParseTag<'<div id=\'main\'></div>'>
type DoubleQuoteAttributeExample = ParseTag<'<div id="main"></div>'>
type SelfCloseTag = ParseTag<'<div class="e" />'>

@g-plane Yes, it's just a toy parser, follow some rules taken from JSX.
Your parser is interesting too, but I found it is also not robust enough.

ParseTag<TrimStart<'html'>> got never instead of html ，text is valid HTML code.

ParseTag<TrimStart<'<div></div> <div></div>'>> only parsed one tag, instead of all tags

ParseTag<TrimStart<'<2 data-id="test"></2>'>> parsed successfully, but 2 is not a valid tagname.

ParseTag<TrimStart<<1div>234>> got never instead of  '<1div>234' which should be seen as normal text.

@Kingwl @xiaoxiangmoe @Lucifier129 So sorry for disturbing you! To make comments of this PR focus on the main topic of this PR, I've created a repository for collecting such wonderful type-level parsers. If you would like to, you can contribute your parsers to this: g-plane/awesome-ts-type-gymnastics. Then original comments about parsers may be able to be hidden.

Thank you, @ahejilsberg! It's amazing! I'm happy that I can have more informative string values by extending startsWith, endsWith, and includes.
interface String {
    startsWith<T extends string, U extends string>(
        this: T & (string extends T ? never : string),
        searchText: U
    ): this is T extends `${U}${infer _}` ? T : never;

    includes<T extends string, U extends string>(
        this: T & (string extends T ? never : string),
        searchText: U
    ): this is T extends `${infer _0}${U}${infer _1}` ? T : never;

    endsWith<T extends string, U extends string>(
        this: T & (string extends T ? never : string),
        searchText: U
    ): this is T extends `${infer _}${U}` ? T : never;
}

const sample = (input: 'foo' | 'bar' | 'baz' | 'qux' | 'quux' | 'corge' | 'grault') => {
    if (input.endsWith('x')) {
        input; // 'qux' | 'quxx'
    } else if (input.startsWith('ba')) {
        input; // 'bar' | 'baz'
    } else if (input.includes('r')) {
        input; // 'corge' | 'grault'
    } else {
        input; // 'foo';
    }
};

I'm not sure here is the correct place to ask to add something to lib.d.ts, I guess this can be added to lib.d.ts.
Playground

Could anyone tell me why we need string extends S ? It seems that S will never be string

Basically you wanna differentiate between these:
type T0<T extends string> = string extends T ? never : T;

type T1 = T0<string>; // never
type T2 = T0<'test'>; // 'test'

@aminpaks @whatasoda
I'm sorry you may not get my point……
I know what you say, I know the difference between string and 'test'
IMO, if we remove string extends S ? string[] :, it should also work.
But, when I try, there was a error say Type instantiation is excessively deep and possibly infinite.
So I think the reason of string extends S ? string[] : is preventing the deep evaluate

@aminpaks @whatasoda
I'm sorry you may not get my point……
I know what you say, I know the difference between string and 'test'
IMO, if we remove string extends S ? string[] :, it should also work.
But, when I try, there was a error say Type instantiation is excessively deep and possibly infinite.
So I think the reason of string extends S ? string[] : is preventing the deep evaluate

Sorry again...
I just assumed that Split will only accept string literals, I forgot that Split need to handle all case including normal string

It seems that the code below isn't allowed even with this PR:
const a = 'a' as const
const ab = `${a}b` as const

I hope the type of variable ab can be literal type 'ab'. Will this be supported?

It does seem like #13969 would be easier to tackle after this gets merged in.

This is really cool! Thank you @ahejlsberg !
I wrote a small GraphQL Lexer parser. I takes a GraphQL SDL language query as generic, and breaks it to identifier in a way the original graphql-js Lexer implements it. Took some of the ideas from the concept of the HTML parser of @Lucifier129 and the JSON parser of Kingwl.
It's a bit buggy with comments (marked as #), and I'm sure it can be improved. The result of it is a LinkedList with { next: Token, value?: string.
I can be also used as a base for writing a GraphQL parser similar to this, that returns a fully typed DocumentNode ;)
*** You can find it here)

@ahejlsberg do you think this feature can be extended to a custom error reporting from the type-system? So if parsing/inference fails, it would be possible to do something like that instead of return never?
type AssertSomething<T extends string> = isValid<T> extends true ? T : throw `Value ${T} is invalid!`;

@dotansimha See #23689.

@dotansimha Please consider contributing your parser to here, instead of making the comments of this PR very long.

@dotansimha I'm working on throw types today. I believe it will be powerful with this PR

It works based on this PR now!

I have submitted PR for throw types at #40402, let's move there for discussion!

Hi, @ahejlsberg I have added a new modifier typeof T (to cast any type into a string) and loosen the type constraint for typeof modifier. It will be very useful when "debugging" a type (useful for #40402). Would you like to pick https://github.com/microsoft/TypeScript/pull/40402/commits/bf6c809f285cdd443f62df3e614981d6eea3e1bd into this branch?

I found a useful scenario with template string types: Make a more intelligent document.querySelector. Here is the result:

Implementation can be found at here.

BTW has anyone noticed this comment? Seems this type should be called Template literal type

Small terminology nitpick:

`abc` is a a template literal: https://tc39.es/ecma262/#sec-template-literal-lexical-components

The term template string is used in the context of tagged templates such as tagFunc`ab${123}cd`. The input of tagFunc is:

The template strings ['ab', 'cd']. https://tc39.es/ecma262/#sec-static-semantics-templatestrings
The substitution value 123

If a mapped type has an as clause, is there a reason to continue to require that the type on the right of the in operator needs to be assignable to string | number | symbol?

@ethanresnick You're right, no reason to constrain the in T type when an as N clause is present. Fixed by my latest commit.

Also, in those mapped types, why does N need to be assignable to string (per your OP, as opposed to string | number | symbol)?

The OP was just wrong here, it's actually string | number | symbol.

I wrote a simple calculator which supports big integers and negatives (compared with @Kingwl's implementation):
import { Plus, Minus } from 'typed-eval'

type test1 = Plus<9999999, 11111> // 10011110
type test2 = Minus<1000000, 99999> // 900001
type test3 = Plus<9999999, -11111> // 9988888
type test4 = Minus<-1000000, -99999> // -900001

source code / playground

@rauschma @aminpaks Terminology updated to template literal type.

I was able to put together a small REST API example. I love this feature!
https://twitter.com/nettofarah/status/1303042368640135168?s=20

@ahejlsberg Have you and/or the team considered "tagged template literal" syntax for the string transformation functions?
E.g.
uppercase`${T}`

instead of
`${uppercase T}`

This would bring the type-level template syntax more in line with the value-level equivalent.
The only immediate cost I can see is that it composition would require an extra level of nesting in some cases:
`${uppercase`${TFirst}`}${TRest}`

Thoughts?

@treybrisbane I don’t know if that makes sense, but another option would be:
`${T}:uppercase ${T}:lowercase ${T}:capitalize ${T}:uncapitalize`

Previously:
`${uppercase T} ${lowercase T} ${capitalize T} ${uncapitalize T}`

If, however, operators such as uppercase T also work elsewhere, then the latter is clearly the better choice.

Can fields be made optional or non-optional within the as clause of mapped types? For instance, what if we want to prevent the following from producing a type error? (the runtime will––in most cases––behave the same as if the entry ["c", undefined] existed within t after all).
type T = {
  a: "a";
  b: 1;
  c: undefined;
};

// produces type error (missing field `c`)
const t: T = {
  a: "a",
  b: 1,
}

It would be nice to be able to do the following:
type MakeUndefinedsOptional<T> = {
  [K in P as P[K] extends undefined ? K+? : K]: P[K];
}

type NewT = MakeUndefinedsOptional<T>;

// no longer produces type error
const t: NewT = {
  a: "a",
  b: 1,
}

I believe this would address #12754 as well.

It should be
type MakeUndefinedsOptional<T> = {
    [P in keyof T as T[P] extends undefined ? P : never]?: T[P]
} & {
        [P in keyof T as T[P] extends undefined ? never : P]: T[P]
    }

I'm trying to think of a way to type having a known prefix and an unknown suffix:
type PrefixedProp<T> = { [P in keyof T & string as `${P}-${...}`]: T[P] }
type T70 = PrefixedProp<{ str: string, num: number }>;
const a: T70 = {
    'str-1': 'foo',
    'str-2': 'bar',
    'num-1': 1321321
};

Is something like this possible? I have tried some different ways but couldn't make it work.

I cannot find example how I can use template literal type for keys of my structure.
For example I have this type:
type Repo<T> = {
  getItems: () => T[];
  getItemById: (id: string) => T;
  getItemsByTitle: (title: string) => T[];
}

And I want to make it less generic by replacing item(s) with entity name. I have tried following but it does not work:
type NamedRepo<T, Sing extends string, Plur extends string> = {
  [`get${Plur}`]: () => T[];
  [`get${Sing}ById`]: (id: string) => T;
  [`get${Plur}ByTitle`]: (title: string) => T[];
}

I can think of workaround using mapped types, Split, Join and intersection, but it should be some simple way to achieve this

@zaverden
type NamedRepo<T, Plur extends string, Sing extends string> =
    {
        [P in keyof T & string as `get${Plur}` | `get${Sing}ById` | `get${Plur}ByTitle`]: T[P]
    }

type NamedFoo = NamedRepo<{ foo: () => 123 }, 'Foo', 'Foos'>

@mohsen1 thank you for sharing but your solution does not provide what I expect.
type FooRepo = NamedRepo<FooObj, 'Foo', 'Foos'>;
// expected:
// type FooRepo = {
//     getFooById: (id: string) => FooObj;
//     getFoos: () => FooObj[];
//     getFoosByTitle: (title: string) => FooObj[];
// }

Here is my solution with replace, it gives expected output, but it require 2 separate types for singular and plural substitutions, 2 replace helpers, flatten helpers (this one for better developer experience after intersection).
I hope same result can be achieved simpler way.
https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-88#code/C4TwDgpgBAkgJhAdsAlqAPAFQHxQLxSYDcAsAFAD0AVFVAKoDO0wAFig1AO4BOAhmJG5RgAeygBjEYgBuEbsCgpkcpuNRSOoqGAA2vJVBEAjAFYQ1w8NCoVyoSFABie4MCQB5U+eBYoEAB5uiHAcxmZquATwSKgY5FBQAN5QANoA1oqIUGkQICIAZoQAugBchOlFUAC+5NikZHZWUABKELq84hDuiJ3oAMoArl4WAUEhUAzA3EoA5gA0UH0QvNziLH6BSOOT04jzLW16nRtjHDuzkfGLQ+EKo1scAAYAJIlK+XJQADJVr0sra1+b0QHyEzSqjygAH4oC9Ej9Xq12p0geDIWVBsNgORGg4kUcuj0IDBEABpXIMLALf6rdb3YJnKazBb4jrQenbJl7S5kRJXFIABUy2VyBUIUAAZBMuTMoLwOKzOt1egLqctaSzDmzsKVygKiuQag0yPZoEiRH1ZlhIkkrjMIMBngAhEDwMoAChQcDK5z2AEp8LhiIacSamuaBToBtxrfhbWQEvbHR6A3ggykivVEw7nSBMGgdBAPbFCz6Zan05mQ8bTVAAHK8AC2EDg5qpi1mJwe0t2+0j0a7DJ7Fzjzl4rg8WPQV0VhM6JPJIEp5ste2tCwA5M8N9SR1LZ8riWSKegI1GYzhN9uFv3uNhaqHa44RCJPCY44kqvUny-zXGG82rZtCI6DPq+pibmBO5QBuYEMBudRAA

I would be inclined to do something like this:
type FromEntries<T extends [PropertyKey, any]> = { [K in T[0]]: Extract<T, [K, any]>[1] }
type NamedRepo<T, Sing extends string, Plur extends string> = FromEntries<
    [`get${Plur}`, () => T[]] |
    [`get${Sing}ById`, (id: string) => T] |
    [`get${Plur}ByTitle`, (title: string) => T[]]
>

or any other type function that walks through key/value pairs and turns them into an object.

@jcalz thank you. It looks good, I can use  this option.
But I still hope we will get more straightforward syntax like this
type NamedRepo<T, Sing extends string, Plur extends string> = {
  [`get${Plur}`]: () => T[];
  [`get${Sing}ById`]: (id: string) => T;
  [`get${Plur}ByTitle`]: (title: string) => T[];
}

@jcalz The new mapped type as clause can be used to make FromEntries even simpler:
type FromEntries<T extends [keyof any, any]> = { [K in T as K[0]]: K[1] };

type NamedRepo<T, Sing extends string, Plur extends string> = FromEntries<
    [`get${Plur}`, () => T[]] |
    [`get${Sing}ById`, (id: string) => T] |
    [`get${Plur}ByTitle`, (title: string) => T[]]
>;

@zaverden The more straightforward syntax you're hoping for wouldn't happen until we someday (hopefully) unify mapped type and arbitrary index signatures. See #26797 for more on that.

Having looked at this PR again, I'd like to join the others in the thread who have voiced their concern around the addition of the uppercase, lowercase, capitalize, and uncapitalize "modifiers".
I don't feel that adding new keywords for each possible kind of transformation is a scalable or practical approach to this problem. In other words, IMO there doesn't appear to be an obvious way of generalising this approach going forward.
I suggested something like "tagged template literal" syntax above. Granted that's not lexically much different, but is semantically different in that the construct becomes
builtinTypeFunction`template string`

where builtinTypeFunction could be any current or future builtin type-level function. Such syntax could also theoretically enable the use of user-defined type-level functions. In such a world, it may even become possible to move the builtin functions into the language. The current syntactic approach in this PR would not allow any of this.
Alternatively (and perhaps even preferably), these transformations could be defined as builtin type aliases, e.g.
Uppercase<'some string here'>

This would allow the compiler to special case them now, but easily move them into normal type alias definitions later if the language were to become flexible enough to implement them. In other words, it enables a migration away from special casing. The proposed syntax does not enable such a migration, meaning that the compiler will have to support this syntax forever (even if the language becomes flexible enough to supercede it).
Naturally, this is just my own opinion, and I offer it as such. But once this PR is merged, there'll likely be no going back, so I want to offer it now in the hope that it'll be taken onboard.
That said, regardless of what the team decides to go with, this will still be an amazing and highly useful set of features that I'll be super glad to have in the language. 🙂

I suggested something like "tagged template literal" syntax above. Granted that's not lexically much different, but is semantically different in that the construct becomes

builtinTypeFunction`template string`

+1 for this idea, it allows the possibility of future migration.

@treybrisbane Current syntax does not limit how many modifiers can exist nor closes the door to possibility of user defined modifiers. In my opinion what you suggested looks worse when interpolating. Consider the first example in the PR:
type GetterName<T extends string> = `get${capitalize T}`;

If I understand your proposal correctly, with your proposed syntax this would look like:
type Capitalize<T extends string> = capitalize`${T}`
type GetterName<T extends string> = `get${Capitalize<T>}`;

@treybrisbane Current syntax does not limit how many modifiers can exist nor closes the door to possibility of user defined modifiers. In my opinion what you suggested looks worse when interpolating. Consider the first example in the PR:

Correct, I think the matter of "looks worse/better" is also important. Having modifiers opens up questions about readability, new syntax and specially in terms of composition as already mentioned in the thread.
type T0<T extends string> = `get${capitalize lowercase T}`;
// vs
type T1<T extends string> = `get${Capitalize<Lowercase<T>>}`;
// vs
type T2<T extends string> = `get${capitalize`${lowercase`${T}`}`}`;

I think
type T2<T extends string> = `get${capitalize`${lowercase`${T}`}`}`;
isn't a problem for me. In JavaScript, you also write this. It's more isomorphic.

Merging this PR in preparation for 4.1 beta. We hear the feedback on uppercase, lowercase, etc. modifiers and will continue to think about more general solutions.

Is
type test = `${infer I}`;

working as intended, i.e., not an error?

@davidmurdoch That should be an error. Thanks for catching!

I have to agree that
type T1<T extends string> = `get${Capitalize<Lowercase<T>>}`;

just feels way more like Typescript and doesn't require such special keywords.
@ahejlsberg will the discussion about this continue in this PR even after it was merged, or will there be some other place?

Perhaps I have misunderstood the intended use of this but I had expected this to work:
type StringWithId<T extends string = string> = `row_${T}`;

function parseId(value: StringWithId){
}

parseId("helloWorld"); // should fail
parseId("row_one") // should pass

playground
but it compiles fine.
this does fail compilation though:
type StringWithId<T extends string> = `row_${T}`;

function parseId<T extends string>(value: StringWithId<T>){
}

parseId("helloWorld"); // should fail
parseId("row_one") // should pass

This also compiles OK:
type StringWithId<T extends string> = `row_${T}`;

function parseId<T extends string>(value: StringWithId<T>, other: T){
}

parseId("row_one", "on") // should fail

@Roaders you can verify for yourself that StringWithId<string> is just string, so there's no way for what you're doing to work unless your function is generic.  Your second example is the way to do it.
The third example doesn't work because of something like #14829: the compiler dutifully infers the union of the different inference candidates for T; if you don't want that to happen you need to make one of the T's non-inferrable or less inferrable (e.g., (value: StringWithId<T>, other: T & {})).
Also, keep in mind that you probably don't want parseId() to accept string values unless its implementation guards against invalid inputs at runtime.  If you write let x = "oops"; parseId(x); it's going to be accepted, since StringWithId<string> is string.  If that matters you can constrain T like T extends (string extends T ? never : string).

@Roaders This is the closest I could get:
type IsStringWithId<T> = T extends `row_${infer _}` ? true : never;

function parseId<T extends (string extends T ? never : string)>(value: T, check: IsStringWithId<T>) {
}

parseId("row_one", true); // compiles
parseId("helloWorld", true); // Argument of type 'boolean' is not assignable to parameter of type 'never'.

@Roaders The following works:
type StringWithId<T extends string> = T extends `row_${infer _}` ? T : never;

declare function parseId<T extends string>(value: StringWithId<T>): void;

declare const s: string;

parseId("helloWorld");  // Error
parseId("row_one");  // Ok
parseId(s);  // Ok

If you want an error on the last line, you can use this declaration:
type StringWithId<T extends string> = string extends T ? never : T extends `row_${infer _}` ? T : never;

I've created (limited length) type-checked hex strings using this! https://gist.github.com/davidmurdoch/3145903f3a267543568225c68dcc325b

Fun fact: You can already kind‑of sort‑of do something similar to the mapped type as clauses in TypeScript 4.0:
From https://gist.github.com/davidmurdoch/de393cca445effbed4d7e22fe33c5b85:
type ReverseHexLookUp = { [K in keyof HexLookUp as HexLookUp[K]]: K };

Is mostly equivalent to:
type ReverseHexLookUp = {
	[K in keyof HexLookUp extends infer T
		? T extends keyof HexLookUp
			? HexLookUp[T]
			: never
		: never
	]: keyof HexLookUp extends infer U
		? U extends keyof HexLookUp
			? HexLookUp[U] extends K
				? U
				: never
			: never
		: never;
};

@ahejlsberg ideas for writing a helper type for removing duplicate chars? I'm trying to figure out how to write a helper type that removes duplicate whitespaces from a given string.  Tried to implement that but it seems like it isn't possible? i'm either getting Type instantiation is excessively deep and possibly infinite. or the language-service crashes (VSCode insiders, TS 4.1.0-dev.20200910).
I guess this hits the limit you exampled with the Digit / camel-case? I think the only way to solve that is to execute it as regex like /\s+/g?

This is amazing.  Still having trouble with scenarios similar to the dotted key path if I try to make it an error to pass an unknown key path for a type.
If the object is a generic T that still has some known properties, the compatibility check fails for the known properties.
Is there a better approach to implement this?
Or any way to extract the known keys of T? (e.g. for T extends { a: any, x: string } -> "a" | "x")

type PathGetter<T> = {
	<TPath extends string>(path: OnlyIfPathCompatible<T, TPath>): PropType<T, TPath>;
	readonly obj: T;
};

function getValues<T extends { a: { b: any }, x: string }>(pathGetter: PathGetter<T>) {
	return {
		obj: pathGetter.obj,
		x: pathGetter("x"), //Error, though it shouldn't be
		ab: pathGetter("a.b"), //Error, thought it shouldn't be
	};
}

function getValuesNonGeneric(pathGetter: PathGetter<{ a: { b: any }, x: string }>) {
	return {
		obj: pathGetter.obj,
		x: pathGetter("x"),
		ab: pathGetter("a.b"),
		//@ts-expect-error Not allowed, as expected.
		c: pathGetter("c")
	};
}

//Same as from the example, but returns `never` instead of unknown.
type PropType<T, Path extends string> =
	string extends Path ? never :
	Path extends keyof T ? T[Path] :
	Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropType<T[K], R> : never :
	never;

type IsNever<T> = keyof any extends keyof T ? (0 extends (1 & T) ? false : true) : false;
type OnlyIfPathCompatible<T, TPath extends string> = IsNever<PropType<T, TPath>> extends true ? never : TPath;

declare function createPathGetter<T>(o: T): PathGetter<T>;

type Values<T> = { obj: T, x: PropType<T, "x">, ab: PropType<T, "a.b"> };

const obj = { a: { b: {c: 42, d: "hello" }}, x: "hello" } as const;
const getter = createPathGetter(obj);
//This still returns the correct type, as expected.
const values: Values<typeof obj> = getValues(getter);
``
(sorry for tabs)

type Extent<T extends number> = number | `${T}px` | `${T}pt`;

const ex1 : Extent<number> = 42;
const ex2 : Extent<number> = '42px';
const ex3 : Extent<number> = '42uu'; // <-- should fail!

Why is the line with ex3 not failing?
https://www.typescriptlang.org/play?ts=4.1.0-dev.20200913#code/C4TwDgpgBAogHsCA7YAeAKlCDkBMDOUSArgLYBGEATgHxQC8RZlVUAPlAAYAkA3ugF8wcTuy59BYYJwDcAKDkBjAPZJ8wLHACMUAFywcKVCQrU6jACwAmeSrUbsVvQcRGTLc1ADk14V9uq6poAzM7wrmjuZgze1sTE-lAA9ElQqAC06VD4ABbKxAA2uFAAZgCGAJYFAIRyQA

type Extent<T> = T extends number ? number : T extends `${infer _}px` | `${infer _}pt` ? T : never;

const ex1 : Extent<number> = 42;
const ex2 : Extent<'42px'> = '42px';
const ex3 : Extent<'42uu'> = '42uu'; // failed!

or
type Extent<T extends number> = number | `${T}px` | `${T}pt`;

const ex1 : Extent<42> = 42;
const ex2 : Extent<42> = '42px';
const ex3 : Extent<42> = '42uu'; // failed!

If you say T is number, number is number | ... absolutely. @derolf

@meritozh I still don't get why
string is assignable to Foo<T> with type Foo<T extends number> = ${T};

@derolf I think this is a bug. You can open an issue.

Hey @ahejlsberg, this PR is really great, thank you so much.
I just wanted to put my two cents in here about tuple treatment. I was thinking of using as ... extends clause to filter out tuple elements while preserving their labels. My intention (between others) is to improve curry types for ramda. Let's take an example:
type At<O extends object, K extends string | symbol | number> =
    O[keyof O & K]

type GapsOf<L1 extends List, L2 extends List> = {
    [K in keyof L1 as At<L2, K> extends x ? K : never]: L1[K]
}

type t0 = GapsOf<[a: 1, b: 2], [__, 2]> // got: {0: 1}       // expected [a: 1]
type t1 = GapsOf<[a: 1, b: 2], [__]>    // got: {0: 1, 1: 2} // expected [a: 1, b: 2]
type t2 = GapsOf<[a: 1, b: 2], []>      // got: {0: 1, 1: 2} // expected [a: 1, b: 2]

This GapsOf type is the type that is responsible for the detection of placeholders in curried functions. If there is a placeholder x, then that (parameter) type is carried over for the next step e.g.:
const toCurry = (name: string, age: number, single: boolean, nicknames: string) => true
const curried = curry(toCurry)

curried(__, 26)(__, true, 'JJ')('Jane') // boolean
// we have proper types in each one of the steps

The only problem here is that parameter names are lost in the subsequent curry steps.
At the moment, mapped types do not iterate over all the properties of a tuple, just its direct members, which preserves their tuple shape. However, I've noticed that this is not the case with the as ... extends clause, the shape of the tuple gets altered into an object.
Do you think that it's reasonable to treat mapped tuple types with as clauses the same way that classic mapped types do?

In the article describing beta of 4.1 there is the following example:
type PropEventSource<T> = {
    on<K extends string & keyof T>
        (eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void;
};

declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;

let person = makeWatchedObject({
    firstName: "Homer",
    age: 42,
    location: "Springfield",
});

// works! 'newName' is typed as 'string'
person.on("firstNameChanged", newName => {
    // 'newName' has the type of 'firstName'
    console.log(`new name is ${newName.toUpperCase()}`);
});

// works! 'newAge' is typed as 'number'
person.on("ageChanged", newAge => {
    if (newAge < 0) {
        console.log("warning! negative age");
    }
})

and it works in playground for v4.1.0-dev.20200919.
I have no understanding why if on<K extends string & keyof T> it works for number type?

In this example:
type Action = "verify" | "write";
type ContentMatch = "match" | "nonMatch";
type Outcome = `${Action}_${ContentMatch}`;

    const action : Action = verify === true ? `verify` : `write`;
    const contentMatch: ContentMatch = contentMatches === true ? `match` : `nonMatch`;

    const outcome: Outcome = `${action}_${contentMatch}`;

Playground
the typing all works fine but when I am actually trying to construct a string that is of type Outcome I can't seem to do it in a way that is type checked. I get:

Type 'string' is not assignable to type '"verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"'.(2322)

I can get round this by doing as Outcome but that will accept any string and could break the code. I tried:
const outcome: Outcome = `${action}_${contentMatch}` as const;

but that gives:

A 'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals.(1355)

How can we construct strings that are typed as templates in a typesafe way?

Will a simple + be available?

I wonder if it would be possible to type the variables in a string interpolation?  🤔
 e.g
type Name = string
type Greeting = `Hello ${Name}`

const friendGreeting : Greeting = `Hello ${5}`   # Invalid: 5 should be string

I am thinking this might be useful in internationalization where one might want some variables interpolated in a string

Using this PR I'm finally able to to a lot of things not working before.
The biggest thing was a library (kendo-ui) which wraps classes and on can use getters and setters in the following way:
class Test {
    PropertyA!: string;
    PropertyB!: Test;
}

var test = new Test();
test.PropertyB = new Test();
test.PropertyB.PropertyB = new Test();
var wrapped = kendo.observable(test );
wrapped.set('PropertyB.PropertyB.PropertyA', 'test');

Using this PR I was able to implement a Type that lists all possibilities, which obviously would fail because it is circular because it could go endlessly PropertyB.PropertyB.PropertyB..........
I've tried fixed it the following way
    type PathExcludes = '__typename';
    type PathImpl<T, TFound, TPref extends keyof T = never> =
        T extends TFound ? never :
        TPref extends never
        ? PathImpl<T, TFound, keyof T> | keyof T
        : TPref extends string
        ? T[TPref] extends string | number | Date ? never
        : `${TPref}.${PathImpl<T[TPref], TFound | T, Exclude<keyof T[TPref], PathExcludes>> & string}` | `${TPref}.${Exclude<keyof T[TPref], PathExcludes> & string}`
        : keyof T;
    type PathImpl2<T> = PathImpl<T, never, Exclude<keyof T, PathExcludes>> | Exclude<keyof T, PathExcludes>;
    type Path<T> = (PathImpl2<T> extends string | keyof T ? PathImpl2<T> : keyof T);

See also playground
Is there anyway to fix and simplify this?
And even better to allow to give a hint to the type so it will only load the next X number of possibilities?
This would actually allow to have code completion with string parameters ;)
Here is an enhanced version that allows for some deeper referencing
Enhanced Playground

In the article describing beta of 4.1 there is the following example:
type PropEventSource<T> = {
    on<K extends string & keyof T>
        (eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void;
};

declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;

let person = makeWatchedObject({
    firstName: "Homer",
    age: 42,
    location: "Springfield",
});

// works! 'newName' is typed as 'string'
person.on("firstNameChanged", newName => {
    // 'newName' has the type of 'firstName'
    console.log(`new name is ${newName.toUpperCase()}`);
});

// works! 'newAge' is typed as 'number'
person.on("ageChanged", newAge => {
    if (newAge < 0) {
        console.log("warning! negative age");
    }
})

and it works in playground for v4.1.0-dev.20200919.
I have no understanding why if on<K extends string & keyof T> it works for number type?
@Constantiner
Take a look at this:
Playground

Is this what you want to have?

Is this what you want to have?
--> the string stuff in you snippet never filters out numbers from the keys in the K argument (K is basically always a string no patter the type that is being returned by the type)

Sorry, I've just paid not too much attention to code and made mistake. Everything is ok with code. I deleted my original comment to not to mess.

@Roaders With #40707 we support template literal expressions in const contexts.

Not sure, if there are already plans for this: How much effort would it be to implement as-clauses in mapped tuple types, so  that they can be used as a filter mechanism  (as suggested above)?
I tried/would like to write something like the following, which currently converts the tuple to an object and keeps stale indices:
type FilterUndefinedX<T extends any[]> = {
    //  filter undefined tuple items with as-clause
    [K in keyof T as T[K] extends undefined ? never : K]: T[K]
}

type TX = FilterUndefinedX<[number, string, undefined, number]>
// { [x: number]: string | number | undefined; 0: number; 1: string; 3: number; length: 4; ... }
// expected: [number, string, number]

As an alternative, filtering could be achieved with recursive types, for example:
type FilterUndefined<T extends any[]> = T extends [] ? [] :
    T extends [infer H, ...infer R] ?
    H extends undefined ? FilterUndefined<R> : [H, ...FilterUndefined<R>] : T

type T1 = FilterUndefined<[number, string, undefined, number]> // [number, string, number]
type T2 = FilterUndefined<[undefined, 2]> // [2]

Though, advantage of the first implementation with mapped tuple types is that it provides uniform behaviour for all mapped types regardless object or tuple type.
Besides: is there a difference performance-wise?
Playground

@ford04 I think mapping arrays/tuples with as might make sense, but not to shift indices around automagically.   In