Skip to content

Commit dd2f4d6

Browse files
committed
Adds Subset type for true subsets of interfaces
The subset type will allow creating true subsets of models to model things like partial DTOs and the like. Resolves microsoft#12936 (and microsoft#28586 to some extent)
1 parent 1e6a6e9 commit dd2f4d6

File tree

6 files changed

+574
-0
lines changed

6 files changed

+574
-0
lines changed

lib/lib.es5.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,11 @@ type Required<T> = {
14321432
[P in keyof T]-?: T[P];
14331433
};
14341434

1435+
/**
1436+
* Construct a true subset of T as defined by U
1437+
*/
1438+
type Subset<T extends U, U> = U;
1439+
14351440
/**
14361441
* Make all properties in T readonly
14371442
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
tests/cases/compiler/subsetTests.ts(16,28): error TS2304: Cannot find name 'Subset'.
2+
tests/cases/compiler/subsetTests.ts(27,40): error TS2304: Cannot find name 'Subset'.
3+
tests/cases/compiler/subsetTests.ts(32,34): error TS2304: Cannot find name 'Subset'.
4+
tests/cases/compiler/subsetTests.ts(39,28): error TS2304: Cannot find name 'Subset'.
5+
tests/cases/compiler/subsetTests.ts(49,40): error TS2304: Cannot find name 'Subset'.
6+
tests/cases/compiler/subsetTests.ts(62,34): error TS2304: Cannot find name 'Subset'.
7+
8+
9+
==== tests/cases/compiler/subsetTests.ts (6 errors) ====
10+
interface User {
11+
name: string;
12+
age: number;
13+
contact: {
14+
email: string;
15+
phone: string;
16+
address: {
17+
street: string;
18+
country: string;
19+
zipcode: number;
20+
}
21+
};
22+
password: string;
23+
}
24+
25+
type PersonalInformation = Subset<User, {
26+
~~~~~~
27+
!!! error TS2304: Cannot find name 'Subset'.
28+
name: string;
29+
age: number;
30+
}>; // Fine
31+
32+
const test: PersonalInformation = {
33+
name: 'Hans',
34+
age: 21,
35+
password: 'string' // Error: password does not exist in type
36+
};
37+
38+
type WronglyTypedPersonalInformation = Subset<User, {
39+
~~~~~~
40+
!!! error TS2304: Cannot find name 'Subset'.
41+
name: string;
42+
age: string; // Error: Types of property age are incompatible
43+
}>;
44+
45+
type ExcessPersonalInformation = Subset<User, {
46+
~~~~~~
47+
!!! error TS2304: Cannot find name 'Subset'.
48+
name: string;
49+
favoriteColor: string; // Error: Property favoriteColor is missing in type User
50+
}>;
51+
52+
// This also works for "deep" properties
53+
54+
type ShippingInformation = Subset<User, {
55+
~~~~~~
56+
!!! error TS2304: Cannot find name 'Subset'.
57+
name: string;
58+
contact: {
59+
address: {
60+
street: string;
61+
zipcode: number;
62+
}
63+
}
64+
}>; // Fine (Omitting properties of nested properties is ok too)
65+
66+
type WronglyTypedShippingInformation = Subset<User, {
67+
~~~~~~
68+
!!! error TS2304: Cannot find name 'Subset'.
69+
name: string;
70+
contact: {
71+
address: {
72+
street: {
73+
name: string;
74+
nr: number;
75+
}; // Error: Types of property street are incompatible
76+
zipcode: number;
77+
}
78+
}
79+
}>;
80+
81+
type ExcessShippingInformation = Subset<User, {
82+
~~~~~~
83+
!!! error TS2304: Cannot find name 'Subset'.
84+
name: string;
85+
contact: {
86+
address: {
87+
street: string;
88+
zipcode: number;
89+
state: string; // Error: Property state is missing in type User
90+
}
91+
}
92+
}>;
93+
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//// [subsetTests.ts]
2+
interface User {
3+
name: string;
4+
age: number;
5+
contact: {
6+
email: string;
7+
phone: string;
8+
address: {
9+
street: string;
10+
country: string;
11+
zipcode: number;
12+
}
13+
};
14+
password: string;
15+
}
16+
17+
type PersonalInformation = Subset<User, {
18+
name: string;
19+
age: number;
20+
}>; // Fine
21+
22+
const test: PersonalInformation = {
23+
name: 'Hans',
24+
age: 21,
25+
password: 'string' // Error: password does not exist in type
26+
};
27+
28+
type WronglyTypedPersonalInformation = Subset<User, {
29+
name: string;
30+
age: string; // Error: Types of property age are incompatible
31+
}>;
32+
33+
type ExcessPersonalInformation = Subset<User, {
34+
name: string;
35+
favoriteColor: string; // Error: Property favoriteColor is missing in type User
36+
}>;
37+
38+
// This also works for "deep" properties
39+
40+
type ShippingInformation = Subset<User, {
41+
name: string;
42+
contact: {
43+
address: {
44+
street: string;
45+
zipcode: number;
46+
}
47+
}
48+
}>; // Fine (Omitting properties of nested properties is ok too)
49+
50+
type WronglyTypedShippingInformation = Subset<User, {
51+
name: string;
52+
contact: {
53+
address: {
54+
street: {
55+
name: string;
56+
nr: number;
57+
}; // Error: Types of property street are incompatible
58+
zipcode: number;
59+
}
60+
}
61+
}>;
62+
63+
type ExcessShippingInformation = Subset<User, {
64+
name: string;
65+
contact: {
66+
address: {
67+
street: string;
68+
zipcode: number;
69+
state: string; // Error: Property state is missing in type User
70+
}
71+
}
72+
}>;
73+
74+
75+
//// [subsetTests.js]
76+
var test = {
77+
name: 'Hans',
78+
age: 21,
79+
password: 'string' // Error: password does not exist in type
80+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
=== tests/cases/compiler/subsetTests.ts ===
2+
interface User {
3+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
4+
5+
name: string;
6+
>name : Symbol(User.name, Decl(subsetTests.ts, 0, 16))
7+
8+
age: number;
9+
>age : Symbol(User.age, Decl(subsetTests.ts, 1, 17))
10+
11+
contact: {
12+
>contact : Symbol(User.contact, Decl(subsetTests.ts, 2, 16))
13+
14+
email: string;
15+
>email : Symbol(email, Decl(subsetTests.ts, 3, 14))
16+
17+
phone: string;
18+
>phone : Symbol(phone, Decl(subsetTests.ts, 4, 22))
19+
20+
address: {
21+
>address : Symbol(address, Decl(subsetTests.ts, 5, 22))
22+
23+
street: string;
24+
>street : Symbol(street, Decl(subsetTests.ts, 6, 18))
25+
26+
country: string;
27+
>country : Symbol(country, Decl(subsetTests.ts, 7, 27))
28+
29+
zipcode: number;
30+
>zipcode : Symbol(zipcode, Decl(subsetTests.ts, 8, 28))
31+
}
32+
};
33+
password: string;
34+
>password : Symbol(User.password, Decl(subsetTests.ts, 11, 6))
35+
}
36+
37+
type PersonalInformation = Subset<User, {
38+
>PersonalInformation : Symbol(PersonalInformation, Decl(subsetTests.ts, 13, 1))
39+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
40+
41+
name: string;
42+
>name : Symbol(name, Decl(subsetTests.ts, 15, 41))
43+
44+
age: number;
45+
>age : Symbol(age, Decl(subsetTests.ts, 16, 17))
46+
47+
}>; // Fine
48+
49+
const test: PersonalInformation = {
50+
>test : Symbol(test, Decl(subsetTests.ts, 20, 5))
51+
>PersonalInformation : Symbol(PersonalInformation, Decl(subsetTests.ts, 13, 1))
52+
53+
name: 'Hans',
54+
>name : Symbol(name, Decl(subsetTests.ts, 20, 35))
55+
56+
age: 21,
57+
>age : Symbol(age, Decl(subsetTests.ts, 21, 17))
58+
59+
password: 'string' // Error: password does not exist in type
60+
>password : Symbol(password, Decl(subsetTests.ts, 22, 12))
61+
62+
};
63+
64+
type WronglyTypedPersonalInformation = Subset<User, {
65+
>WronglyTypedPersonalInformation : Symbol(WronglyTypedPersonalInformation, Decl(subsetTests.ts, 24, 2))
66+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
67+
68+
name: string;
69+
>name : Symbol(name, Decl(subsetTests.ts, 26, 53))
70+
71+
age: string; // Error: Types of property age are incompatible
72+
>age : Symbol(age, Decl(subsetTests.ts, 27, 17))
73+
74+
}>;
75+
76+
type ExcessPersonalInformation = Subset<User, {
77+
>ExcessPersonalInformation : Symbol(ExcessPersonalInformation, Decl(subsetTests.ts, 29, 3))
78+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
79+
80+
name: string;
81+
>name : Symbol(name, Decl(subsetTests.ts, 31, 47))
82+
83+
favoriteColor: string; // Error: Property favoriteColor is missing in type User
84+
>favoriteColor : Symbol(favoriteColor, Decl(subsetTests.ts, 32, 17))
85+
86+
}>;
87+
88+
// This also works for "deep" properties
89+
90+
type ShippingInformation = Subset<User, {
91+
>ShippingInformation : Symbol(ShippingInformation, Decl(subsetTests.ts, 34, 3))
92+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
93+
94+
name: string;
95+
>name : Symbol(name, Decl(subsetTests.ts, 38, 41))
96+
97+
contact: {
98+
>contact : Symbol(contact, Decl(subsetTests.ts, 39, 17))
99+
100+
address: {
101+
>address : Symbol(address, Decl(subsetTests.ts, 40, 14))
102+
103+
street: string;
104+
>street : Symbol(street, Decl(subsetTests.ts, 41, 18))
105+
106+
zipcode: number;
107+
>zipcode : Symbol(zipcode, Decl(subsetTests.ts, 42, 27))
108+
}
109+
}
110+
}>; // Fine (Omitting properties of nested properties is ok too)
111+
112+
type WronglyTypedShippingInformation = Subset<User, {
113+
>WronglyTypedShippingInformation : Symbol(WronglyTypedShippingInformation, Decl(subsetTests.ts, 46, 3))
114+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
115+
116+
name: string;
117+
>name : Symbol(name, Decl(subsetTests.ts, 48, 53))
118+
119+
contact: {
120+
>contact : Symbol(contact, Decl(subsetTests.ts, 49, 17))
121+
122+
address: {
123+
>address : Symbol(address, Decl(subsetTests.ts, 50, 14))
124+
125+
street: {
126+
>street : Symbol(street, Decl(subsetTests.ts, 51, 18))
127+
128+
name: string;
129+
>name : Symbol(name, Decl(subsetTests.ts, 52, 21))
130+
131+
nr: number;
132+
>nr : Symbol(nr, Decl(subsetTests.ts, 53, 29))
133+
134+
}; // Error: Types of property street are incompatible
135+
zipcode: number;
136+
>zipcode : Symbol(zipcode, Decl(subsetTests.ts, 55, 14))
137+
}
138+
}
139+
}>;
140+
141+
type ExcessShippingInformation = Subset<User, {
142+
>ExcessShippingInformation : Symbol(ExcessShippingInformation, Decl(subsetTests.ts, 59, 3))
143+
>User : Symbol(User, Decl(subsetTests.ts, 0, 0))
144+
145+
name: string;
146+
>name : Symbol(name, Decl(subsetTests.ts, 61, 47))
147+
148+
contact: {
149+
>contact : Symbol(contact, Decl(subsetTests.ts, 62, 17))
150+
151+
address: {
152+
>address : Symbol(address, Decl(subsetTests.ts, 63, 14))
153+
154+
street: string;
155+
>street : Symbol(street, Decl(subsetTests.ts, 64, 18))
156+
157+
zipcode: number;
158+
>zipcode : Symbol(zipcode, Decl(subsetTests.ts, 65, 27))
159+
160+
state: string; // Error: Property state is missing in type User
161+
>state : Symbol(state, Decl(subsetTests.ts, 66, 28))
162+
}
163+
}
164+
}>;
165+

0 commit comments

Comments
 (0)