mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
node builder should strive to preserve mapped type keyofness (#23815)
This commit is contained in:
parent
1cf1b92889
commit
eb80e9a51c
@ -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,
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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))
|
||||
|
||||
@ -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; }
|
||||
>{} : {}
|
||||
|
||||
19
tests/cases/compiler/mappedTypeUnionConstraintInferences.ts
Normal file
19
tests/cases/compiler/mappedTypeUnionConstraintInferences.ts
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user