#Conditional type to return next element in array always evaluates to `never`. Why?

109 messages · Page 1 of 1 (latest)

dry haven
#

!ts

#

I'm really sorry because I get the sense that I've already been taught the lesson here, but I can't figure out why Next<AllVersions, I> always seems to be never

#

!ts

summer roostBOT
#
export const versionOrder = [
  1, 2
] as const;

export type AllVersions = typeof versionOrder;
export type Version = AllVersions[number];

export type Next<List extends readonly unknown[], Item> = List extends [
  infer First,
  infer Second,
  ...infer Rest
]
  ? First extends Item
    ? Second
    : Next<[Second, ...Rest], Item>
  : never;

declare function getNextVersionBack<I extends Version>(
//         ^? - function getNextVersionBack<I extends 1 | 2>(version: I): Next<AllVersions, I>
  version: I
//         ^? - (type parameter) I in getNextVersionBack<I extends 1 | 2>(version: I): never
): Next<AllVersions, I>;
// ^? - type Next<List extends readonly unknown[], Item> = List extends [infer First, infer Second, ...infer Rest] ? First extends Item ? Second : Next<[Second, ...Rest], Item> : never

const a = getNextVersionBack(1) satisfies 1;
//    ^? - const a: never
console.log(a);
const b = getNextVersionBack(2); // I was expecting an error?
//    ^? - const b: never
console.log(b);
dry haven
#

If I change the top three lines to this, it works as expected:

#

!ts

summer roostBOT
#
type AllVersions = [1, 2, 3];
//   ^? - type AllVersions = [1, 2, 3]
type Version = AllVersions[number];
//   ^? - type Version = 3 | 1 | 2
dry haven
#

I don't see the significance of this difference

#

!helper

lilac fulcrum
#

@dry haven AllVersions is readonly, but your constraint in the conditional type is not

dry haven
#

your constraint in the conditional type
What part of it are you referring to?

#

I don't think you mean this: List extends readonly unknown[]

#

oh you mean this!

List extends *readonly* [
  infer First,
  infer Second,
  ...infer Rest
]
#

omg, I spent so much time on this.

#

is there any way of making it not care about readonly?

#

(googling)

#

I couldn't find anything but I'm really tired so I'll look into it tomorrow. Thank you so much

lilac fulcrum
#

conceptually, readonly means "we don't know if you can write to it"

#

for arrays specifically it means it's missing the methods that mutate the array

dry haven
#

That seems really strange to me.isn't it reasonable to want this type to work regardless of whether or not the array is mutable?

lilac fulcrum
#

oh

#

yeah in that case, just always put readonly for array constraints

dry haven
#

I just figured it out

#

Yep. Okay thanks

lilac fulcrum
dry haven
#

admittedly I am exhausted trying to figure this out and this may come off as a little whiny, but I feel like I'm running into problems like this twice a day and if I could only get a little bit more type information from the editor I could figure these things out on my own

#

Am I just not understanding how to read the error messages?

#

To me they did not feel very informative

#

And this is unrelated, but I don't understand why this compiles:

#

Actually I think I can figure that out. Finally I remember reading something

#

The never type is a subtype of, and assignable to, every type
Man that's terse... I think that means that 2 can be assigned to never which is why this compiles.

#

I think I got it backwards:

declare let x: never;
const y: number = x satisfies number; // compiles

#

That... doesn't seem like desired behavior here, but I have a feeling it's important in another contexts

lilac fulcrum
#

yeah

lilac fulcrum
#

so it's perfectly fine (and if anything, it is desired behavior)

#

if it isn't then maybe never is the wrong type to be returning

#

note that if a function's return type is never that means it never returns

#

so either an infinite loop, or it throws an error

dry haven
#

But isn't the point of the satisfies keyword to say, "this is what I'm expecting so let me know if I'm wrong"?

lilac fulcrum
dry haven
#

And I get that, technically, it isn't wrong because of this "The never type is a subtype of, and assignable to, every type," but it seems like there's no way to catch human error like this

lilac fulcrum
#

my favorite is [never, "error: descriptive error message here"]

dry haven
#

hah. I was trying really hard not to but I couldn't figure out why was

lilac fulcrum
#

a custom declare const UNRETURNABLE: unique symbol would also work

#

basically, the one situation never is acceptable is when you know for sure that branch will never ever happen

#

usually you do that with a combination of generic parameter constraints (<T extends Constraint>) and carefully chosen conditional types (e.g. T extends [infer U extends Constraint[number]])

