Skip to content

Commit

Permalink
Merge pull request #32208 from microsoft/fix31766
Browse files Browse the repository at this point in the history
Restore union-like behavior for inference to conditional types
  • Loading branch information
ahejlsberg authored Jul 2, 2019
2 parents 47856b3 + 8e75382 commit 345777e
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 30 deletions.
60 changes: 32 additions & 28 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15354,36 +15354,11 @@ namespace ts {
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
}
else if (target.flags & TypeFlags.Conditional && !contravariant) {
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
const targetTypes = [getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>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 (<UnionOrIntersectionType>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<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | 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 (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, t);
}
}
priority = savePriority;
}
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, !!(target.flags & TypeFlags.Intersection));
}
else if (source.flags & TypeFlags.Union) {
// Source is a union or intersection type, infer from each constituent type
Expand Down Expand Up @@ -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<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> =
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;

declare function f<T>(t: T, partial: DeepPartial<T>): T;

function g(p1: I, p2: Partial<I>): I {
return f(p1, p2);
}


//// [conditionalTypeRelaxingConstraintAssignability.js]
"use strict";
Expand All @@ -37,3 +51,6 @@ new Elem(undefined);
new Elem('');
new Elem(''); // error
new Elem(''); // error
function g(p1, p2) {
return f(p1, p2);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> =
>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[K]>} : 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: T, partial: DeepPartial<T>): 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>): 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))
}

Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,30 @@ new Elem('' as ElChildren); // error
>'' as ElChildren : ElChildren
>'' : ""

// Repro from #31766

interface I { a: string }
>a : string

type DeepPartial<T> =
>DeepPartial : DeepPartial<T>

T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;

declare function f<T>(t: T, partial: DeepPartial<T>): T;
>f : <T>(t: T, partial: DeepPartial<T>) => T
>t : T
>partial : DeepPartial<T>

function g(p1: I, p2: Partial<I>): I {
>g : (p1: I, p2: Partial<I>) => I
>p1 : I
>p2 : Partial<I>

return f(p1, p2);
>f(p1, p2) : I
>f : <T>(t: T, partial: DeepPartial<T>) => T
>p1 : I
>p2 : Partial<I>
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
new Elem('' as ElChildren); // error

// Repro from #31766

interface I { a: string }

type DeepPartial<T> =
T extends object ? {[K in keyof T]?: DeepPartial<T[K]>} : T;

declare function f<T>(t: T, partial: DeepPartial<T>): T;

function g(p1: I, p2: Partial<I>): I {
return f(p1, p2);
}

0 comments on commit 345777e

Please sign in to comment.