diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dbaf4ed034..367f64a9b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4532,7 +4532,7 @@ namespace ts { const isomorphicProp = isomorphicType && getPropertyOfType(isomorphicType, propName); const isOptional = templateOptional || !!(isomorphicProp && isomorphicProp.flags & SymbolFlags.Optional); const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | (isOptional ? SymbolFlags.Optional : 0), propName); - prop.type = addOptionality(propType, isOptional); + prop.type = propType; prop.isReadonly = templateReadonly || isomorphicProp && isReadonlySymbol(isomorphicProp); members[propName] = prop; } @@ -4556,7 +4556,7 @@ namespace ts { function getTemplateTypeFromMappedType(type: MappedType) { return type.templateType || (type.templateType = type.declaration.type ? - instantiateType(getTypeFromTypeNode(type.declaration.type), type.mapper || identityMapper) : + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!type.declaration.questionToken), type.mapper || identityMapper) : unknownType); } @@ -6021,7 +6021,7 @@ namespace ts { } const mapper = createUnaryTypeMapper(getTypeParameterFromMappedType(type), indexType); const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; - return addOptionality(instantiateType(getTemplateTypeFromMappedType(type), templateMapper), !!type.declaration.questionToken); + return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); } function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { @@ -8484,16 +8484,18 @@ namespace ts { const typeInferences = createTypeInferencesObject(); const typeInferencesArray = [typeInferences]; const templateType = getTemplateTypeFromMappedType(target); + const readonlyMask = target.declaration.readonlyToken ? false : true; + const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; const properties = getPropertiesOfType(source); const members = createSymbolTable(properties); let hasInferredTypes = false; for (const prop of properties) { const inferredPropType = inferTargetType(getTypeOfSymbol(prop)); if (inferredPropType) { - const inferredProp = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & SymbolFlags.Optional, prop.name); + const inferredProp = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & optionalMask, prop.name); inferredProp.declarations = prop.declarations; inferredProp.type = inferredPropType; - inferredProp.isReadonly = isReadonlySymbol(prop); + inferredProp.isReadonly = readonlyMask && isReadonlySymbol(prop); members[prop.name] = inferredProp; hasInferredTypes = true; } @@ -8502,7 +8504,7 @@ namespace ts { if (indexInfo) { const inferredIndexType = inferTargetType(indexInfo.type); if (inferredIndexType) { - indexInfo = createIndexInfo(inferredIndexType, indexInfo.isReadonly); + indexInfo = createIndexInfo(inferredIndexType, readonlyMask && indexInfo.isReadonly); hasInferredTypes = true; } } diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.js b/tests/baselines/reference/isomorphicMappedTypeInference.js index 0fc646043d..87f3e1c401 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.js +++ b/tests/baselines/reference/isomorphicMappedTypeInference.js @@ -103,6 +103,21 @@ function f6(s: string) { }); let v = unboxify(b); let x: string | number | boolean = v[s]; +} + +declare function validate(obj: { [P in keyof T]?: T[P] }): T; +declare function clone(obj: { readonly [P in keyof T]: T[P] }): T; +declare function validateAndClone(obj: { readonly [P in keyof T]?: T[P] }): T; + +type Foo = { + a?: number; + readonly b: string; +} + +function f10(foo: Foo) { + let x = validate(foo); // { a: number, readonly b: string } + let y = clone(foo); // { a?: number, b: string } + let z = validateAndClone(foo); // { a: number, b: string } } //// [isomorphicMappedTypeInference.js] @@ -190,6 +205,11 @@ function f6(s) { var v = unboxify(b); var x = v[s]; } +function f10(foo) { + var x = validate(foo); // { a: number, readonly b: string } + var y = clone(foo); // { a?: number, b: string } + var z = validateAndClone(foo); // { a: number, b: string } +} //// [isomorphicMappedTypeInference.d.ts] @@ -220,3 +240,17 @@ declare function makeDictionary(obj: { [x: string]: T; }; declare function f6(s: string): void; +declare function validate(obj: { + [P in keyof T]?: T[P]; +}): T; +declare function clone(obj: { + readonly [P in keyof T]: T[P]; +}): T; +declare function validateAndClone(obj: { + readonly [P in keyof T]?: T[P]; +}): T; +declare type Foo = { + a?: number; + readonly b: string; +}; +declare function f10(foo: Foo): void; diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.symbols b/tests/baselines/reference/isomorphicMappedTypeInference.symbols index 62dcc6316b..3e8a92e769 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.symbols +++ b/tests/baselines/reference/isomorphicMappedTypeInference.symbols @@ -332,3 +332,64 @@ function f6(s: string) { >v : Symbol(v, Decl(isomorphicMappedTypeInference.ts, 102, 7)) >s : Symbol(s, Decl(isomorphicMappedTypeInference.ts, 96, 12)) } + +declare function validate(obj: { [P in keyof T]?: T[P] }): T; +>validate : Symbol(validate, Decl(isomorphicMappedTypeInference.ts, 104, 1)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 106, 29)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 106, 37)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 106, 37)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 106, 26)) + +declare function clone(obj: { readonly [P in keyof T]: T[P] }): T; +>clone : Symbol(clone, Decl(isomorphicMappedTypeInference.ts, 106, 64)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 107, 26)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 107, 43)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 107, 43)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 107, 23)) + +declare function validateAndClone(obj: { readonly [P in keyof T]?: T[P] }): T; +>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 108, 37)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 108, 54)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 108, 54)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 108, 34)) + +type Foo = { +>Foo : Symbol(Foo, Decl(isomorphicMappedTypeInference.ts, 108, 81)) + + a?: number; +>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 110, 12)) + + readonly b: string; +>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 111, 15)) +} + +function f10(foo: Foo) { +>f10 : Symbol(f10, Decl(isomorphicMappedTypeInference.ts, 113, 1)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13)) +>Foo : Symbol(Foo, Decl(isomorphicMappedTypeInference.ts, 108, 81)) + + let x = validate(foo); // { a: number, readonly b: string } +>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 116, 7)) +>validate : Symbol(validate, Decl(isomorphicMappedTypeInference.ts, 104, 1)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13)) + + let y = clone(foo); // { a?: number, b: string } +>y : Symbol(y, Decl(isomorphicMappedTypeInference.ts, 117, 7)) +>clone : Symbol(clone, Decl(isomorphicMappedTypeInference.ts, 106, 64)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13)) + + let z = validateAndClone(foo); // { a: number, b: string } +>z : Symbol(z, Decl(isomorphicMappedTypeInference.ts, 118, 7)) +>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13)) +} diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.types b/tests/baselines/reference/isomorphicMappedTypeInference.types index 00a80639f7..eee2d6cbe7 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.types +++ b/tests/baselines/reference/isomorphicMappedTypeInference.types @@ -403,3 +403,67 @@ function f6(s: string) { >v : { [x: string]: string | number | boolean; } >s : string } + +declare function validate(obj: { [P in keyof T]?: T[P] }): T; +>validate : (obj: { [P in keyof T]?: T[P] | undefined; }) => T +>T : T +>obj : { [P in keyof T]?: T[P] | undefined; } +>P : P +>T : T +>T : T +>P : P +>T : T + +declare function clone(obj: { readonly [P in keyof T]: T[P] }): T; +>clone : (obj: { readonly [P in keyof T]: T[P]; }) => T +>T : T +>obj : { readonly [P in keyof T]: T[P]; } +>P : P +>T : T +>T : T +>P : P +>T : T + +declare function validateAndClone(obj: { readonly [P in keyof T]?: T[P] }): T; +>validateAndClone : (obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T +>T : T +>obj : { readonly [P in keyof T]?: T[P] | undefined; } +>P : P +>T : T +>T : T +>P : P +>T : T + +type Foo = { +>Foo : Foo + + a?: number; +>a : number | undefined + + readonly b: string; +>b : string +} + +function f10(foo: Foo) { +>f10 : (foo: Foo) => void +>foo : Foo +>Foo : Foo + + let x = validate(foo); // { a: number, readonly b: string } +>x : { a: number; readonly b: string; } +>validate(foo) : { a: number; readonly b: string; } +>validate : (obj: { [P in keyof T]?: T[P] | undefined; }) => T +>foo : Foo + + let y = clone(foo); // { a?: number, b: string } +>y : { a?: number | undefined; b: string; } +>clone(foo) : { a?: number | undefined; b: string; } +>clone : (obj: { readonly [P in keyof T]: T[P]; }) => T +>foo : Foo + + let z = validateAndClone(foo); // { a: number, b: string } +>z : { a: number; b: string; } +>validateAndClone(foo) : { a: number; b: string; } +>validateAndClone : (obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T +>foo : Foo +} diff --git a/tests/baselines/reference/mappedTypeErrors.errors.txt b/tests/baselines/reference/mappedTypeErrors.errors.txt index b3cba7eb54..945652bf92 100644 --- a/tests/baselines/reference/mappedTypeErrors.errors.txt +++ b/tests/baselines/reference/mappedTypeErrors.errors.txt @@ -16,9 +16,9 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(38,24): error TS2344: T Type 'T' is not assignable to type '"visible"'. Type 'string | number' is not assignable to type '"visible"'. Type 'string' is not assignable to type '"visible"'. -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]; }'. +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; }'. 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]; }'. -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]; }'. +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; }'. 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][]; }'. @@ -112,13 +112,13 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(67,9): error TS2403: Su var x: { [P in keyof T]: T[P] }; var x: { [P in keyof T]?: T[P] }; // Error ~ -!!! 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]; }'. +!!! 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; }'. var x: { readonly [P in keyof T]: T[P] }; // Error ~ !!! 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]; }'. var x: { readonly [P in keyof T]?: T[P] }; // Error ~ -!!! 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]; }'. +!!! 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; }'. } function f12() { diff --git a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts index 19a0c4889a..4aab2d95ba 100644 --- a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts +++ b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts @@ -1,3 +1,4 @@ +// @strictNullChecks: true // @noimplicitany: true // @declaration: true @@ -104,4 +105,19 @@ function f6(s: string) { }); let v = unboxify(b); let x: string | number | boolean = v[s]; +} + +declare function validate(obj: { [P in keyof T]?: T[P] }): T; +declare function clone(obj: { readonly [P in keyof T]: T[P] }): T; +declare function validateAndClone(obj: { readonly [P in keyof T]?: T[P] }): T; + +type Foo = { + a?: number; + readonly b: string; +} + +function f10(foo: Foo) { + let x = validate(foo); // { a: number, readonly b: string } + let y = clone(foo); // { a?: number, b: string } + let z = validateAndClone(foo); // { a: number, b: string } } \ No newline at end of file