Conversation

ahejlsberg

This PR improves type inference involving source and target sides that are both union or intersection types. When inferring from a type S to a type T, if S and T are both union types or both intersection types, we first reduce S and T by removing constituents that are matched by a constituent in the other type. For example, when inferring from string | string[] to string | T, we reduce the types to string[] and T, thus inferring string[] for T.

An example:

type Maybe<T> = T | void;

function isDefined<T>(x: Maybe<T>): x is T {
    return x !== undefined && x !== null;
}

function isUndefined<T>(x: Maybe<T>): x is void {
    return x === undefined || x === null;
}

function getOrElse<T>(x: Maybe<T>, defaultValue: T): T {
    return isDefined(x) ? x : defaultValue;
}

function test1(x: Maybe<string>) {
    let x1 = getOrElse(x, "Undefined");         // string
    let x2 = isDefined(x) ? x : "Undefined";    // string
    let x3 = isUndefined(x) ? "Undefined" : x;  // string
}

function test2(x: Maybe<number>) {
    let x1 = getOrElse(x, -1);         // number
    let x2 = isDefined(x) ? x : -1;    // number
    let x3 = isUndefined(x) ? -1 : x;  // number
}

Fixes #2264.
Fixes #4212.
Fixes #5417.
Fixes #5456.

@@ -6244,6 +6254,41 @@ namespace ts {
}
}

function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

return forEach(target.types, t => isTypeIdenticalTo(source, t) || undefined);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely an option, but not quite as efficient. I will keep what's there.

@sandersn

👍

}
}
if (modified) {
return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you break this into multiple lines?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wordwrapping man.

@mariusschulz

@ahejlsberg You've got a little typo in your code example: isUndefined<T> should use or rather than and, shouldn't it? Just trying to avoid confusion here.

function isUndefined<T>(x: Maybe<T>): x is void {
    return x === undefined || x === null;
}

@ahejlsberg

@mariusschulz Yes indeed, thanks for catching that.

@ENikS

Will this address issue mentioned in #3215 ?

@ahejlsberg

@ENikS No, the two are unrelated.

@masaeedu

@ahejlsberg Would inferring T more accurately in the following scenario be worthwhile:

interface A { a: any; }

function test<T>(arg: A & T): T { ... }

let result = test({a: "", b: 10});

The type of result is inferred as {}, although I would expect it to be inferred as {b: number}. Admittedly I don't have a very clear use case for this at the moment.

@ahejlsberg

@masaeedu We could potentially explore that, but it could get complex and would require type inference to manufacture new types, neither of which I'm crazy about. Specifically, in your example, we'd have to manufacture a new type { b: number } and infer that for T. I think it is desirable to have the property that type inference never infers types that don't already occur in your code.

@microsoftmicrosoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on . Already have an account? Sign in.
None yet
None yet

Successfully merging this pull request may close these issues.