@@ -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 }
0 commit comments