5

In the following code you notice that the type of Result1 is never, yet the type of test3 is []. I can't make sense of this. Why are the results not the same, considering that they are both reading the never type from MyEvents?

type EventArgs<EventTypes, K extends keyof EventTypes> =
    EventTypes[K] extends never /* CHECK NEVER */
        ? []
        : EventTypes[K] extends any[]
            ? EventTypes[K]
            : [EventTypes[K]];

type foo<T> = T extends never /* CHECK NEVER */ ? [] : [boolean]
type Result1 = foo<MyEvents['anotherEvent']> // HERE, type is `never`

type MyEvents = {
    anotherEvent: never // event has no args
}

type Result2 = EventArgs<MyEvents, 'anotherEvent'> // HERE, type is `[]`

playground link

trusktr
  • 44,284
  • 53
  • 191
  • 263
  • What is your original goal, aka original problem? Without it it's hard to understand what you're trying to achieve. – Nurbol Alpysbayev Dec 31 '18 at 07:23
  • @NurbolAlpysbayev Hi, I had a typo, I fixed the question so that `foo` is checking `never` in the conditional. Notice that the result type in `Result1` is `never`, yet the result in `Result2` is `[]`. Just curious why this is? Because it seems like they both land on `[]` in the conditional check. – trusktr Dec 31 '18 at 07:26
  • It seems to me that you are using `never` type completely wrong. Do you want by `never` to indicate that there are no items? If yes it should be done by `[]` or `null`. `never` is not for that. And I hardly can imagine that something can extend `never`. – Nurbol Alpysbayev Dec 31 '18 at 07:26
  • Just write your original goal and we'll sort it out easily, I think. – Nurbol Alpysbayev Dec 31 '18 at 07:29
  • @NurbolAlpysbayev It's because I am doing this (https://stackoverflow.com/questions/53568797) and instead of using `undefined` I thought I would try `never` to see what happens, and found that the result is inconsistent, so I'm just trying to understand why. In practice, I think `undefined` is fine for defining what the args of the event in that other question should be, because if I actually wanted to define an arg of type undefined that wouldn't make much sense in the runtime JS code anyways. – trusktr Dec 31 '18 at 07:33
  • For reference, here's what I'm doing in practice so far: https://github.com/trusktr/events-typed (uses undefined) – trusktr Dec 31 '18 at 07:33

1 Answers1

15

What you really are asking is:

type Foo = never extends never ? true : false // gives true
//but
type Bar<T> = T extends never ? true : false
type Baz = Bar<never> // not `true` as expected but `never`!

Well, I became curious on this and wondered if it has something to do with distributive conditional types.

So I changed the above code to this:

type Bar<T> = [T] extends [never] ? true : false
type Baz = Bar<never> // true as expected

Hence the answer is: You are distributing an empty union aka never and this gives a result of the distribution of an empty union (aka never): that is another empty union! Completely makes sense!

UPD: why never is an "empty union"? Well maybe this code will demonstrate it:

type Union1 = number | string | never // number | string
type Union2 = number | never // number
type Union3 = never // never aka empty union
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • Do you mind if I also provide an answer to this ? The reason is in the PR introducing `never` https://github.com/Microsoft/TypeScript/pull/8652 – Titian Cernicova-Dragomir Dec 31 '18 at 07:49
  • @TitianCernicova-Dragomir Hi Titian, I am glad that you even asked, but I am always happy to see your answers, because they always add some good value! So of course, yes! In the meantime I am adding some examples of `never`, of how I use it personally. – Nurbol Alpysbayev Dec 31 '18 at 07:51
  • @TitianCernicova-Dragomir and Nurbol, in my case `MyEvents` is never used at runtime. I am only using it to define a map of event names to event payload types, as in the other question you answered Titian: https://stackoverflow.com/a/53569010/454780. Still curious why one actually gives me the `[]` type in the true part of the conditional, while the other does not land on either the true or false part of the conditional, and both *seem* to be checking `never`. – trusktr Dec 31 '18 at 08:05
  • @trusktr I've found out what's the reason of that, check the addition to my answer! – Nurbol Alpysbayev Dec 31 '18 at 08:16
  • @NurbolAlpysbayev Yeah I think your answer makes sense :) – Titian Cernicova-Dragomir Dec 31 '18 at 08:24
  • 1
    @TitianCernicova-Dragomir Hey, why you've deleted your answer? ) I guess that means that you consider mine better? Cool then! :D But I am sorry about that) – Nurbol Alpysbayev Dec 31 '18 at 08:25
  • @TitianCernicova-Dragomir and Nurbol, it's hard to understand, but interesting that I can hack it making both side of the extends a tuple. Also doing the opposite check `never extends T` is quite nice too and make it work! Good to know there's a way to make it work, though this means that in the event emitter example of the other SO question that if the check uses `never extends EventTypes[K]` that then providing a type of `undefined` for an event payload or event handler argument would just not make any sense. Or would it? Maybe that's for another question. – trusktr Dec 31 '18 at 10:01
  • 1
    @trusktr It's hard to understand until you carefully read the link (in the answer) about DCT and [this](https://stackoverflow.com/questions/51651499/typescript-what-is-a-naked-type-parameter) and do some practice or play with few examples. In turn, those eventEmitter questions are hard for me, because I events generally hard for me (and I dislike this pattern), and referencing to other questions is making it harder. My suggestion is to close this question (i.e. consider accepting my answer if it helped or after you understand it) and to move on to the next little question. – Nurbol Alpysbayev Dec 31 '18 at 10:12
  • 1
    @trusktr `hack it making both side of the extends a tuple` it's totally not a hack or bug, it's a feature (naked type parameter vs "clothed" see the link in the previous comment). But yes it looked like a hack for me too when I have been learning TS. – Nurbol Alpysbayev Dec 31 '18 at 10:18
  • Quick comment, `never extends T` won't work because that is *always* true, whether `T=never` or `T=string` etc. The only solution is to check `[T] extends [never]`. Brilliant answer! It's frustrating that this question has poor SEO visibility, I only found it after starting to write my own question along the same lines! – Ahmed Fasih Jun 09 '20 at 19:13
  • You can *also* do `Extract extends never ? true : false`. Extract results in `T` if it's non-`never` and `never` otherwise, and `never extends never` works as expected. See [Extract](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types) docs. – Ahmed Fasih Jun 09 '20 at 20:10