Skip to content

Type inference for conditional expands [A | B] to [A] | [B] #29535

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

Closed
samiskin opened this issue Jan 23, 2019 · 7 comments
Closed

Type inference for conditional expands [A | B] to [A] | [B] #29535

samiskin opened this issue Jan 23, 2019 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@samiskin
Copy link

samiskin commented Jan 23, 2019

TypeScript Version: 3.3.0-dev.20190119

Search Terms: generic parameter type inference union conditional

Code

type ArrayOf<T> = T extends void ? [] : [T];
const test: ArrayOf<number | string> = [1, 'hello'];

Expected behavior:
No type errors, the array should take [number | string]
Actual behavior:
Type error due to it thinking the type is [number] | [string]:

error TS2322: Type '[number, string]' is not assignable to type '[string] | [number]'.
  Type '[number, string]' is not assignable to type '[number]'.
    Types of property 'length' are incompatible.
      Type '2' is not assignable to type '1'.

2 const test: ArrayOf<number | string> = [1, 'hello'];
        ~~~~

Found 1 error.

Playground Link: http://www.typescriptlang.org/play/#src=type%20ArrayOf%3CT%3E%20%3D%20T%20extends%20void%20%3F%20%5B%5D%20%3A%20%5BT%5D%3B%0D%0Aconst%20test%3A%20ArrayOf%3Cnumber%20%7C%20string%3E%20%3D%20%5B1%2C%20'hello'%5D%3B

Related Issues: Didn't find any that involved conditionals like my example

@DanielRosenwasser DanielRosenwasser added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 23, 2019
@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jan 23, 2019

Conditional types are typically distributive on their checked types. Try [T] extends [void] ? [] : [T] which avoids the issue since the checked type can no longer be instantiated as a union.

@samiskin
Copy link
Author

Thanks for the workaround!

If you have time could you elaborate on why this is "working as intended" as opposed to a bug? Is it simply that even though distributing the checked type results in a misleading type ([T] where T = A | B resulting in [A] | [B]), this is simply how we define the conditional operator and we accept that? Or is there something actually incorrect about not doing the distributing

@jack-williams
Copy link
Collaborator

From #21316

The distributive property of conditional types can conveniently be used to filter union types

Without distribution baked in it's impossible to get this generic filtering behaviour that is very useful, but you can always recover "normal" conditional types using the trick from @DanielRosenwasser.

@DanielRosenwasser
Copy link
Member

Without distribution baked in it's impossible to get this generic filtering behaviour that is very useful

Well we could have introduced a distribution operator, but the counter-argument was that it generally works as most users intend with the current semantics. Otherwise, users would frequently forget to distribute their types when writing conditionals.

@samiskin samiskin changed the title Type inference for conditional default parameter expands [A | B] to [A] | [B] Type inference for conditional expands [A | B] to [A] | [B] Jan 23, 2019
@samiskin
Copy link
Author

samiskin commented Jan 23, 2019

I updated the example with a much smaller one since looks like the source is conditionals

type ArrayOf<T> = T extends void ? [] : [T];
const test: ArrayOf<number | string> = [1, 'hello'];

I don't know if this makes sense, but would it be possible to have distribution work based on expanding from the right side and not expanding out of constructs where it doesn't make sense? ([A | B] becoming [A] | [B])

@jack-williams
Copy link
Collaborator

jack-williams commented Jan 25, 2019

@samiskin There would be some contexts where that does make sense, but others where it would not. The problem is that TypeScript really needs an approach that works well across the board because it cannot predict every use case. The beauty of conditional types, as they are now, is that they parametrically distribute over unions without making to many assumptions about the types. Being parametric provides future-compatibility, which is a nice thing to have!

I'm not sure I understood your question entirely:

would it be possible to have distribution work based on expanding from the right side and not expanding out of constructs where it doesn't make sense

Could you provide some examples with and without expansion?

@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