From 47f15d290bcbb215205303bf5c4f7bdef211935e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 28 Feb 2018 15:00:31 -0800 Subject: [PATCH 1/3] Add inference priority for mapped type keys The new priority causes union inference, similarly to return type --- src/compiler/checker.ts | 7 ++- src/compiler/types.ts | 13 +++-- .../reference/api/tsserverlibrary.d.ts | 10 ++-- tests/baselines/reference/api/typescript.d.ts | 10 ++-- .../reference/mappedTypeMultiInference.js | 34 ++++++++++++ .../mappedTypeMultiInference.symbols | 44 +++++++++++++++ .../reference/mappedTypeMultiInference.types | 54 +++++++++++++++++++ .../compiler/mappedTypeMultiInference.ts | 21 ++++++++ 8 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/mappedTypeMultiInference.js create mode 100644 tests/baselines/reference/mappedTypeMultiInference.symbols create mode 100644 tests/baselines/reference/mappedTypeMultiInference.types create mode 100644 tests/cases/compiler/mappedTypeMultiInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9579ba80d0fb1..4402be0ba6cf0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11706,7 +11706,7 @@ namespace ts { const inferredType = inferTypeForHomomorphicMappedType(source, target); if (inferredType) { const savePriority = priority; - priority |= InferencePriority.MappedType; + priority |= InferencePriority.HomomorphicMappedType; inferFromTypes(inferredType, inference.typeParameter); priority = savePriority; } @@ -11716,7 +11716,10 @@ namespace ts { if (constraintType.flags & TypeFlags.TypeParameter) { // We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type // parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X. + const savePriority = priority; + priority |= InferencePriority.MappedType; inferFromTypes(getIndexType(source), constraintType); + priority = savePriority; inferFromTypes(getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol)), getTemplateTypeFromMappedType(target)); return; } @@ -11848,7 +11851,7 @@ namespace ts { // If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if // union types were requested or if all inferences were made from the return type position, infer a // union type. Otherwise, infer a common supertype. - const unwidenedType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? + const unwidenedType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.PriorityImpliesUnion ? getUnionType(baseCandidates, UnionReduction.Subtype) : getCommonSupertype(baseCandidates); inferredType = getWidenedType(unwidenedType); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3be8602f6d2c7..9ff72814d9188 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3902,11 +3902,14 @@ namespace ts { export type TypeMapper = (t: TypeParameter) => Type; export const enum InferencePriority { - NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type - MappedType = 1 << 1, // Reverse inference for mapped type - ReturnType = 1 << 2, // Inference made from return type of generic function - NoConstraints = 1 << 3, // Don't infer from constraints of instantiable types - AlwaysStrict = 1 << 4, // Always use strict rules for contravariant inferences + NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type + HomomorphicMappedType = 1 << 1, // Reverse inference for hoimomorphic mapped type + MappedType = 1 << 2, // Reverse inference for mapped type + ReturnType = 1 << 3, // Inference made from return type of generic function + NoConstraints = 1 << 4, // Don't infer from constraints of instantiable types + AlwaysStrict = 1 << 5, // Always use strict rules for contravariant inferences + + PriorityImpliesUnion = ReturnType | MappedType, // These priorities imply that the resulting type should be a union of all candidates } /* @internal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 43ebec6ad2424..a48cbeb58c72d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2222,10 +2222,12 @@ declare namespace ts { } enum InferencePriority { NakedTypeVariable = 1, - MappedType = 2, - ReturnType = 4, - NoConstraints = 8, - AlwaysStrict = 16, + HomomorphicMappedType = 2, + MappedType = 4, + ReturnType = 8, + NoConstraints = 16, + AlwaysStrict = 32, + PriorityImpliesUnion = 12, } interface JsFileExtensionInfo { extension: string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f111d09013b4d..7320a11beaa32 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2222,10 +2222,12 @@ declare namespace ts { } enum InferencePriority { NakedTypeVariable = 1, - MappedType = 2, - ReturnType = 4, - NoConstraints = 8, - AlwaysStrict = 16, + HomomorphicMappedType = 2, + MappedType = 4, + ReturnType = 8, + NoConstraints = 16, + AlwaysStrict = 32, + PriorityImpliesUnion = 12, } interface JsFileExtensionInfo { extension: string; diff --git a/tests/baselines/reference/mappedTypeMultiInference.js b/tests/baselines/reference/mappedTypeMultiInference.js new file mode 100644 index 0000000000000..1ce8e6b878565 --- /dev/null +++ b/tests/baselines/reference/mappedTypeMultiInference.js @@ -0,0 +1,34 @@ +//// [mappedTypeMultiInference.ts] +interface Style { + flashy: any; +} + +declare function mergeStyleSets( + ...cssSets: { [P in K]?: Style }[]): { [P in K]: Style }; + +// Expected: +// let x: { +// a: Style; +// b: Style; +// } +let x = mergeStyleSets( + {}, + { + a: { flashy: true }, + }, + { + b: { flashy: true }, + }, +) + +//// [mappedTypeMultiInference.js] +// Expected: +// let x: { +// a: Style; +// b: Style; +// } +var x = mergeStyleSets({}, { + a: { flashy: true } +}, { + b: { flashy: true } +}); diff --git a/tests/baselines/reference/mappedTypeMultiInference.symbols b/tests/baselines/reference/mappedTypeMultiInference.symbols new file mode 100644 index 0000000000000..216d0746f4ce6 --- /dev/null +++ b/tests/baselines/reference/mappedTypeMultiInference.symbols @@ -0,0 +1,44 @@ +=== tests/cases/compiler/mappedTypeMultiInference.ts === +interface Style { +>Style : Symbol(Style, Decl(mappedTypeMultiInference.ts, 0, 0)) + + flashy: any; +>flashy : Symbol(Style.flashy, Decl(mappedTypeMultiInference.ts, 0, 17)) +} + +declare function mergeStyleSets( +>mergeStyleSets : Symbol(mergeStyleSets, Decl(mappedTypeMultiInference.ts, 2, 1)) +>K : Symbol(K, Decl(mappedTypeMultiInference.ts, 4, 32)) + + ...cssSets: { [P in K]?: Style }[]): { [P in K]: Style }; +>cssSets : Symbol(cssSets, Decl(mappedTypeMultiInference.ts, 4, 50)) +>P : Symbol(P, Decl(mappedTypeMultiInference.ts, 5, 19)) +>K : Symbol(K, Decl(mappedTypeMultiInference.ts, 4, 32)) +>Style : Symbol(Style, Decl(mappedTypeMultiInference.ts, 0, 0)) +>P : Symbol(P, Decl(mappedTypeMultiInference.ts, 5, 44)) +>K : Symbol(K, Decl(mappedTypeMultiInference.ts, 4, 32)) +>Style : Symbol(Style, Decl(mappedTypeMultiInference.ts, 0, 0)) + +// Expected: +// let x: { +// a: Style; +// b: Style; +// } +let x = mergeStyleSets( +>x : Symbol(x, Decl(mappedTypeMultiInference.ts, 12, 3)) +>mergeStyleSets : Symbol(mergeStyleSets, Decl(mappedTypeMultiInference.ts, 2, 1)) + + {}, + { + a: { flashy: true }, +>a : Symbol(a, Decl(mappedTypeMultiInference.ts, 14, 5)) +>flashy : Symbol(flashy, Decl(mappedTypeMultiInference.ts, 15, 12)) + + }, + { + b: { flashy: true }, +>b : Symbol(b, Decl(mappedTypeMultiInference.ts, 17, 5)) +>flashy : Symbol(flashy, Decl(mappedTypeMultiInference.ts, 18, 12)) + + }, +) diff --git a/tests/baselines/reference/mappedTypeMultiInference.types b/tests/baselines/reference/mappedTypeMultiInference.types new file mode 100644 index 0000000000000..d07a60f3f4bab --- /dev/null +++ b/tests/baselines/reference/mappedTypeMultiInference.types @@ -0,0 +1,54 @@ +=== tests/cases/compiler/mappedTypeMultiInference.ts === +interface Style { +>Style : Style + + flashy: any; +>flashy : any +} + +declare function mergeStyleSets( +>mergeStyleSets : (...cssSets: { [P in K]?: Style; }[]) => { [P in K]: Style; } +>K : K + + ...cssSets: { [P in K]?: Style }[]): { [P in K]: Style }; +>cssSets : { [P in K]?: Style; }[] +>P : P +>K : K +>Style : Style +>P : P +>K : K +>Style : Style + +// Expected: +// let x: { +// a: Style; +// b: Style; +// } +let x = mergeStyleSets( +>x : { a: Style; b: Style; } +>mergeStyleSets( {}, { a: { flashy: true }, }, { b: { flashy: true }, },) : { a: Style; b: Style; } +>mergeStyleSets : (...cssSets: { [P in K]?: Style; }[]) => { [P in K]: Style; } + + {}, +>{} : {} + { +>{ a: { flashy: true }, } : { a: { flashy: boolean; }; } + + a: { flashy: true }, +>a : { flashy: boolean; } +>{ flashy: true } : { flashy: boolean; } +>flashy : boolean +>true : true + + }, + { +>{ b: { flashy: true }, } : { b: { flashy: boolean; }; } + + b: { flashy: true }, +>b : { flashy: boolean; } +>{ flashy: true } : { flashy: boolean; } +>flashy : boolean +>true : true + + }, +) diff --git a/tests/cases/compiler/mappedTypeMultiInference.ts b/tests/cases/compiler/mappedTypeMultiInference.ts new file mode 100644 index 0000000000000..46d2093fc2ad7 --- /dev/null +++ b/tests/cases/compiler/mappedTypeMultiInference.ts @@ -0,0 +1,21 @@ +interface Style { + flashy: any; +} + +declare function mergeStyleSets( + ...cssSets: { [P in K]?: Style }[]): { [P in K]: Style }; + +// Expected: +// let x: { +// a: Style; +// b: Style; +// } +let x = mergeStyleSets( + {}, + { + a: { flashy: true }, + }, + { + b: { flashy: true }, + }, +) \ No newline at end of file From 9e12919a2ac16beddc04831b7fb2b00a345d0d98 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 28 Feb 2018 17:31:48 -0800 Subject: [PATCH 2/3] Rename priority --- src/compiler/checker.ts | 2 +- src/compiler/types.ts | 4 ++-- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4402be0ba6cf0..6240ab80cdfe6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11717,7 +11717,7 @@ namespace ts { // We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type // parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X. const savePriority = priority; - priority |= InferencePriority.MappedType; + priority |= InferencePriority.MappedTypeConstraint; inferFromTypes(getIndexType(source), constraintType); priority = savePriority; inferFromTypes(getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol)), getTemplateTypeFromMappedType(target)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9ff72814d9188..b89869479dbee 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3904,12 +3904,12 @@ namespace ts { export const enum InferencePriority { NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type HomomorphicMappedType = 1 << 1, // Reverse inference for hoimomorphic mapped type - MappedType = 1 << 2, // Reverse inference for mapped type + MappedTypeConstraint = 1 << 2, // Reverse inference for mapped type ReturnType = 1 << 3, // Inference made from return type of generic function NoConstraints = 1 << 4, // Don't infer from constraints of instantiable types AlwaysStrict = 1 << 5, // Always use strict rules for contravariant inferences - PriorityImpliesUnion = ReturnType | MappedType, // These priorities imply that the resulting type should be a union of all candidates + PriorityImpliesUnion = ReturnType | MappedTypeConstraint, // These priorities imply that the resulting type should be a union of all candidates } /* @internal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a48cbeb58c72d..fc5302ef16b54 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2223,7 +2223,7 @@ declare namespace ts { enum InferencePriority { NakedTypeVariable = 1, HomomorphicMappedType = 2, - MappedType = 4, + MappedTypeConstraint = 4, ReturnType = 8, NoConstraints = 16, AlwaysStrict = 32, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7320a11beaa32..bf0c79e8f8405 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2223,7 +2223,7 @@ declare namespace ts { enum InferencePriority { NakedTypeVariable = 1, HomomorphicMappedType = 2, - MappedType = 4, + MappedTypeConstraint = 4, ReturnType = 8, NoConstraints = 16, AlwaysStrict = 32, From 9cabea66599b1433175a0a23d9e866ed0f6037eb Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 1 Mar 2018 12:56:59 -0800 Subject: [PATCH 3/3] Fix comment typo --- src/compiler/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b89869479dbee..b0e7bd1d20a39 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3903,7 +3903,7 @@ namespace ts { export const enum InferencePriority { NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type - HomomorphicMappedType = 1 << 1, // Reverse inference for hoimomorphic mapped type + HomomorphicMappedType = 1 << 1, // Reverse inference for homomorphic mapped type MappedTypeConstraint = 1 << 2, // Reverse inference for mapped type ReturnType = 1 << 3, // Inference made from return type of generic function NoConstraints = 1 << 4, // Don't infer from constraints of instantiable types