node builder should strive to preserve mapped type keyofness (#23815)

This commit is contained in:
Wesley Wigham 2018-05-03 12:38:31 -07:00 committed by GitHub
parent 1cf1b92889
commit eb80e9a51c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 273 additions and 10 deletions

View File

@ -3158,7 +3158,16 @@ namespace ts {
Debug.assert(!!(type.flags & TypeFlags.Object));
const readonlyToken = type.declaration.readonlyToken ? <ReadonlyToken | PlusToken | MinusToken>createToken(type.declaration.readonlyToken.kind) : undefined;
const questionToken = type.declaration.questionToken ? <QuestionToken | PlusToken | MinusToken>createToken(type.declaration.questionToken.kind) : undefined;
const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context, getConstraintTypeFromMappedType(type));
let appropriateConstraintTypeNode: TypeNode;
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// We have a { [P in keyof T]: X }
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
appropriateConstraintTypeNode = createTypeOperatorNode(typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
}
else {
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
}
const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode);
const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context);
const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode);
return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
@ -3534,17 +3543,21 @@ namespace ts {
return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments);
}
function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintFromTypeParameter(type)): TypeParameterDeclaration {
function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode): TypeParameterDeclaration {
const savedContextFlags = context.flags;
context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic
const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
const constraintNode = constraint && typeToTypeNodeHelper(constraint, context);
const defaultParameter = getDefaultFromTypeParameter(type);
const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context);
context.flags = savedContextFlags;
return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode);
}
function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintFromTypeParameter(type)): TypeParameterDeclaration {
const constraintNode = constraint && typeToTypeNodeHelper(constraint, context);
return typeParameterToDeclarationWithConstraint(type, context, constraintNode);
}
function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration {
const parameterDeclaration = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
Debug.assert(!!parameterDeclaration || isTransientSymbol(parameterSymbol) && !!parameterSymbol.isRestParameter);
@ -6221,10 +6234,8 @@ namespace ts {
const templateType = getTemplateTypeFromMappedType(<MappedType>type.target || type);
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
const templateModifiers = getMappedTypeModifiers(type);
const constraintDeclaration = type.declaration.typeParameter.constraint;
const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique;
if (constraintDeclaration.kind === SyntaxKind.TypeOperator &&
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) {
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// We have a { [P in keyof T]: X }
for (const prop of getPropertiesOfType(modifiersType)) {
addMemberForKeyType(getLiteralTypeFromPropertyName(prop, include), /*_index*/ undefined, prop);
@ -6301,15 +6312,23 @@ namespace ts {
unknownType);
}
function getConstraintDeclarationForMappedType(type: MappedType) {
return type.declaration.typeParameter.constraint;
}
function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) {
const constraintDeclaration = getConstraintDeclarationForMappedType(type);
return constraintDeclaration.kind === SyntaxKind.TypeOperator &&
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword;
}
function getModifiersTypeFromMappedType(type: MappedType) {
if (!type.modifiersType) {
const constraintDeclaration = type.declaration.typeParameter.constraint;
if (constraintDeclaration.kind === SyntaxKind.TypeOperator &&
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) {
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check
// AST nodes here because, when T is a non-generic type, the logic below eagerly resolves
// 'keyof T' to a literal union type and we can't recover T from that type.
type.modifiersType = instantiateType(getTypeFromTypeNode((<TypeOperatorNode>constraintDeclaration).type), type.mapper || identityMapper);
type.modifiersType = instantiateType(getTypeFromTypeNode((<TypeOperatorNode>getConstraintDeclarationForMappedType(type)).type), type.mapper || identityMapper);
}
else {
// Otherwise, get the declared constraint type, and if the constraint type is a type parameter,

View File

@ -0,0 +1,51 @@
//// [mappedTypeUnionConstraintInferences.ts]
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
export function doSomething_Actual<T extends {
prop: string;
}>(a: T) {
const x: { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; } = null as any;
return x;
}
export declare function doSomething_Expected<T extends {
prop: string;
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
export let a = doSomething_Actual({ prop: "test" });
a = {} // should be fine, equivalent to below
export let b = doSomething_Expected({ prop: "test" });
b = {} // fine
//// [mappedTypeUnionConstraintInferences.js]
"use strict";
exports.__esModule = true;
function doSomething_Actual(a) {
var x = null;
return x;
}
exports.doSomething_Actual = doSomething_Actual;
exports.a = doSomething_Actual({ prop: "test" });
exports.a = {}; // should be fine, equivalent to below
exports.b = doSomething_Expected({ prop: "test" });
exports.b = {}; // fine
//// [mappedTypeUnionConstraintInferences.d.ts]
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
export declare function doSomething_Actual<T extends {
prop: string;
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
export declare function doSomething_Expected<T extends {
prop: string;
}>(a: T): {
[P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P];
};
export declare let a: {
prop?: string;
};
export declare let b: {
prop?: string;
};

View File

@ -0,0 +1,81 @@
=== tests/cases/compiler/mappedTypeUnionConstraintInferences.ts ===
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
>Omit : Symbol(Omit, Decl(mappedTypeUnionConstraintInferences.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 0, 25))
>K : Symbol(K, Decl(mappedTypeUnionConstraintInferences.ts, 0, 27))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 0, 25))
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 0, 25))
>Exclude : Symbol(Exclude, Decl(lib.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 0, 25))
>K : Symbol(K, Decl(mappedTypeUnionConstraintInferences.ts, 0, 27))
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
>PartialProperties : Symbol(PartialProperties, Decl(mappedTypeUnionConstraintInferences.ts, 0, 78))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 1, 38))
>K : Symbol(K, Decl(mappedTypeUnionConstraintInferences.ts, 1, 40))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 1, 38))
>Partial : Symbol(Partial, Decl(lib.d.ts, --, --))
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 1, 38))
>K : Symbol(K, Decl(mappedTypeUnionConstraintInferences.ts, 1, 40))
>Omit : Symbol(Omit, Decl(mappedTypeUnionConstraintInferences.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 1, 38))
>K : Symbol(K, Decl(mappedTypeUnionConstraintInferences.ts, 1, 40))
export function doSomething_Actual<T extends {
>doSomething_Actual : Symbol(doSomething_Actual, Decl(mappedTypeUnionConstraintInferences.ts, 1, 95))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 2, 35))
prop: string;
>prop : Symbol(prop, Decl(mappedTypeUnionConstraintInferences.ts, 2, 46))
}>(a: T) {
>a : Symbol(a, Decl(mappedTypeUnionConstraintInferences.ts, 4, 3))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 2, 35))
const x: { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; } = null as any;
>x : Symbol(x, Decl(mappedTypeUnionConstraintInferences.ts, 5, 9))
>P : Symbol(P, Decl(mappedTypeUnionConstraintInferences.ts, 5, 16))
>PartialProperties : Symbol(PartialProperties, Decl(mappedTypeUnionConstraintInferences.ts, 0, 78))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 2, 35))
>PartialProperties : Symbol(PartialProperties, Decl(mappedTypeUnionConstraintInferences.ts, 0, 78))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 2, 35))
>P : Symbol(P, Decl(mappedTypeUnionConstraintInferences.ts, 5, 16))
return x;
>x : Symbol(x, Decl(mappedTypeUnionConstraintInferences.ts, 5, 9))
}
export declare function doSomething_Expected<T extends {
>doSomething_Expected : Symbol(doSomething_Expected, Decl(mappedTypeUnionConstraintInferences.ts, 7, 1))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 8, 45))
prop: string;
>prop : Symbol(prop, Decl(mappedTypeUnionConstraintInferences.ts, 8, 56))
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
>a : Symbol(a, Decl(mappedTypeUnionConstraintInferences.ts, 10, 3))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 8, 45))
>P : Symbol(P, Decl(mappedTypeUnionConstraintInferences.ts, 10, 13))
>PartialProperties : Symbol(PartialProperties, Decl(mappedTypeUnionConstraintInferences.ts, 0, 78))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 8, 45))
>PartialProperties : Symbol(PartialProperties, Decl(mappedTypeUnionConstraintInferences.ts, 0, 78))
>T : Symbol(T, Decl(mappedTypeUnionConstraintInferences.ts, 8, 45))
>P : Symbol(P, Decl(mappedTypeUnionConstraintInferences.ts, 10, 13))
export let a = doSomething_Actual({ prop: "test" });
>a : Symbol(a, Decl(mappedTypeUnionConstraintInferences.ts, 12, 10))
>doSomething_Actual : Symbol(doSomething_Actual, Decl(mappedTypeUnionConstraintInferences.ts, 1, 95))
>prop : Symbol(prop, Decl(mappedTypeUnionConstraintInferences.ts, 12, 35))
a = {} // should be fine, equivalent to below
>a : Symbol(a, Decl(mappedTypeUnionConstraintInferences.ts, 12, 10))
export let b = doSomething_Expected({ prop: "test" });
>b : Symbol(b, Decl(mappedTypeUnionConstraintInferences.ts, 15, 10))
>doSomething_Expected : Symbol(doSomething_Expected, Decl(mappedTypeUnionConstraintInferences.ts, 7, 1))
>prop : Symbol(prop, Decl(mappedTypeUnionConstraintInferences.ts, 15, 37))
b = {} // fine
>b : Symbol(b, Decl(mappedTypeUnionConstraintInferences.ts, 15, 10))

