1// @strict: true 2// @declaration: true 3 4// Template types example from #12754 5 6const createScopedActionType = <S extends string>(scope: S) => <T extends string>(type: T) => `${scope}/${type}` as `${S}/${T}`; 7const createActionInMyScope = createScopedActionType("MyScope"); // <T extends string>(type: T) => `MyScope/${T}` 8const MY_ACTION = createActionInMyScope("MY_ACTION"); // 'MyScope/MY_ACTION' 9 10// Union types are distributed over template types 11 12type EventName<S extends string> = `${S}Changed`; 13type EN1 = EventName<'Foo' | 'Bar' | 'Baz'>; 14type Loc = `${'top' | 'middle' | 'bottom'}-${'left' | 'center' | 'right'}`; 15 16// Primitive literal types can be spread into templates 17 18type ToString<T extends string | number | boolean | bigint> = `${T}`; 19type TS1 = ToString<'abc' | 42 | true | -1234n>; 20 21// Nested template literal type normalization 22 23type TL1<T extends string> = `a${T}b${T}c`; 24type TL2<U extends string> = TL1<`x${U}y`>; // `ax${U}ybx{$U}yc` 25type TL3 = TL2<'o'>; // 'axoybxoyc' 26 27// Casing intrinsics 28 29type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`; 30 31type TCA1 = Cases<'bar'>; // 'BAR bar Bar bar' 32type TCA2 = Cases<'BAR'>; // 'BAR bar BAR bAR' 33 34// Assignability 35 36function test<T extends 'foo' | 'bar'>(name: `get${Capitalize<T>}`) { 37 let s1: string = name; 38 let s2: 'getFoo' | 'getBar' = name; 39} 40 41function fa1<T>(x: T, y: { [P in keyof T]: T[P] }, z: { [P in keyof T & string as `p_${P}`]: T[P] }) { 42 y = x; 43 z = x; // Error 44} 45 46function fa2<T, U extends T, A extends string, B extends A>(x: { [P in B as `p_${P}`]: T }, y: { [Q in A as `p_${Q}`]: U }) { 47 x = y; 48 y = x; // Error 49} 50 51// String transformations using recursive conditional types 52 53type Join<T extends unknown[], D extends string> = 54 T extends [] ? '' : 55 T extends [string | number | boolean | bigint] ? `${T[0]}` : 56 T extends [string | number | boolean | bigint, ...infer U] ? `${T[0]}${D}${Join<U, D>}` : 57 string; 58 59type TJ1 = Join<[1, 2, 3, 4], '.'> 60type TJ2 = Join<['foo', 'bar', 'baz'], '-'>; 61type TJ3 = Join<[], '.'> 62 63// Inference based on delimiters 64 65type MatchPair<S extends string> = S extends `[${infer A},${infer B}]` ? [A, B] : unknown; 66 67type T20 = MatchPair<'[1,2]'>; // ['1', '2'] 68type T21 = MatchPair<'[foo,bar]'>; // ['foo', 'bar'] 69type T22 = MatchPair<' [1,2]'>; // unknown 70type T23 = MatchPair<'[123]'>; // unknown 71type T24 = MatchPair<'[1,2,3,4]'>; // ['1', '2,3,4'] 72 73type SnakeToCamelCase<S extends string> = 74 S extends `${infer T}_${infer U}` ? `${Lowercase<T>}${SnakeToPascalCase<U>}` : 75 S extends `${infer T}` ? `${Lowercase<T>}` : 76 SnakeToPascalCase<S>; 77 78type SnakeToPascalCase<S extends string> = 79 string extends S ? string : 80 S extends `${infer T}_${infer U}` ? `${Capitalize<Lowercase<T>>}${SnakeToPascalCase<U>}` : 81 S extends `${infer T}` ? `${Capitalize<Lowercase<T>>}` : 82 never; 83 84type RR0 = SnakeToPascalCase<'hello_world_foo'>; // 'HelloWorldFoo' 85type RR1 = SnakeToPascalCase<'FOO_BAR_BAZ'>; // 'FooBarBaz' 86type RR2 = SnakeToCamelCase<'hello_world_foo'>; // 'helloWorldFoo' 87type RR3 = SnakeToCamelCase<'FOO_BAR_BAZ'>; // 'fooBarBaz' 88 89// Single character inference 90 91type FirstTwoAndRest<S extends string> = S extends `${infer A}${infer B}${infer R}` ? [`${A}${B}`, R] : unknown; 92 93type T25 = FirstTwoAndRest<'abcde'>; // ['ab', 'cde'] 94type T26 = FirstTwoAndRest<'ab'>; // ['ab', ''] 95type T27 = FirstTwoAndRest<'a'>; // unknown 96 97type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' |'8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; 98 99type HexColor<S extends string> = 100 S extends `#${infer R1}${infer R2}${infer G1}${infer G2}${infer B1}${infer B2}` ? 101 [R1, R2, G1, G2, B1, B2] extends [HexDigit, HexDigit, HexDigit, HexDigit, HexDigit, HexDigit] ? 102 S : 103 never : 104 never; 105 106type TH1 = HexColor<'#8080FF'>; // '#8080FF' 107type TH2 = HexColor<'#80c0ff'>; // '#80c0ff' 108type TH3 = HexColor<'#8080F'>; // never 109type TH4 = HexColor<'#8080FFF'>; // never 110 111// Recursive inference 112 113type Trim<S extends string> = 114 S extends ` ${infer T}` ? Trim<T> : 115 S extends `${infer T} ` ? Trim<T> : 116 S; 117 118type TR1 = Trim<'xx '>; // 'xx' 119type TR2 = Trim<' xx'>; // 'xx' 120type TR3 = Trim<' xx '>; // 'xx' 121 122type Split<S extends string, D extends string> = 123 string extends S ? string[] : 124 S extends '' ? [] : 125 S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : 126 [S]; 127 128type T40 = Split<'foo', '.'>; // ['foo'] 129type T41 = Split<'foo.bar.baz', '.'>; // ['foo', 'bar', 'baz'] 130type T42 = Split<'foo.bar', ''>; // ['f', 'o', 'o', '.', 'b', 'a', 'r'] 131type T43 = Split<any, '.'>; // string[] 132 133// Inference and property name paths 134 135declare function getProp<T, P0 extends keyof T & string, P1 extends keyof T[P0] & string, P2 extends keyof T[P0][P1] & string>(obj: T, path: `${P0}.${P1}.${P2}`): T[P0][P1][P2]; 136declare function getProp<T, P0 extends keyof T & string, P1 extends keyof T[P0] & string>(obj: T, path: `${P0}.${P1}`): T[P0][P1]; 137declare function getProp<T, P0 extends keyof T & string>(obj: T, path: P0): T[P0]; 138declare function getProp(obj: object, path: string): unknown; 139 140let p1 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a'); 141let p2 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a.b'); 142let p3 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a.b.d'); 143 144type PropType<T, Path extends string> = 145 string extends Path ? unknown : 146 Path extends keyof T ? T[Path] : 147 Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropType<T[K], R> : unknown : 148 unknown; 149 150declare function getPropValue<T, P extends string>(obj: T, path: P): PropType<T, P>; 151declare const s: string; 152 153const obj = { a: { b: {c: 42, d: 'hello' }}}; 154 155getPropValue(obj, 'a'); // { b: {c: number, d: string } } 156getPropValue(obj, 'a.b'); // {c: number, d: string } 157getPropValue(obj, 'a.b.d'); // string 158getPropValue(obj, 'a.b.x'); // unknown 159getPropValue(obj, s); // unknown 160 161// Infer type variables in template literals have string constraint 162 163type S1<T> = T extends `foo${infer U}bar` ? S2<U> : never; 164type S2<S extends string> = S; 165 166// Check that infer T declarations are validated 167 168type TV1 = `${infer X}`; 169 170// Batched single character inferences for lower recursion depth 171 172type Chars<S extends string> = 173 string extends S ? string[] : 174 S extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer C4}${infer C5}${infer C6}${infer C7}${infer C8}${infer C9}${infer R}` ? [C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, ...Chars<R>] : 175 S extends `${infer C}${infer R}` ? [C, ...Chars<R>] : 176 S extends '' ? [] : 177 never; 178 179type L1 = Chars<'FooBarBazThisIsALongerString'>; // ['F', 'o', 'o', 'B', 'a', 'r', ...] 180 181// Infer never when source isn't a literal type that matches the pattern 182 183type Foo<T> = T extends `*${infer S}*` ? S : never; 184 185type TF1 = Foo<any>; // never 186type TF2 = Foo<string>; // never 187type TF3 = Foo<'abc'>; // never 188type TF4 = Foo<'*abc*'>; // 'abc' 189 190// Cross product unions limited to 100,000 constituents 191 192type A = any; 193 194type U1 = {a1:A} | {b1:A} | {c1:A} | {d1:A} | {e1:A} | {f1:A} | {g1:A} | {h1:A} | {i1:A} | {j1:A}; 195type U2 = {a2:A} | {b2:A} | {c2:A} | {d2:A} | {e2:A} | {f2:A} | {g2:A} | {h2:A} | {i2:A} | {j2:A}; 196type U3 = {a3:A} | {b3:A} | {c3:A} | {d3:A} | {e3:A} | {f3:A} | {g3:A} | {h3:A} | {i3:A} | {j3:A}; 197type U4 = {a4:A} | {b4:A} | {c4:A} | {d4:A} | {e4:A} | {f4:A} | {g4:A} | {h4:A} | {i4:A} | {j4:A}; 198type U5 = {a5:A} | {b5:A} | {c5:A} | {d5:A} | {e5:A} | {f5:A} | {g5:A} | {h5:A} | {i5:A} | {j5:A}; 199 200type U100000 = U1 & U2 & U3 & U4 & U5; // Error 201 202type Digits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; 203 204type D100000 = `${Digits}${Digits}${Digits}${Digits}${Digits}`; // Error 205 206type TDigits = [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | [9]; 207 208type T100000 = [...TDigits, ...TDigits, ...TDigits, ...TDigits, ...TDigits]; // Error 209 210// Repro from #40863 211 212type IsNegative<T extends number> = `${T}` extends `-${string}` ? true : false; 213 214type AA<T extends number, Q extends number> = 215 [true, true] extends [IsNegative<T>, IsNegative<Q>] ? 'Every thing is ok!' : ['strange', IsNegative<T>, IsNegative<Q>]; 216 217type BB = AA<-2, -2>; 218 219// Repro from #40970 220 221type PathKeys<T> = 222 T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> : 223 T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> : 224 never; 225 226type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never; 227 228declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>; 229 230const obj2 = { 231 name: 'John', 232 age: 42, 233 cars: [ 234 { make: 'Ford', age: 10 }, 235 { make: 'Trabant', age: 35 } 236 ] 237} as const; 238 239let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' 240