Skip to content

Commit aaf0c89

Browse files
authored
Merge pull request #12563 from Microsoft/mappedTypeModifiers
Preserve modifiers in homomorphic mapped types
2 parents 60c7340 + 65e98c8 commit aaf0c89

File tree

5 files changed

+873
-9
lines changed

5 files changed

+873
-9
lines changed

Diff for: src/compiler/checker.ts

+17-9
Original file line numberDiff line numberDiff line change
@@ -4497,12 +4497,14 @@ namespace ts {
44974497
// Resolve upfront such that recursive references see an empty object type.
44984498
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
44994499
// In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type,
4500-
// and T as the template type.
4500+
// and T as the template type. If K is of the form 'keyof S', the mapped type and S are
4501+
// isomorphic and we copy property modifiers from corresponding properties in S.
45014502
const typeParameter = getTypeParameterFromMappedType(type);
45024503
const constraintType = getConstraintTypeFromMappedType(type);
4504+
const isomorphicType = getIsomorphicTypeFromMappedType(type);
45034505
const templateType = getTemplateTypeFromMappedType(type);
4504-
const isReadonly = !!type.declaration.readonlyToken;
4505-
const isOptional = !!type.declaration.questionToken;
4506+
const templateReadonly = !!type.declaration.readonlyToken;
4507+
const templateOptional = !!type.declaration.questionToken;
45064508
// First, if the constraint type is a type parameter, obtain the base constraint. Then,
45074509
// if the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X.
45084510
// Finally, iterate over the constituents of the resulting iteration type.
@@ -4515,18 +4517,19 @@ namespace ts {
45154517
const iterationMapper = createUnaryTypeMapper(typeParameter, t);
45164518
const templateMapper = type.mapper ? combineTypeMappers(type.mapper, iterationMapper) : iterationMapper;
45174519
const propType = instantiateType(templateType, templateMapper);
4518-
// If the current iteration type constituent is a literal type, create a property.
4519-
// Otherwise, for type string create a string index signature and for type number
4520-
// create a numeric index signature.
4521-
if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) {
4520+
// If the current iteration type constituent is a string literal type, create a property.
4521+
// Otherwise, for type string create a string index signature.
4522+
if (t.flags & TypeFlags.StringLiteral) {
45224523
const propName = (<LiteralType>t).text;
4524+
const isomorphicProp = isomorphicType && getPropertyOfType(isomorphicType, propName);
4525+
const isOptional = templateOptional || !!(isomorphicProp && isomorphicProp.flags & SymbolFlags.Optional);
45234526
const prop = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | (isOptional ? SymbolFlags.Optional : 0), propName);
45244527
prop.type = addOptionality(propType, isOptional);
4525-
prop.isReadonly = isReadonly;
4528+
prop.isReadonly = templateReadonly || isomorphicProp && isReadonlySymbol(isomorphicProp);
45264529
members[propName] = prop;
45274530
}
45284531
else if (t.flags & TypeFlags.String) {
4529-
stringIndexInfo = createIndexInfo(propType, isReadonly);
4532+
stringIndexInfo = createIndexInfo(propType, templateReadonly);
45304533
}
45314534
});
45324535
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
@@ -4549,6 +4552,11 @@ namespace ts {
45494552
unknownType);
45504553
}
45514554

4555+
function getIsomorphicTypeFromMappedType(type: MappedType) {
4556+
const constraint = getConstraintDeclaration(getTypeParameterFromMappedType(type));
4557+
return constraint.kind === SyntaxKind.TypeOperator ? instantiateType(getTypeFromTypeNode((<TypeOperatorNode>constraint).type), type.mapper || identityMapper) : undefined;
4558+
}
4559+
45524560
function getErasedTemplateTypeFromMappedType(type: MappedType) {
45534561
return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType));
45544562
}

