From f55b3ef87a8263c44ab41d728c4669363dfa772c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 29 Nov 2016 12:48:41 -0800 Subject: [PATCH] Preserve modifiers in isomorphic mapped types --- src/compiler/checker.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 527735a3b0..46f1048877 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4497,12 +4497,14 @@ namespace ts { // Resolve upfront such that recursive references see an empty object type. setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, - // and T as the template type. + // and T as the template type. If K is of the form 'keyof S', the mapped type and S are + // isomorphic and we copy property modifiers from corresponding properties in S. const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); + const isomorphicType = getIsomorphicTypeFromMappedType(type); const templateType = getTemplateTypeFromMappedType(type); - const isReadonly = !!type.declaration.readonlyToken; - const isOptional = !!type.declaration.questionToken; + const templateReadonly = !!type.declaration.readonlyToken; + const templateOptional = !!type.declaration.questionToken; // First, if the constraint type is a type parameter, obtain the base constraint. Then, // if the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X. // Finally, iterate over the constituents of the resulting iteration type. @@ -4515,18 +4517,19 @@ namespace ts { const iterationMapper = createUnaryTypeMapper(typeParameter, t); const templateMapper = type.mapper ? combineTypeMappers(type.mapper, iterationMapper) : iterationMapper; const propType = instantiateType(templateType, templateMapper); - // If the current iteration type constituent is a literal type, create a property. - // Otherwise, for type string create a string index signature and for type number - // create a numeric index signature. - if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) { + // If the current iteration type constituent is a string literal type, create a property. + // Otherwise, for type string create a string index signature. + if (t.flags & TypeFlags.StringLiteral) { const propName = (t).text; + 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.isReadonly = isReadonly; + prop.isReadonly = templateReadonly || isomorphicProp && isReadonlySymbol(isomorphicProp); members[propName] = prop; } else if (t.flags & TypeFlags.String) { - stringIndexInfo = createIndexInfo(propType, isReadonly); + stringIndexInfo = createIndexInfo(propType, templateReadonly); } }); setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); @@ -4549,6 +4552,11 @@ namespace ts { unknownType); } + function getIsomorphicTypeFromMappedType(type: MappedType) { + const constraint = getConstraintDeclaration(getTypeParameterFromMappedType(type)); + return constraint.kind === SyntaxKind.TypeOperator ? instantiateType(getTypeFromTypeNode((constraint).type), type.mapper || identityMapper) : undefined; + } + function getErasedTemplateTypeFromMappedType(type: MappedType) { return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType)); }