From 5b24ea80a74dcb73cef336c2ee06957b858c2c89 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 1 Jul 2019 17:46:50 -1000 Subject: [PATCH 1/2] Restore union-like behavior for inference to conditional types --- src/compiler/checker.ts | 60 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 92df91d00841f..893e7901d64cd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15354,36 +15354,11 @@ namespace ts { inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } else if (target.flags & TypeFlags.Conditional && !contravariant) { - inferFromTypes(source, getTrueTypeFromConditionalType(target)); - inferFromTypes(source, getFalseTypeFromConditionalType(target)); + const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; + inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); } else if (target.flags & TypeFlags.UnionOrIntersection) { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. - let typeVariableCount = 0; - for (const t of (target).types) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - inferFromTypes(source, t); - } - } - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. For intersection types - // we only infer to single naked type variables. - if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; - for (const t of (target).types) { - if (getInferenceInfoForType(t)) { - inferFromTypes(source, t); - } - } - priority = savePriority; - } + inferToMultipleTypes(source, (target).types, !!(target.flags & TypeFlags.Intersection)); } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type @@ -15481,6 +15456,35 @@ namespace ts { return undefined; } + function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + let typeVariableCount = 0; + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + inferFromTypes(source, t); + } + } + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) { + const savePriority = priority; + priority |= InferencePriority.NakedTypeVariable; + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferFromTypes(source, t); + } + } + priority = savePriority; + } + } + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; From 8e75382ce3a8aff1783efd2f9fa5e35da241daf9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 1 Jul 2019 17:49:36 -1000 Subject: [PATCH 2/2] Add regression test --- ...onalTypeRelaxingConstraintAssignability.js | 19 +++++++- ...ypeRelaxingConstraintAssignability.symbols | 44 +++++++++++++++++++ ...lTypeRelaxingConstraintAssignability.types | 27 ++++++++++++ ...onalTypeRelaxingConstraintAssignability.ts | 15 ++++++- 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.js b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.js index f066b18d5c64f..5f142116bab6f 100644 --- a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.js +++ b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.js @@ -21,7 +21,21 @@ export class Elem< new Elem(undefined as ElChildren.Void); new Elem('' as ElChildren.Text); new Elem('' as ElChildren.Void | ElChildren.Text); // error -new Elem('' as ElChildren); // error +new Elem('' as ElChildren); // error + +// Repro from #31766 + +interface I { a: string } + +type DeepPartial = + T extends object ? {[K in keyof T]?: DeepPartial} : T; + +declare function f(t: T, partial: DeepPartial): T; + +function g(p1: I, p2: Partial): I { + return f(p1, p2); +} + //// [conditionalTypeRelaxingConstraintAssignability.js] "use strict"; @@ -37,3 +51,6 @@ new Elem(undefined); new Elem(''); new Elem(''); // error new Elem(''); // error +function g(p1, p2) { + return f(p1, p2); +} diff --git a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.symbols b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.symbols index 7ce85b0fbfaf8..cd526f9d4bce9 100644 --- a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.symbols +++ b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.symbols @@ -71,3 +71,47 @@ new Elem('' as ElChildren); // error >Elem : Symbol(Elem, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 8, 83)) >ElChildren : Symbol(ElChildren, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 0, 0), Decl(conditionalTypeRelaxingConstraintAssignability.ts, 2, 20)) +// Repro from #31766 + +interface I { a: string } +>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27)) +>a : Symbol(I.a, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 13)) + +type DeepPartial = +>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17)) + + T extends object ? {[K in keyof T]?: DeepPartial} : T; +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17)) +>K : Symbol(K, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 25)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17)) +>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17)) +>K : Symbol(K, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 25)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 28, 17)) + +declare function f(t: T, partial: DeepPartial): T; +>f : Symbol(f, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 64)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19)) +>t : Symbol(t, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 22)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19)) +>partial : Symbol(partial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 27)) +>DeepPartial : Symbol(DeepPartial, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 26, 25)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19)) +>T : Symbol(T, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 19)) + +function g(p1: I, p2: Partial): I { +>g : Symbol(g, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 31, 56)) +>p1 : Symbol(p1, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 11)) +>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27)) +>p2 : Symbol(p2, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 17)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27)) +>I : Symbol(I, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 22, 27)) + + return f(p1, p2); +>f : Symbol(f, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 29, 64)) +>p1 : Symbol(p1, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 11)) +>p2 : Symbol(p2, Decl(conditionalTypeRelaxingConstraintAssignability.ts, 33, 17)) +} + diff --git a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.types b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.types index c827e26bddb99..2df682290f52c 100644 --- a/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.types +++ b/tests/baselines/reference/conditionalTypeRelaxingConstraintAssignability.types @@ -62,3 +62,30 @@ new Elem('' as ElChildren); // error >'' as ElChildren : ElChildren >'' : "" +// Repro from #31766 + +interface I { a: string } +>a : string + +type DeepPartial = +>DeepPartial : DeepPartial + + T extends object ? {[K in keyof T]?: DeepPartial} : T; + +declare function f(t: T, partial: DeepPartial): T; +>f : (t: T, partial: DeepPartial) => T +>t : T +>partial : DeepPartial + +function g(p1: I, p2: Partial): I { +>g : (p1: I, p2: Partial) => I +>p1 : I +>p2 : Partial + + return f(p1, p2); +>f(p1, p2) : I +>f : (t: T, partial: DeepPartial) => T +>p1 : I +>p2 : Partial +} + diff --git a/tests/cases/compiler/conditionalTypeRelaxingConstraintAssignability.ts b/tests/cases/compiler/conditionalTypeRelaxingConstraintAssignability.ts index 2f18e464e3b1d..04eaee9b5114d 100644 --- a/tests/cases/compiler/conditionalTypeRelaxingConstraintAssignability.ts +++ b/tests/cases/compiler/conditionalTypeRelaxingConstraintAssignability.ts @@ -21,4 +21,17 @@ export class Elem< new Elem(undefined as ElChildren.Void); new Elem('' as ElChildren.Text); new Elem('' as ElChildren.Void | ElChildren.Text); // error -new Elem('' as ElChildren); // error \ No newline at end of file +new Elem('' as ElChildren); // error + +// Repro from #31766 + +interface I { a: string } + +type DeepPartial = + T extends object ? {[K in keyof T]?: DeepPartial} : T; + +declare function f(t: T, partial: DeepPartial): T; + +function g(p1: I, p2: Partial): I { + return f(p1, p2); +}