#Conditional type to return next element in array always evaluates to `never`. Why?
109 messages · Page 1 of 1 (latest)
!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
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);
type AllVersions = [1, 2, 3];
// ^? - type AllVersions = [1, 2, 3]
type Version = AllVersions[number];
// ^? - type Version = 3 | 1 | 2
@dry haven AllVersions is readonly, but your constraint in the conditional type is not
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
nope
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
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?
(you can still read from mutable arrays, so normal arrays still count as readonly arrays - confusingly enough)
Yeah I read that earlier once I realized the difference above, but I needed you to tell me that I was missing the readonly in that second location
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 toneverwhich 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
or rather, never is assignable to 2
yeah
well, a value of type never cannot ever exist, by definition
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
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"?
yes, and all 0 of the values in the type never are also of type 2
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
the solution is simple: never return never
my favorite is [never, "error: descriptive error message here"]
hah. I was trying really hard not to but I couldn't figure out why was
i think void would be usable there
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]])
you mean as a return type, right? I thought they mean different things in different contexts, e.g., discriminator conditional types (or whatever they are called)
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")
yeah I started to notice this.
(this is about any, not never)
| never: "either T (the left side), or one of these 0 elements" - there are no values matching the 0 elements so it results in just T
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)
& never: T, but also one of these 0 elements at the same time
!hb dist
if you mean when doing this
Yeah that's exactly what I meant, hah
yeah - a common (?!) way to think about it is (because it contains 0 members), it's equivalent to a union with 0 members
From my ignorant position, it felt like never was doing something different than normal here
so you're replacing the union with 1 member (aka a non-union) with the union with 0 members, effectively removing it
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
Speaking of which, I feel like this is the biggest thing holding me back from the epiphany ^
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
Anyway, thanks for the help! Good night
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);
Thanks to @lilac fulcrum , I learned the solution is this:
export type Next<List extends readonly unknown[], Item> = List extends readonly [ // *readonly*
infer First,
infer Second,
...infer Rest
]
? First extends Item
? Second
: Next<[Second, ...Rest], Item>
: never;
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
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
That makes me feel a bit better, but on the other hand, I feel like it doesn't have to be as hard as it is. For example, I don't understand how this helps me:
wouldn't it be better to show me the union of the right hand side? Does this exist if I hover over something else?
you could hover over X in type X = TwineVariables<Version>
ahhhh
wouldn't it be better to show me the union of the right hand side?
how is it supposed to do that ifThas not been instantiated yet
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
In hindsight, that makes sense
Okay now that I know that, I think that'll really help me. Thank you @wicked cobalt
@wicked cobalt Is there anything like that I could've done with this specific issue? Using that tip, I don't know how to get more information than this:
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
List extends readonly [
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)
Yeah that part I get now. By the way, what's the value of allowing me to make this mistake?
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
an answer like this is exactly what I'm looking for
idrk what you want me to say
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 ¯_(ツ)_/¯
I'm saying you already did
Thank you