View File

@ -0,0 +1,93 @@
=== tests/cases/compiler/mappedTypeUnionConstraintInferences.ts ===
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
>Omit : Pick<T, Exclude<keyof T, K>>
>T : T
>K : K
>T : T
>Pick : Pick<T, K>
>T : T
>Exclude : Exclude<T, U>
>T : T
>K : K
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
>PartialProperties : PartialProperties<T, K>
>T : T
>K : K
>T : T
>Partial : Partial<T>
>Pick : Pick<T, K>
>T : T
>K : K
>Omit : Pick<T, Exclude<keyof T, K>>
>T : T
>K : K
export function doSomething_Actual<T extends {
>doSomething_Actual : <T extends { prop: string; }>(a: T) => { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
>T : T
prop: string;
>prop : string
}>(a: T) {
>a : T
>T : T
const x: { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; } = null as any;
>x : { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
>P : P
>PartialProperties : PartialProperties<T, K>
>T : T
>PartialProperties : PartialProperties<T, K>
>T : T
>P : P
>null as any : any
>null : null
return x;
>x : { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
}
export declare function doSomething_Expected<T extends {
>doSomething_Expected : <T extends { prop: string; }>(a: T) => { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
>T : T
prop: string;
>prop : string
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
>a : T
>T : T
>P : P
>PartialProperties : PartialProperties<T, K>
>T : T
>PartialProperties : PartialProperties<T, K>
>T : T
>P : P
export let a = doSomething_Actual({ prop: "test" });
>a : { prop?: string; }
>doSomething_Actual({ prop: "test" }) : { prop?: string; }
>doSomething_Actual : <T extends { prop: string; }>(a: T) => { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
>{ prop: "test" } : { prop: string; }
>prop : string
>"test" : "test"
a = {} // should be fine, equivalent to below
>a = {} : {}
>a : { prop?: string; }
>{} : {}
export let b = doSomething_Expected({ prop: "test" });
>b : { prop?: string; }
>doSomething_Expected({ prop: "test" }) : { prop?: string; }
>doSomething_Expected : <T extends { prop: string; }>(a: T) => { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; }
>{ prop: "test" } : { prop: string; }
>prop : string
>"test" : "test"
b = {} // fine
>b = {} : {}
>b : { prop?: string; }
>{} : {}

View File

@ -0,0 +1,19 @@
// @declaration: true
// @keyofStringsOnly: true
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
export function doSomething_Actual<T extends {
prop: string;
}>(a: T) {
const x: { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; } = null as any;
return x;
}
export declare function doSomething_Expected<T extends {
prop: string;
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
export let a = doSomething_Actual({ prop: "test" });
a = {} // should be fine, equivalent to below
export let b = doSomething_Expected({ prop: "test" });
b = {} // fine