Skip to content

Commit 030da0b

Browse files
authored
Merge pull request #12589 from Microsoft/mappedTypeModifierInference
Mapped type modifier inference
2 parents 4c9bdb9 + 2517187 commit 030da0b

File tree

6 files changed

+187
-10
lines changed

6 files changed

+187
-10
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4532,7 +4532,7 @@ namespace ts {
45324532
const isomorphicProp = isomorphicType && getPropertyOfType(isomorphicType, propName);
45334533
const isOptional = templateOptional || !!(isomorphicProp && isomorphicProp.flags & SymbolFlags.Optional);
45344534
const prop = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | (isOptional ? SymbolFlags.Optional : 0), propName);
4535-
prop.type = addOptionality(propType, isOptional);
4535+
prop.type = propType;
45364536
prop.isReadonly = templateReadonly || isomorphicProp && isReadonlySymbol(isomorphicProp);
45374537
members[propName] = prop;
45384538
}
@@ -4556,7 +4556,7 @@ namespace ts {
45564556
function getTemplateTypeFromMappedType(type: MappedType) {
45574557
return type.templateType ||
45584558
(type.templateType = type.declaration.type ?
4559-
instantiateType(getTypeFromTypeNode(type.declaration.type), type.mapper || identityMapper) :
4559+
instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!type.declaration.questionToken), type.mapper || identityMapper) :
45604560
unknownType);
45614561
}
45624562

@@ -6021,7 +6021,7 @@ namespace ts {
60216021
}
60226022
const mapper = createUnaryTypeMapper(getTypeParameterFromMappedType(type), indexType);
60236023
const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
6024-
return addOptionality(instantiateType(getTemplateTypeFromMappedType(type), templateMapper), !!type.declaration.questionToken);
6024+
return instantiateType(getTemplateTypeFromMappedType(type), templateMapper);
60256025
}
60266026

60276027
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
@@ -8484,16 +8484,18 @@ namespace ts {
84848484
const typeInferences = createTypeInferencesObject();
84858485
const typeInferencesArray = [typeInferences];
84868486
const templateType = getTemplateTypeFromMappedType(target);
8487+
const readonlyMask = target.declaration.readonlyToken ? false : true;
8488+
const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional;
84878489
const properties = getPropertiesOfType(source);
84888490
const members = createSymbolTable(properties);
84898491
let hasInferredTypes = false;
84908492
for (const prop of properties) {
84918493
const inferredPropType = inferTargetType(getTypeOfSymbol(prop));
84928494
if (inferredPropType) {
8493-
const inferredProp = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & SymbolFlags.Optional, prop.name);
8495+
const inferredProp = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & optionalMask, prop.name);
84948496
inferredProp.declarations = prop.declarations;
84958497
inferredProp.type = inferredPropType;
8496-
inferredProp.isReadonly = isReadonlySymbol(prop);
8498+
inferredProp.isReadonly = readonlyMask && isReadonlySymbol(prop);
84978499
members[prop.name] = inferredProp;
84988500
hasInferredTypes = true;
84998501
}
@@ -8502,7 +8504,7 @@ namespace ts {
85028504
if (indexInfo) {
85038505
const inferredIndexType = inferTargetType(indexInfo.type);
85048506
if (inferredIndexType) {
8505-
indexInfo = createIndexInfo(inferredIndexType, indexInfo.isReadonly);
8507+
indexInfo = createIndexInfo(inferredIndexType, readonlyMask && indexInfo.isReadonly);
85068508
hasInferredTypes = true;
85078509
}
85088510
}

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ function f6(s: string) {
103103
});
104104
let v = unboxify(b);
105105
let x: string | number | boolean = v[s];
106+
}
107+
108+
declare function validate<T>(obj: { [P in keyof T]?: T[P] }): T;
109+
declare function clone<T>(obj: { readonly [P in keyof T]: T[P] }): T;
110+
declare function validateAndClone<T>(obj: { readonly [P in keyof T]?: T[P] }): T;
111+
112+
type Foo = {
113+
a?: number;
114+
readonly b: string;
115+
}
116+
117+
function f10(foo: Foo) {
118+
let x = validate(foo); // { a: number, readonly b: string }
119+
let y = clone(foo); // { a?: number, b: string }
120+
let z = validateAndClone(foo); // { a: number, b: string }
106121
}
107122