dry haven
lilac fulcrum
#

nope, never is always the same type everywhere

#

any is the one that means different things in different places

#

but even then it's not that it conceptually means something different, depending on how you think about it

#

(in general, any is "do whatever suppresses the most type errors")

lilac fulcrum
lilac fulcrum
dry haven
#

Like that simple example seemed relevant in the conditional discriminator types (I can't remember the specifics, but I do remember that never filters out union members, if that's the right terminology)

lilac fulcrum
#

!hb dist

summer roostBOT
lilac fulcrum
#

if you mean when doing this

dry haven
#

Yeah that's exactly what I meant, hah

lilac fulcrum
#

yeah - a common (?!) way to think about it is (because it contains 0 members), it's equivalent to a union with 0 members

dry haven
#

From my ignorant position, it felt like never was doing something different than normal here

lilac fulcrum
#

so you're replacing the union with 1 member (aka a non-union) with the union with 0 members, effectively removing it

dry haven
#

that sort of makes sense but don't hate me if I forget it tomorrow. I've been programminng for a long time but this feels like when I first started; I expect an epiphany where it will all make sense at once

dry haven
#

Any advice on this? When it comes to non-trivial type signatures, I don't feel like I get much troubleshooting information. e.g., that function was always returning never, but I still have no idea how to figure out I missed that second readonly besides memorizing it as something that could cause a problem

dry haven
#

Anyway, thanks for the help! Good night

dry haven
#

I'm going to summarize this problem so helpers don't need to read the whole thing:

#

This always returns never:```ts
export const versionOrder = [
1, 2
] as const;

export type AllVersions = typeof versionOrder;
export type Version = AllVersions[number];

export type Next<List extends readonly unknown[], Item> = List extends [
infer First,
infer Second,
...infer Rest
]
? First extends Item
? Second
: Next<[Second, ...Rest], Item>
: never;

declare function getNextVersionBack<I extends Version>(
// ^? - function getNextVersionBack<I extends 1 | 2>(version: I): Next<AllVersions, I>
version: I
// ^? - (type parameter) I in getNextVersionBack<I extends 1 | 2>(version: I): never
): Next<AllVersions, I>;
// ^? - type Next<List extends readonly unknown[], Item> = List extends [infer First, infer Second, ...infer Rest] ? First extends Item ? Second : Next<[Second, ...Rest], Item> : never

const a = getNextVersionBack(1) satisfies 1;
// ^? - const a: never // Why isn't it const a: 1; ???
console.log(a);

dry haven
#

I feel like I'm running into problems like this twice a day and if I could only get a little bit more type information from the editor I could figure these things out on my own. Am I just not understanding how to read the error messages? As someone still learning conditional types, how could I have figured this out without asking for help?

#

!helper

wicked cobalt
#

i would say typescript has a steep learning curve if you're beginning to wade in the deep end; it's pretty hard at first and i remember struggling to grasp the concept of conditionals/distribution, infer, etc

dry haven
#

wouldn't it be better to show me the union of the right hand side? Does this exist if I hover over something else?

wicked cobalt
#

you could hover over X in type X = TwineVariables<Version>

dry haven
#

ahhhh

wicked cobalt
#

wouldn't it be better to show me the union of the right hand side?
how is it supposed to do that if T has not been instantiated yet

dry haven
#

Okay these are the types of things I'm looking for. Now that I know that, I might be able to gather enough information so that I don't need to ask others

dry haven
#

Okay now that I know that, I think that'll really help me. Thank you @wicked cobalt

dry haven
#

Maybe that would've been enough, had I have known

#

since that might be the case, maybe I'll wait until I get to a point where I'm stuck again before I ask such a question

#

Thank you

#

!resolved

wicked cobalt
#

readonly things aren't assignable to mutable things

#

so check if List extends readonly [...] instead

#

both mutable and readonly are assignable to readonly, so there's really no downside

#

(as const makes it readonly)

dry haven
#

Yeah that part I get now. By the way, what's the value of allowing me to make this mistake?

wicked cobalt
dry haven
# wicked cobalt hm? you have readonly here though

Yes sorry that must be confusing: I already know the solution now, but I couldn't have figured it out without somebody helping me. Now my question is related but not the same: how can I learn enough to be able to troubleshoot problems like this without asking others for help

dry haven
wicked cobalt
#

i learned purely from experience

#

so im definitely not qualified in giving resources

#

hell i dont even remember what it was like

#

if you ask me just keep at it and you'll learn ¯_(ツ)_/¯

dry haven
#

Thank you