Diff for: tests/baselines/reference/mappedTypeModifiers.js

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
//// [mappedTypeModifiers.ts]
2+
3+
type T = { a: number, b: string };
4+
type TU = { a: number | undefined, b: string | undefined };
5+
type TP = { a?: number, b?: string };
6+
type TR = { readonly a: number, readonly b: string };
7+
type TPR = { readonly a?: number, readonly b?: string };
8+
9+
// Validate they all have the same keys
10+
var v00: "a" | "b";
11+
var v00: keyof T;
12+
var v00: keyof TU;
13+
var v00: keyof TP;
14+
var v00: keyof TR;
15+
var v00: keyof TPR;
16+
17+
// Validate that non-isomorphic mapped types strip modifiers
18+
var v01: T;
19+
var v01: Pick<TR, keyof T>;
20+
var v01: Pick<Readonly<T>, keyof T>;
21+
22+
// Validate that non-isomorphic mapped types strip modifiers
23+
var v02: TU;
24+
var v02: Pick<TP, keyof T>;
25+
var v02: Pick<TPR, keyof T>;
26+
var v02: Pick<Partial<T>, keyof T>;
27+
var v02: Pick<Partial<Readonly<T>>, keyof T>;
28+
29+
// Validate that isomorphic mapped types preserve optional modifier
30+
var v03: TP;
31+
var v03: Partial<T>;
32+
33+
// Validate that isomorphic mapped types preserve readonly modifier
34+
var v04: TR;
35+
var v04: Readonly<T>;
36+
37+
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
38+
var v05: TPR;
39+
var v05: Partial<TR>;
40+
var v05: Readonly<TP>;
41+
var v05: Partial<Readonly<T>>;
42+
var v05: Readonly<Partial<T>>;
43+
44+
type Boxified<T> = { [P in keyof T]: { x: T[P] } };
45+
46+
type B = { a: { x: number }, b: { x: string } };
47+
type BU = { a: { x: number } | undefined, b: { x: string } | undefined };
48+
type BP = { a?: { x: number }, b?: { x: string } };
49+
type BR = { readonly a: { x: number }, readonly b: { x: string } };
50+
type BPR = { readonly a?: { x: number }, readonly b?: { x: string } };
51+
52+
// Validate they all have the same keys
53+
var b00: "a" | "b";
54+
var b00: keyof B;
55+
var b00: keyof BU;
56+
var b00: keyof BP;
57+
var b00: keyof BR;
58+
var b00: keyof BPR;
59+
60+
// Validate that non-isomorphic mapped types strip modifiers
61+
var b01: B;
62+
var b01: Pick<BR, keyof B>;
63+
var b01: Pick<Readonly<BR>, keyof B>;
64+
65+
// Validate that non-isomorphic mapped types strip modifiers
66+
var b02: BU;
67+
var b02: Pick<BP, keyof B>;
68+
var b02: Pick<BPR, keyof B>;
69+
var b02: Pick<Partial<B>, keyof B>;
70+
var b02: Pick<Partial<Readonly<B>>, keyof B>;
71+
72+
// Validate that isomorphic mapped types preserve optional modifier
73+
var b03: BP;
74+
var b03: Partial<B>;
75+
76+
// Validate that isomorphic mapped types preserve readonly modifier
77+
var b04: BR;
78+
var b04: Readonly<B>;
79+
80+
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
81+
var b05: BPR;
82+
var b05: Partial<BR>;
83+
var b05: Readonly<BP>;
84+
var b05: Partial<Readonly<B>>;
85+
var b05: Readonly<Partial<B>>;
86+
87+
//// [mappedTypeModifiers.js]
88+
// Validate they all have the same keys
89+
var v00;
90+
var v00;
91+
var v00;
92+
var v00;
93+
var v00;
94+
var v00;
95+
// Validate that non-isomorphic mapped types strip modifiers
96+
var v01;
97+
var v01;
98+
var v01;
99+
// Validate that non-isomorphic mapped types strip modifiers
100+
var v02;
101+
var v02;
102+
var v02;
103+
var v02;
104+
var v02;
105+
// Validate that isomorphic mapped types preserve optional modifier
106+
var v03;
107+
var v03;
108+
// Validate that isomorphic mapped types preserve readonly modifier
109+
var v04;
110+
var v04;
111+
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
112+
var v05;
113+
var v05;
114+
var v05;
115+
var v05;
116+
var v05;
117+
// Validate they all have the same keys
118+
var b00;
119+
var b00;
120+
var b00;
121+
var b00;
122+
var b00;
123+
var b00;
124+
// Validate that non-isomorphic mapped types strip modifiers
125+
var b01;
126+
var b01;
127+
var b01;
128+
// Validate that non-isomorphic mapped types strip modifiers
129+
var b02;
130+
var b02;
131+
var b02;
132+
var b02;
133+
var b02;
134+
// Validate that isomorphic mapped types preserve optional modifier
135+
var b03;
136+
var b03;
137+
// Validate that isomorphic mapped types preserve readonly modifier
138+
var b04;
139+
var b04;
140+
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
141+
var b05;
142+
var b05;
143+
var b05;
144+
var b05;
145+
var b05;

0 commit comments

Comments
 (0)