108123
//// [isomorphicMappedTypeInference.js]
@@ -190,6 +205,11 @@ function f6(s) {
190205
var v = unboxify(b);
191206
var x = v[s];
192207
}
208+
function f10(foo) {
209+
var x = validate(foo); // { a: number, readonly b: string }
210+
var y = clone(foo); // { a?: number, b: string }
211+
var z = validateAndClone(foo); // { a: number, b: string }
212+
}
193213

194214

195215
//// [isomorphicMappedTypeInference.d.ts]
@@ -220,3 +240,17 @@ declare function makeDictionary<T>(obj: {
220240
[x: string]: T;
221241
};
222242
declare function f6(s: string): void;
243+
declare function validate<T>(obj: {
244+
[P in keyof T]?: T[P];
245+
}): T;
246+
declare function clone<T>(obj: {
247+
readonly [P in keyof T]: T[P];
248+
}): T;
249+
declare function validateAndClone<T>(obj: {
250+
readonly [P in keyof T]?: T[P];
251+
}): T;
252+
declare type Foo = {
253+
a?: number;
254+
readonly b: string;
255+
};
256+
declare function f10(foo: Foo): void;

tests/baselines/reference/isomorphicMappedTypeInference.symbols

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,64 @@ function f6(s: string) {
332332
>v : Symbol(v, Decl(isomorphicMappedTypeInference.ts, 102, 7))
333333
>s : Symbol(s, Decl(isomorphicMappedTypeInference.ts, 96, 12))
334334
}
335+
336+
declare function validate<T>(obj: { [P in keyof T]?: T[P] }): T;
337+
>validate : Symbol(validate, Decl(isomorphicMappedTypeInference.ts, 104, 1))
338+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26))
339+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 106, 29))
340+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 106, 37))
341+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26))
342+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26))
343+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 106, 37))
344+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26))
345+
346+
declare function clone<T>(obj: { readonly [P in keyof T]: T[P] }): T;
347+
>clone : Symbol(clone, Decl(isomorphicMappedTypeInference.ts, 106, 64))
348+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23))
349+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 107, 26))
350+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 107, 43))
351+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23))
352+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23))
353+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 107, 43))
354+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23))
355+
356+
declare function validateAndClone<T>(obj: { readonly [P in keyof T]?: T[P] }): T;
357+
>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69))
358+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34))
359+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 108, 37))
360+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 108, 54))
361+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34))
362+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34))
363+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 108, 54))
364+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34))
365+
366+
type Foo = {
367+
>Foo : Symbol(Foo, Decl(isomorphicMappedTypeInference.ts, 108, 81))
368+
369+
a?: number;
370+
>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 110, 12))
371+
372+
readonly b: string;
373+
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 111, 15))
374+
}
375+
376+
function f10(foo: Foo) {
377+
>f10 : Symbol(f10, Decl(isomorphicMappedTypeInference.ts, 113, 1))
378+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
379+
>Foo : Symbol(Foo, Decl(isomorphicMappedTypeInference.ts, 108, 81))
380+
381+
let x = validate(foo); // { a: number, readonly b: string }
382+
>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 116, 7))
383+
>validate : Symbol(validate, Decl(isomorphicMappedTypeInference.ts, 104, 1))
384+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
385+
386+
let y = clone(foo); // { a?: number, b: string }
387+
>y : Symbol(y, Decl(isomorphicMappedTypeInference.ts, 117, 7))
388+
>clone : Symbol(clone, Decl(isomorphicMappedTypeInference.ts, 106, 64))
389+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
390+
391+
let z = validateAndClone(foo); // { a: number, b: string }
392+
>z : Symbol(z, Decl(isomorphicMappedTypeInference.ts, 118, 7))
393+
>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69))
394+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
395+
}

