Skip to content

Commit 096d0ae

Browse files
committed
Add tests
1 parent 2d70ca2 commit 096d0ae

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
// Mapped type 'as N' clauses
5+
6+
type Getters<T> = { [P in keyof T & string as `get${capitalize P}`]: () => T[P] };
7+
type TG1 = Getters<{ foo: string, bar: number, baz: { z: boolean } }>;
8+
9+
// Mapped type with 'as N' clause has no constraint on 'in T' clause
10+
11+
type PropDef<K extends keyof any, T> = { name: K, type: T };
12+
13+
type TypeFromDefs<T extends PropDef<keyof any, any>> = { [P in T as P['name']]: P['type'] };
14+
15+
type TP1 = TypeFromDefs<{ name: 'a', type: string } | { name: 'b', type: number } | { name: 'a', type: boolean }>;
16+
17+
// No array or tuple type mapping when 'as N' clause present
18+
19+
type TA1 = Getters<string[]>;
20+
type TA2 = Getters<[number, boolean]>;
21+
22+
// Filtering using 'as N' clause
23+
24+
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
25+
type TM1 = Methods<{ foo(): number, bar(x: string): boolean, baz: string | number }>;
26+
27+
// Mapping to multiple names using 'as N' clause
28+
29+
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
30+
type TD1 = DoubleProp<{ a: string, b: number }>; // { a1: string, a2: string, b1: number, b2: number }
31+
type TD2 = keyof TD1; // 'a1' | 'a2' | 'b1' | 'b2'
32+
type TD3<U> = keyof DoubleProp<U>; // `${keyof U & string}1` | `${keyof U & string}2`

0 commit comments

Comments
 (0)