Skip to content
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
39 changes: 29 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
51 changes: 51 additions & 0 deletions tests/baselines/reference/mappedTypeUnionConstraintInferences.js
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions tests/cases/compiler/mappedTypeUnionConstraintInferences.ts
Original file line number Diff line number Diff line change
@@ -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