Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inference for contravariant positions in second argument of conditional type results in arbitrary union supertype #38039

Closed
reverofevil opened this issue Apr 18, 2020 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@reverofevil
Copy link

TypeScript Version: 3.8.3 (Nightly is broken at the moment)

Search Terms:
distributive conditional types over intersection

Code

type X = (
    ((x: { a: 1 }) => 0) & ((x: { b: 2 }) => 0)
) extends (a: infer T) => any ? T : never;
type Y = (
    ((x: { b: 2 }) => 0) & ((x: { a: 1 }) => 0)
) extends (a: infer T) => any ? T : never;
type XY = X extends Y ? Y extends X ? true : false : false;

type A = (
    ((x: { a: 1 }) => 0) | ((x: { b: 2 }) => 0)
) extends (a: infer T) => any ? T : never; 
type B = (
    ((x: { b: 2 }) => 0) | ((x: { a: 1 }) => 0)
) extends (a: infer T) => any ? T : never;
type AB = A extends B ? B extends A ? true : false : false;

Expected behavior:
XY and AB both true, i.e. condition types distribute over intersections the same way as over unions.

Actual behavior:
XY is false. Type checker picks the last term of intersection.

Playground Link:
Playground Link.

Related Issues:
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types

@jack-williams
Copy link
Collaborator

Intersections of functions behave like overloaded functions, and the behaviour for a conditional type inferring from an overloaded function is to use the last signature as described in #21496

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).

@reverofevil
Copy link
Author

reverofevil commented Apr 19, 2020

I would expect that if I used conditional on an intersection

type F = ((a: 1) => 0) & ((a: 2, b: 3) => 0) & ((a: 4, b?: 5, c?: 6) => 0)

types of arguments would be inferred either as

1 | 2 | 4
3 | 5 | undefined
6 | undefined
unknown

or

1 | 2 | 4
3 | 5
6
unknown

or

1 | 2 | 4
unknown
unknown
unknown

but surely not

4
5 | undefined
6 | undefined
unknown

that we're getting right now.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 20, 2020
@RyanCavanaugh
Copy link
Member

Re: the title, it would be plainly wrong to do this

type Pt = { x: number } & { y: number }
type IsPoint<T> = T extends { x: number, y: number } ? true : false;
// Expected true, not false & false
type X = IsPoint<Pt>;

@reverofevil
Copy link
Author

I'm not sure how to properly call it otherwise. The "distributive conditional types" term doesn't really cover that distributivity through contravariant positions converts unions to intersections (and in case of this issue, vice versa).

The example you've shown doesn't show distribution through contravariant positions.

@reverofevil reverofevil changed the title Conditional types do not distribute over intersection Inference for contravariant positions in second argument of conditional type results in arbitrary union supertype Apr 20, 2020
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants