Skip to content

Commit 0ba2348

Browse files
authored
Merge pull request #12276 from Microsoft/libMappedTypes
Predefined mapped types in lib.d.ts
2 parents 4c24744 + 997c586 commit 0ba2348

File tree

9 files changed

+446
-351
lines changed

9 files changed

+446
-351
lines changed

src/compiler/checker.ts

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4550,6 +4550,10 @@ namespace ts {
45504550
unknownType);
45514551
}
45524552

4553+
function getErasedTemplateTypeFromMappedType(type: MappedType) {
4554+
return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType));
4555+
}
4556+
45534557
function isGenericMappedType(type: Type) {
45544558
if (getObjectFlags(type) & ObjectFlags.Mapped) {
45554559
const constraintType = getConstraintTypeFromMappedType(<MappedType>type);
@@ -7190,29 +7194,18 @@ namespace ts {
71907194
return result;
71917195
}
71927196
}
7193-
if (isGenericMappedType(target)) {
7194-
// A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
7195-
if (isGenericMappedType(source)) {
7196-
if ((result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) &&
7197-
(result = isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors))) {
7198-
return result;
7199-
}
7200-
}
7201-
}
7202-
else {
7203-
// Even if relationship doesn't hold for unions, intersections, or generic type references,
7204-
// it may hold in a structural comparison.
7205-
const apparentSource = getApparentType(source);
7206-
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
7207-
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
7208-
// relates to X. Thus, we include intersection types on the source side here.
7209-
if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
7210-
// Report structural errors only if we haven't reported any errors yet
7211-
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
7212-
if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
7213-
errorInfo = saveErrorInfo;
7214-
return result;
7215-
}
7197+
// Even if relationship doesn't hold for unions, intersections, or generic type references,
7198+
// it may hold in a structural comparison.
7199+
const apparentSource = getApparentType(source);
7200+
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
7201+
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
7202+
// relates to X. Thus, we include intersection types on the source side here.
7203+
if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
7204+
// Report structural errors only if we haven't reported any errors yet
7205+
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
7206+
if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
7207+
errorInfo = saveErrorInfo;
7208+
return result;
72167209
}
72177210
}
72187211
}
@@ -7441,6 +7434,9 @@ namespace ts {
74417434
if (expandingFlags === 3) {
74427435
result = Ternary.Maybe;
74437436
}
7437+
else if (isGenericMappedType(source) || isGenericMappedType(target)) {
7438+
result = mappedTypeRelatedTo(source, target, reportErrors);
7439+
}
74447440
else {
74457441
result = propertiesRelatedTo(source, target, reportErrors);
74467442
if (result) {
@@ -7472,6 +7468,30 @@ namespace ts {
74727468
return result;
74737469
}
74747470

7471+
// A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
7472+
function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
7473+
if (isGenericMappedType(source) && isGenericMappedType(target)) {
7474+
let result: Ternary;
7475+
if (relation === identityRelation) {
7476+
const readonlyMatches = !(<MappedType>source).declaration.readonlyToken === !(<MappedType>target).declaration.readonlyToken;
7477+
const optionalMatches = !(<MappedType>source).declaration.questionToken === !(<MappedType>target).declaration.questionToken;
7478+
if (readonlyMatches && optionalMatches) {
7479+
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7480+
return result & isRelatedTo(getErasedTemplateTypeFromMappedType(<MappedType>source), getErasedTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7481+
}
7482+
}
7483+
}
7484+
else {
7485+
if (relation === comparableRelation || !(<MappedType>source).declaration.questionToken || (<MappedType>target).declaration.questionToken) {
7486+
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7487+
return result & isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7488+
}
7489+
}
7490+
}
7491+
}
7492+
return Ternary.False;
7493+
}
7494+
74757495
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
74767496
if (relation === identityRelation) {
74777497
return propertiesIdenticalTo(source, target);

src/lib/es5.d.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ interface ObjectConstructor {
180180
* Prevents the modification of existing property attributes and values, and prevents the addition of new properties.
181181
* @param o Object on which to lock the attributes.
182182
*/
183-
freeze<T>(o: T): T;
183+
freeze<T>(o: T): Readonly<T>;
184184

185185
/**
186186
* Prevents the addition of new properties to an object.
@@ -1343,6 +1343,34 @@ interface ArrayLike<T> {
13431343
readonly [n: number]: T;
13441344
}
13451345

1346+
/**
1347+
* Make all properties in T optional
1348+
*/
1349+
type Partial<T> = {
1350+
[P in keyof T]?: T[P];
1351+
};
1352+
1353+
/**
1354+
* Make all properties in T readonly
1355+
*/
1356+
type Readonly<T> = {
1357+
readonly [P in keyof T]: T[P];
1358+
};
1359+
1360+
/**
1361+
* From T pick a set of properties K
1362+
*/
1363+
type Pick<T, K extends keyof T> = {
1364+
[P in K]: T[P];
1365+
}
1366+
1367+
/**
1368+
* Construct a type with a set of properties K of type T
1369+
*/
1370+
type Record<K extends string | number, T> = {
1371+
[P in K]: T;
1372+
}
1373+
13461374
/**
13471375
* Represents a raw buffer of binary data, which is used to store data for the
13481376
* different typed arrays. ArrayBuffers cannot be read from or written to directly,

tests/baselines/reference/mappedTypeErrors.errors.txt

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,29 @@
1-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(34,20): error TS2313: Type parameter 'P' has a circular constraint.
2-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(35,20): error TS2322: Type 'Date' is not assignable to type 'string | number'.
1+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(20,20): error TS2313: Type parameter 'P' has a circular constraint.
2+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(21,20): error TS2322: Type 'Date' is not assignable to type 'string | number'.
33
Type 'Date' is not assignable to type 'number'.
4-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(36,19): error TS2344: Type 'Date' does not satisfy the constraint 'string | number'.
4+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(22,19): error TS2344: Type 'Date' does not satisfy the constraint 'string | number'.
55
Type 'Date' is not assignable to type 'number'.
6-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(39,24): error TS2344: Type '"foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
7-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(40,24): error TS2344: Type '"name" | "foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
6+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(25,24): error TS2344: Type '"foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
7+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(26,24): error TS2344: Type '"name" | "foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
88
Type '"foo"' is not assignable to type '"name" | "width" | "height" | "visible"'.
9-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(42,24): error TS2344: Type '"x" | "y"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
9+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(28,24): error TS2344: Type '"x" | "y"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
1010
Type '"x"' is not assignable to type '"name" | "width" | "height" | "visible"'.
11-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(44,24): error TS2344: Type 'undefined' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
12-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(47,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
11+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(30,24): error TS2344: Type 'undefined' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
12+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(33,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
1313
Type 'T' is not assignable to type '"visible"'.
14-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
14+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(37,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
1515
Type 'string | number' is not assignable to type '"name" | "width" | "height" | "visible"'.
1616
Type 'string' is not assignable to type '"name" | "width" | "height" | "visible"'.
1717
Type 'T' is not assignable to type '"visible"'.
1818
Type 'string | number' is not assignable to type '"visible"'.
1919
Type 'string' is not assignable to type '"visible"'.
20+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(59,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]?: T[P]; }'.
21+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(60,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]: T[P]; }'.
22+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(61,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]?: T[P]; }'.
23+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(66,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.
2024

2125

22-
==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (9 errors) ====
23-
24-
type Partial<T> = {
25-
[P in keyof T]?: T[P];
26-
};
27-
28-
type Readonly<T> = {
29-
readonly [P in keyof T]: T[P];
30-
};
31-
32-
type Pick<T, K extends keyof T> = {
33-
[P in K]: T[P];
34-
}
35-
36-
type Record<K extends string | number, T> = {
37-
[_ in K]: T;
38-
}
26+
==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (13 errors) ====
3927

4028
interface Shape {
4129
name: string;
@@ -53,6 +41,8 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: T
5341
y: number;
5442
}
5543

44+
// Constraint checking
45+
5646
type T00 = { [P in P]: string }; // Error
5747
~
5848
!!! error TS2313: Type parameter 'P' has a circular constraint.
@@ -107,4 +97,33 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: T
10797

10898
function f4<T extends keyof Named>(x: T) {
10999
let y: Pick<Shape, T>;
100+
}
101+
102+
// Type identity checking
103+
104+
function f10<T>() {
105+
type K = keyof T;
106+
var x: { [P in keyof T]: T[P] };
107+
var x: { [Q in keyof T]: T[Q] };
108+
var x: { [R in K]: T[R] };
109+
}
110+
111+
function f11<T>() {
112+
var x: { [P in keyof T]: T[P] };
113+
var x: { [P in keyof T]?: T[P] }; // Error
114+
~
115+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]?: T[P]; }'.
116+
var x: { readonly [P in keyof T]: T[P] }; // Error
117+
~
118+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]: T[P]; }'.
119+
var x: { readonly [P in keyof T]?: T[P] }; // Error
120+
~
121+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]?: T[P]; }'.
122+
}
123+
124+
function f12<T>() {
125+
var x: { [P in keyof T]: T[P] };
126+
var x: { [P in keyof T]: T[P][] }; // Error
127+
~
128+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.
110129
}

tests/baselines/reference/mappedTypeErrors.js

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
11
//// [mappedTypeErrors.ts]
22

3-
type Partial<T> = {
4-
[P in keyof T]?: T[P];
5-
};
6-
7-
type Readonly<T> = {
8-
readonly [P in keyof T]: T[P];
9-
};
10-
11-
type Pick<T, K extends keyof T> = {
12-
[P in K]: T[P];
13-
}
14-
15-
type Record<K extends string | number, T> = {
16-
[_ in K]: T;
17-
}
18-
193
interface Shape {
204
name: string;
215
width: number;
@@ -32,6 +16,8 @@ interface Point {
3216
y: number;
3317
}
3418

19+
// Constraint checking
20+
3521
type T00 = { [P in P]: string }; // Error
3622
type T01 = { [P in Date]: number }; // Error
3723
type T02 = Record<Date, number>; // Error
@@ -58,6 +44,27 @@ function f3<T extends keyof Shape>(x: T) {
5844

5945
function f4<T extends keyof Named>(x: T) {
6046
let y: Pick<Shape, T>;
47+
}
48+
49+
// Type identity checking
50+
51+
function f10<T>() {
52+
type K = keyof T;
53+
var x: { [P in keyof T]: T[P] };
54+
var x: { [Q in keyof T]: T[Q] };
55+
var x: { [R in K]: T[R] };
56+
}
57+
58+
function f11<T>() {
59+
var x: { [P in keyof T]: T[P] };
60+
var x: { [P in keyof T]?: T[P] }; // Error
61+
var x: { readonly [P in keyof T]: T[P] }; // Error
62+
var x: { readonly [P in keyof T]?: T[P] }; // Error
63+
}
64+
65+
function f12<T>() {
66+
var x: { [P in keyof T]: T[P] };
67+
var x: { [P in keyof T]: T[P][] }; // Error
6168
}
6269

6370
//// [mappedTypeErrors.js]
@@ -73,21 +80,25 @@ function f3(x) {
7380
function f4(x) {
7481
var y;
7582
}
83+
// Type identity checking
84+
function f10() {
85+
var x;
86+
var x;
87+
var x;
88+
}
89+
function f11() {
90+
var x;
91+
var x; // Error
92+
var x; // Error
93+
var x; // Error
94+
}
95+
function f12() {
96+
var x;
97+
var x; // Error
98+
}
7699

77100

78101
//// [mappedTypeErrors.d.ts]
79-
declare type Partial<T> = {
80-
[P in keyof T]?: T[P];
81-
};
82-
declare type Readonly<T> = {
83-
readonly [P in keyof T]: T[P];
84-
};
85-
declare type Pick<T, K extends keyof T> = {
86-
[P in K]: T[P];
87-
};
88-
declare type Record<K extends string | number, T> = {
89-
[_ in K]: T;
90-
};
91102
interface Shape {
92103
name: string;
93104
width: number;
@@ -119,3 +130,6 @@ declare function f1<T>(x: T): void;
119130
declare function f2<T extends string | number>(x: T): void;
120131
declare function f3<T extends keyof Shape>(x: T): void;
121132
declare function f4<T extends keyof Named>(x: T): void;
133+
declare function f10<T>(): void;
134+
declare function f11<T>(): void;
135+
declare function f12<T>(): void;

0 commit comments

Comments
 (0)