|
| 1 | +// @strict: true |
| 2 | +// @declaration: true |
| 3 | + |
| 4 | +// Template types example from #12754 |
| 5 | + |
| 6 | +const createScopedActionType = <S extends string>(scope: S) => <T extends string>(type: T) => `${scope}/${type}` as `${S}/${T}`; |
| 7 | +const createActionInMyScope = createScopedActionType("MyScope"); // <T extends string>(type: T) => `MyScope/${T}` |
| 8 | +const MY_ACTION = createActionInMyScope("MY_ACTION"); // 'MyScope/MY_ACTION' |
| 9 | + |
| 10 | +// Union types are distributed over template types |
| 11 | + |
| 12 | +type EventName<S extends string> = `${S}Changed`; |
| 13 | +type EN1 = EventName<'Foo' | 'Bar' | 'Baz'>; |
| 14 | +type Loc = `${'top' | 'middle' | 'bottom'}-${'left' | 'center' | 'right'}`; |
| 15 | + |
| 16 | +// Primitive literal types can be spread into templates |
| 17 | + |
| 18 | +type ToString<T extends string | number | boolean | bigint> = `${T}`; |
| 19 | +type TS1 = ToString<'abc' | 42 | true | -1234n>; |
| 20 | + |
| 21 | +// Casing modifiers |
| 22 | + |
| 23 | +type Cases<T extends string> = `${uppercase T} ${lowercase T} ${capitalize T} ${uncapitalize T}`; |
| 24 | + |
| 25 | +type TCA1 = Cases<'bar'>; // 'BAR bar Bar bar' |
| 26 | +type TCA2 = Cases<'BAR'>; // 'BAR bar BAR bAR' |
| 27 | + |
| 28 | +// Assignability |
| 29 | + |
| 30 | +function test<T extends 'foo' | 'bar'>(name: `get${capitalize T}`) { |
| 31 | + let s1: string = name; |
| 32 | + let s2: 'getFoo' | 'getBar' = name; |
| 33 | +} |
| 34 | + |
| 35 | +function fa1<T>(x: T, y: { [P in keyof T]: T[P] }, z: { [P in keyof T & string as `p_${P}`]: T[P] }) { |
| 36 | + y = x; |
| 37 | + z = x; // Error |
| 38 | +} |
| 39 | + |
| 40 | +function 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 }) { |
| 41 | + x = y; |
| 42 | + y = x; // Error |
| 43 | +} |
| 44 | + |
| 45 | +// String transformations using recursive conditional types |
| 46 | + |
| 47 | +type Join<T extends (string | number | boolean | bigint)[], D extends string> = |
| 48 | + T extends [] ? '' : |
| 49 | + T extends [unknown] ? `${T[0]}` : |
| 50 | + T extends [unknown, ...infer U] ? `${T[0]}${D}${Join<U, D>}` : |
| 51 | + string; |
| 52 | + |
| 53 | +type TJ1 = Join<[1, 2, 3, 4], '.'> |
| 54 | +type TJ2 = Join<['foo', 'bar', 'baz'], '-'>; |
| 55 | +type TJ3 = Join<[], '.'> |
| 56 | + |
| 57 | +// Inference based on delimiters |
| 58 | + |
| 59 | +type MatchPair<S extends string> = S extends `[${infer A},${infer B}]` ? [A, B] : unknown; |
| 60 | + |
| 61 | +type T20 = MatchPair<'[1,2]'>; // ['1', '2'] |
| 62 | +type T21 = MatchPair<'[foo,bar]'>; // ['foo', 'bar'] |
| 63 | +type T22 = MatchPair<' [1,2]'>; // unknown |
| 64 | +type T23 = MatchPair<'[123]'>; // unknown |
| 65 | +type T24 = MatchPair<'[1,2,3,4]'>; // ['1', '2,3,4'] |
| 66 | + |
| 67 | +type SnakeToCamelCase<S extends string> = |
| 68 | + S extends `${infer T}_${infer U}` ? `${lowercase T}${SnakeToPascalCase<U>}` : |
| 69 | + S extends `${infer T}` ? `${lowercase T}` : |
| 70 | + SnakeToPascalCase<S>; |
| 71 | + |
| 72 | +type SnakeToPascalCase<S extends string> = |
| 73 | + string extends S ? string : |
| 74 | + S extends `${infer T}_${infer U}` ? `${capitalize `${lowercase T}`}${SnakeToPascalCase<U>}` : |
| 75 | + S extends `${infer T}` ? `${capitalize `${lowercase T}`}` : |
| 76 | + never; |
| 77 | + |
| 78 | +type RR0 = SnakeToPascalCase<'hello_world_foo'>; // 'HelloWorldFoo' |
| 79 | +type RR1 = SnakeToPascalCase<'FOO_BAR_BAZ'>; // 'FooBarBaz' |
| 80 | +type RR2 = SnakeToCamelCase<'hello_world_foo'>; // 'helloWorldFoo' |
| 81 | +type RR3 = SnakeToCamelCase<'FOO_BAR_BAZ'>; // 'fooBarBaz' |
| 82 | + |
| 83 | +// Single character inference |
| 84 | + |
| 85 | +type FirstTwoAndRest<S extends string> = S extends `${infer A}${infer B}${infer R}` ? [`${A}${B}`, R] : unknown; |
| 86 | + |
| 87 | +type T25 = FirstTwoAndRest<'abcde'>; // ['ab', 'cde'] |
| 88 | +type T26 = FirstTwoAndRest<'ab'>; // ['ab', ''] |
| 89 | +type T27 = FirstTwoAndRest<'a'>; // unknown |
| 90 | + |
| 91 | +type Capitalize<S extends string> = S extends `${infer H}${infer T}` ? `${uppercase H}${T}` : S; |
| 92 | +type Uncapitalize<S extends string> = S extends `${infer H}${infer T}` ? `${lowercase H}${T}` : S; |
| 93 | + |
| 94 | +type TC1 = Capitalize<'foo'>; // 'Foo' |
| 95 | +type TC2 = Uncapitalize<'Foo'>; // 'foo' |
| 96 | + |
| 97 | +type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' |'8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; |
| 98 | + |
| 99 | +type 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 | + |
| 106 | +type TH1 = HexColor<'#8080FF'>; // '#8080FF' |
| 107 | +type TH2 = HexColor<'#80c0ff'>; // '#80c0ff' |
| 108 | +type TH3 = HexColor<'#8080F'>; // never |
| 109 | +type TH4 = HexColor<'#8080FFF'>; // never |
| 110 | + |
| 111 | +// Recursive inference |
| 112 | + |
| 113 | +type Trim<S extends string> = |
| 114 | + S extends ` ${infer T}` ? Trim<T> : |
| 115 | + S extends `${infer T} ` ? Trim<T> : |
| 116 | + S; |
| 117 | + |
| 118 | +type TR1 = Trim<'xx '>; // 'xx' |
| 119 | +type TR2 = Trim<' xx'>; // 'xx' |
| 120 | +type TR3 = Trim<' xx '>; // 'xx' |
| 121 | + |
| 122 | +type 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 | + |
| 128 | +type T40 = Split<'foo', '.'>; // ['foo'] |
| 129 | +type T41 = Split<'foo.bar.baz', '.'>; // ['foo', 'bar', 'baz'] |
| 130 | +type T42 = Split<'foo.bar', ''>; // ['f', 'o', 'o', '.', 'b', 'a', 'r'] |
| 131 | +type T43 = Split<any, '.'>; // string[] |
| 132 | + |
| 133 | +// Inference and property name paths |
| 134 | + |
| 135 | +declare 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]; |
| 136 | +declare function getProp<T, P0 extends keyof T & string, P1 extends keyof T[P0] & string>(obj: T, path: `${P0}.${P1}`): T[P0][P1]; |
| 137 | +declare function getProp<T, P0 extends keyof T & string>(obj: T, path: P0): T[P0]; |
| 138 | +declare function getProp(obj: object, path: string): unknown; |
| 139 | + |
| 140 | +let p1 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a'); |
| 141 | +let p2 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a.b'); |
| 142 | +let p3 = getProp({ a: { b: {c: 42, d: 'hello' }}} as const, 'a.b.d'); |
| 143 | + |
| 144 | +type 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 | + |
| 150 | +declare function getPropValue<T, P extends string>(obj: T, path: P): PropType<T, P>; |
| 151 | +declare const s: string; |
| 152 | + |
| 153 | +const obj = { a: { b: {c: 42, d: 'hello' }}}; |
| 154 | + |
| 155 | +getPropValue(obj, 'a'); // { b: {c: number, d: string } } |
| 156 | +getPropValue(obj, 'a.b'); // {c: number, d: string } |
| 157 | +getPropValue(obj, 'a.b.d'); // string |
| 158 | +getPropValue(obj, 'a.b.x'); // unknown |
| 159 | +getPropValue(obj, s); // unknown |
| 160 | + |
| 161 | +// Infer type variables in template literals have string constraint |
| 162 | + |
| 163 | +type S1<T> = T extends `foo${infer U}bar` ? S2<U> : never; |
| 164 | +type S2<S extends string> = S; |
| 165 | + |
| 166 | +// Batched single character inferences for lower recursion depth |
| 167 | + |
| 168 | +type Chars<S extends string> = |
| 169 | + string extends S ? string[] : |
| 170 | + 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>] : |
| 171 | + S extends `${infer C}${infer R}` ? [C, ...Chars<R>] : |
| 172 | + S extends '' ? [] : |
| 173 | + never; |
| 174 | + |
| 175 | +type L1 = Chars<'FooBarBazThisIsALongerString'>; // ['F', 'o', 'o', 'B', 'a', 'r', ...] |
| 176 | + |
| 177 | +// Cross product unions limited to 100,000 constituents |
| 178 | + |
| 179 | +type A = any; |
| 180 | + |
| 181 | +type U1 = {a1:A} | {b1:A} | {c1:A} | {d1:A} | {e1:A} | {f1:A} | {g1:A} | {h1:A} | {i1:A} | {j1:A}; |
| 182 | +type U2 = {a2:A} | {b2:A} | {c2:A} | {d2:A} | {e2:A} | {f2:A} | {g2:A} | {h2:A} | {i2:A} | {j2:A}; |
| 183 | +type U3 = {a3:A} | {b3:A} | {c3:A} | {d3:A} | {e3:A} | {f3:A} | {g3:A} | {h3:A} | {i3:A} | {j3:A}; |
| 184 | +type U4 = {a4:A} | {b4:A} | {c4:A} | {d4:A} | {e4:A} | {f4:A} | {g4:A} | {h4:A} | {i4:A} | {j4:A}; |
| 185 | +type U5 = {a5:A} | {b5:A} | {c5:A} | {d5:A} | {e5:A} | {f5:A} | {g5:A} | {h5:A} | {i5:A} | {j5:A}; |
| 186 | + |
| 187 | +type U100000 = U1 & U2 & U3 & U4 & U5; // Error |
| 188 | + |
| 189 | +type Digits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; |
| 190 | + |
| 191 | +type D100000 = `${Digits}${Digits}${Digits}${Digits}${Digits}`; // Error |
| 192 | + |
| 193 | +type TDigits = [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | [9]; |
| 194 | + |
| 195 | +type T100000 = [...TDigits, ...TDigits, ...TDigits, ...TDigits, ...TDigits]; // Error |
0 commit comments