Skip to content

Preserve modifiers in homomorphic mapped types #12563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 = (<LiteralType>t).text;
const isomorphicProp = isomorphicType && getPropertyOfType(isomorphicType, propName);
const isOptional = templateOptional || !!(isomorphicProp && isomorphicProp.flags & SymbolFlags.Optional);
const prop = <TransientSymbol>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);
Expand All @@ -4549,6 +4552,11 @@ namespace ts {
unknownType);
}

function getIsomorphicTypeFromMappedType(type: MappedType) {
const constraint = getConstraintDeclaration(getTypeParameterFromMappedType(type));
return constraint.kind === SyntaxKind.TypeOperator ? instantiateType(getTypeFromTypeNode((<TypeOperatorNode>constraint).type), type.mapper || identityMapper) : undefined;
}

function getErasedTemplateTypeFromMappedType(type: MappedType) {
return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType));
}
Expand Down
145 changes: 145 additions & 0 deletions tests/baselines/reference/mappedTypeModifiers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//// [mappedTypeModifiers.ts]

type T = { a: number, b: string };
type TU = { a: number | undefined, b: string | undefined };
type TP = { a?: number, b?: string };
type TR = { readonly a: number, readonly b: string };
type TPR = { readonly a?: number, readonly b?: string };

// Validate they all have the same keys
var v00: "a" | "b";
var v00: keyof T;
var v00: keyof TU;
var v00: keyof TP;
var v00: keyof TR;
var v00: keyof TPR;

// Validate that non-isomorphic mapped types strip modifiers
var v01: T;
var v01: Pick<TR, keyof T>;
var v01: Pick<Readonly<T>, keyof T>;

// Validate that non-isomorphic mapped types strip modifiers
var v02: TU;
var v02: Pick<TP, keyof T>;
var v02: Pick<TPR, keyof T>;
var v02: Pick<Partial<T>, keyof T>;
var v02: Pick<Partial<Readonly<T>>, keyof T>;

// Validate that isomorphic mapped types preserve optional modifier
var v03: TP;
var v03: Partial<T>;

// Validate that isomorphic mapped types preserve readonly modifier
var v04: TR;
var v04: Readonly<T>;

// Validate that isomorphic mapped types preserve both partial and readonly modifiers
var v05: TPR;
var v05: Partial<TR>;
var v05: Readonly<TP>;
var v05: Partial<Readonly<T>>;
var v05: Readonly<Partial<T>>;

type Boxified<T> = { [P in keyof T]: { x: T[P] } };

type B = { a: { x: number }, b: { x: string } };
type BU = { a: { x: number } | undefined, b: { x: string } | undefined };
type BP = { a?: { x: number }, b?: { x: string } };
type BR = { readonly a: { x: number }, readonly b: { x: string } };
type BPR = { readonly a?: { x: number }, readonly b?: { x: string } };

// Validate they all have the same keys
var b00: "a" | "b";
var b00: keyof B;
var b00: keyof BU;
var b00: keyof BP;
var b00: keyof BR;
var b00: keyof BPR;

// Validate that non-isomorphic mapped types strip modifiers
var b01: B;
var b01: Pick<BR, keyof B>;
var b01: Pick<Readonly<BR>, keyof B>;

// Validate that non-isomorphic mapped types strip modifiers
var b02: BU;
var b02: Pick<BP, keyof B>;
var b02: Pick<BPR, keyof B>;
var b02: Pick<Partial<B>, keyof B>;
var b02: Pick<Partial<Readonly<B>>, keyof B>;

// Validate that isomorphic mapped types preserve optional modifier
var b03: BP;
var b03: Partial<B>;

// Validate that isomorphic mapped types preserve readonly modifier
var b04: BR;
var b04: Readonly<B>;

// Validate that isomorphic mapped types preserve both partial and readonly modifiers
var b05: BPR;
var b05: Partial<BR>;
var b05: Readonly<BP>;
var b05: Partial<Readonly<B>>;
var b05: Readonly<Partial<B>>;

//// [mappedTypeModifiers.js]
// Validate they all have the same keys
var v00;
var v00;
var v00;
var v00;
var v00;
var v00;
// Validate that non-isomorphic mapped types strip modifiers
var v01;
var v01;
var v01;
// Validate that non-isomorphic mapped types strip modifiers
var v02;
var v02;
var v02;
var v02;
var v02;
// Validate that isomorphic mapped types preserve optional modifier
var v03;
var v03;
// Validate that isomorphic mapped types preserve readonly modifier
var v04;
var v04;
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
var v05;
var v05;
var v05;
var v05;
var v05;
// Validate they all have the same keys
var b00;
var b00;
var b00;
var b00;
var b00;
var b00;
// Validate that non-isomorphic mapped types strip modifiers
var b01;
var b01;
var b01;
// Validate that non-isomorphic mapped types strip modifiers
var b02;
var b02;
var b02;
var b02;
var b02;
// Validate that isomorphic mapped types preserve optional modifier
var b03;
var b03;
// Validate that isomorphic mapped types preserve readonly modifier
var b04;
var b04;
// Validate that isomorphic mapped types preserve both partial and readonly modifiers
var b05;
var b05;
var b05;
var b05;
var b05;
Loading