tests/baselines/reference/isomorphicMappedTypeInference.types

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,67 @@ function f6(s: string) {
403403
>v : { [x: string]: string | number | boolean; }
404404
>s : string
405405
}
406+
407+
declare function validate<T>(obj: { [P in keyof T]?: T[P] }): T;
408+
>validate : <T>(obj: { [P in keyof T]?: T[P] | undefined; }) => T
409+
>T : T
410+
>obj : { [P in keyof T]?: T[P] | undefined; }
411+
>P : P
412+
>T : T
413+
>T : T
414+
>P : P
415+
>T : T
416+
417+
declare function clone<T>(obj: { readonly [P in keyof T]: T[P] }): T;
418+
>clone : <T>(obj: { readonly [P in keyof T]: T[P]; }) => T
419+
>T : T
420+
>obj : { readonly [P in keyof T]: T[P]; }
421+
>P : P
422+
>T : T
423+
>T : T
424+
>P : P
425+
>T : T
426+
427+
declare function validateAndClone<T>(obj: { readonly [P in keyof T]?: T[P] }): T;
428+
>validateAndClone : <T>(obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T
429+
>T : T
430+
>obj : { readonly [P in keyof T]?: T[P] | undefined; }
431+
>P : P
432+
>T : T
433+
>T : T
434+
>P : P
435+
>T : T
436+
437+
type Foo = {
438+
>Foo : Foo
439+
440+
a?: number;
441+
>a : number | undefined
442+
443+
readonly b: string;
444+
>b : string
445+
}
446+
447+
function f10(foo: Foo) {
448+
>f10 : (foo: Foo) => void
449+
>foo : Foo
450+
>Foo : Foo
451+
452+
let x = validate(foo); // { a: number, readonly b: string }
453+
>x : { a: number; readonly b: string; }
454+
>validate(foo) : { a: number; readonly b: string; }
455+
>validate : <T>(obj: { [P in keyof T]?: T[P] | undefined; }) => T
456+
>foo : Foo
457+
458+
let y = clone(foo); // { a?: number, b: string }
459+
>y : { a?: number | undefined; b: string; }
460+
>clone(foo) : { a?: number | undefined; b: string; }
461+
>clone : <T>(obj: { readonly [P in keyof T]: T[P]; }) => T
462+
>foo : Foo
463+
464+
let z = validateAndClone(foo); // { a: number, b: string }
465+
>z : { a: number; b: string; }
466+
>validateAndClone(foo) : { a: number; b: string; }
467+
>validateAndClone : <T>(obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T
468+
>foo : Foo
469+
}

tests/baselines/reference/mappedTypeErrors.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(38,24): error TS2344: T
1616
Type 'T' is not assignable to type '"visible"'.
1717
Type 'string | number' is not assignable to type '"visible"'.
1818
Type 'string' is not assignable to type '"visible"'.
19-
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 '{ [P in keyof T]?: T[P]; }'.
19+
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 '{ [P in keyof T]?: T[P] | undefined; }'.
2020
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]; }'.
21-
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(62,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]; }'.
21+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(62,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] | undefined; }'.
2222
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(67,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][]; }'.
2323

2424

@@ -112,13 +112,13 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(67,9): error TS2403: Su
112112
var x: { [P in keyof T]: T[P] };
113113
var x: { [P in keyof T]?: T[P] }; // Error
114114
~
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]; }'.
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] | undefined; }'.
116116
var x: { readonly [P in keyof T]: T[P] }; // Error
117117
~
118118
!!! 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]; }'.
119119
var x: { readonly [P in keyof T]?: T[P] }; // Error
120120
~
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]; }'.
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] | undefined; }'.
122122
}
123123

124124
function f12<T>() {

tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @strictNullChecks: true
12
// @noimplicitany: true
23
// @declaration: true
34

@@ -104,4 +105,19 @@ function f6(s: string) {
104105
});
105106
let v = unboxify(b);
106107
let x: string | number | boolean = v[s];
108+
}
109+
110+
declare function validate<T>(obj: { [P in keyof T]?: T[P] }): T;
111+
declare function clone<T>(obj: { readonly [P in keyof T]: T[P] }): T;
112+
declare function validateAndClone<T>(obj: { readonly [P in keyof T]?: T[P] }): T;
113+
114+
type Foo = {
115+
a?: number;
116+
readonly b: string;
117+
}
118+
119+
function f10(foo: Foo) {
120+
let x = validate(foo); // { a: number, readonly b: string }
121+
let y = clone(foo); // { a?: number, b: string }
122+
let z = validateAndClone(foo); // { a: number, b: string }
107123
}

0 commit comments

Comments
 (0)