#Why does this ParseInt2 type have no errors?

94 messages · Page 1 of 1 (latest)

gritty craneBOT
#
Bawdy Ink Slinger#9429

Preview:```ts
//in my code
type ParseInt<T extends text${number}> =
T extends any
? (T extends text${infer Digit extends number}
? Digit
: never)
: never

type Result = ParseInt<"text0" | "text1" | "text2" | "text3" | "text">
// ^?

//in my code
type ParseInt2<T> =
...```

oak remnant
#
//in my code
type ParseInt2<T> = // type ParseInt2<T extends `text${number}`> causes errors below
  T extends any
  ? (T extends `text${infer Digit extends number}`
    ? Digit
    : never)
  : never

type Result2 = ParseInt2<"text0" | "text1" | "text2" | "text3" | "text">
#

In my mind, I feel like it should error either way?

#

oh wait... are both of these always going to return true? ts T extends any ? (T extends `text${infer Digit extends number}` ...

outer yarrow
#

T extends any is always true

oak remnant
#

Maybe this is what @high bobcat mentioned earlier re: the type system can't protect you in these situations

outer yarrow
#

you probably don't want any there

#

that's a distributive conditional

oak remnant
outer yarrow
# outer yarrow that's a distributive conditional

ParseInt2<"text0" | "text1" | "text2" | "text3" | "text"> is equivalent to
ParseInt2<"text0"> | ParseInt2<"text1"> | ParseInt2<"text2"> | ParseInt2<"text3"> | ParseInt2<"text">
and then since you take Digit, that becomes
0 | 1 | 2 | 3 | never
since never is basically a 0-member union, that's equivalent to 0 | 1 | 2 | 3

#

!hb distributive conditional

gritty craneBOT
oak remnant
#

oh damn

#

right right right

outer yarrow
#

the T extends any part is useless, you can just remove that part
what behavior do you want with the rest?

oak remnant
#

That's really difficult for me to memorize; I feel like never gives compilation errors in very similar contexts

oak remnant
outer yarrow
#

which part specifically?

oak remnant
# outer yarrow which part specifically?

The difference in the signature between these two:


//in my code
type ParseInt<T extends `text${number}`> =
  T extends any
  ? (T extends `text${infer Digit extends number}`
    ? Digit
    : never)
  : never

type Result = ParseInt<"text0" | "text1" | "text2" | "text3" | "text">
//   ^?


//in my code
type ParseInt2<T> =
  T extends any
  ? (T extends `text${infer Digit extends number}`
    ? Digit
    : never)
  : never

type Result2 = ParseInt2<"text0" | "text1" | "text2" | "text3" | "text">
//   ^?
#

specifically, why the latter doesn't error, but you've explained why

outer yarrow
#

im a bit confused, those don't seem related. did you copy the wrong link?

oak remnant
oak remnant
outer yarrow
#

was confused about how this ParseInt conditional thing was related to the ranges question you linked

oak remnant
#

uh...

#

oops my bad

#

I copied the wrong link

#

There was a comment in that answer that took me to the right one

#

This might sound like a dumb question but, how can one tell when conditional types that return never are for Distributive Conditional Type uses and when they're for type checking?

#

Because, correct me if I'm wrong, there are other conditional types that return never and those never's cause compilation errors, right?

outer yarrow
#

conditionals as a whole treat never as like, an "invalid branch", to be ignored

oak remnant
#

oh, always ignored?

#

so that's very different in non-conditionals, right?

outer yarrow
#

well, the result should be ignored, because it comes from something invalid

#

never itself is still the same but it has a clearer, more specific meaning from a conditional

outer yarrow
outer yarrow
# gritty crane

conditionals are distributive by default, as stated here conditionals can be explicitly made not distributive by wrapping the condition operands in []

#

think of the distributive conditionals as being a mapping, of sorts

oak remnant
#

"conditionals are distributive by default" ohhhh, I didn't realize that

#

I thought it depended on whether unions were involved or not

outer yarrow
#

never adds a kind of filtering element, because distributive conditionals are unioned back together to get the result
as mentioned before never is a kind of 0-element union, so it just disappears when unioned with something else
for example:

oak remnant
#

right, I gotcha

#

I read a long article on that

gritty craneBOT
#
type PrefixOnlyNumbers<N> = N extends number ? `num:${N}` : never;
type X = PrefixOnlyNumbers<"a" | 0 | 1 | "b" | {}>;
//   ^? - type X = "num:0" | "num:1"
oak remnant
outer yarrow
oak remnant
#

I think I get all the pieces but I still need to experiment some more to understand them holistically, But this has really helped

#

Let me try something to see if I still get it

#

bah, I've confused myself, sorry. I think I have to sleep on this unfortunately. But I can tell I'm starting to get it

outer yarrow
#

good night & good luck

#

you can leave this open if you want to reuse it or you could close this and make a new thread for a fresh start

oak remnant
#

no wait here we go:

#

intuitively I know this is a different scenario

#

but in my state of mind, I see a conditional where never means something very different

#

Or at least does something different

#

Is the key difference here that one is for a type and the other is for a function, respectively?

outer yarrow
#

the conditional here always evaluates to T

oak remnant
#

oh!

#

Okay so it's completely consistent with what you were saying

#

I think I could've got that on my own if the // ^? evaluated to number

outer yarrow
#

T extends number is a given constraint, so T extends number ? T : never always evaluates to T
i believe it's trying to infer from there just like how it'd work without the conditional
so it'd try to infer a T extends number, but string doesn't fit that constraint

oak remnant
#

but that error message is a big clue

outer yarrow
#

never could be an intentional restriction to never have a possible value, it could be an excluded branch in a conditional, it could be the result of trying to fulfill incompatible types at once

#

all depends on context

#

hope that makes sense

outer yarrow
#

ok i have no idea what's actually going on here anymore

#

i know abstractly why it doesn't work but i have no idea about the process lol

oak remnant
outer yarrow
#

i'd guess it probably tries to infer T first

#

T becomes string, T does not extends number, t becomes never, T does not extends never, error
something like that maybe

oak remnant
#

t: T extends number ? T : never doesn't this return isn't this type a union of T | never? A union of T | never becomes T, and T extends number

outer yarrow
#

ah yeah the possible results becomes T, so it can attempt to infer maybe?

#

then it can actually eval the conditional

oak remnant
#

I don't know... the only reason I could even say that is because you taught me a few messages back

outer yarrow
#

needs some testing hold on

oak remnant
#

let me give you a link for your convenience

gritty craneBOT
#
Bawdy Ink Slinger#9429

Preview:ts function test<T>( t: T extends number ? T : never ): void { ...

outer yarrow
#

im not the greatest with function generic inferment tbh, so still not sure about that

#

idk who might be really experienced with that, sorry

oak remnant
#

I think I'm too tired to do it right now, but if I were going to experiment, I would make T (in the signature) extend a simple object instead, and then see what happens if I actually pass in something that extends it rather than is it.

#

Although, I wouldn't expect that to make a difference

#

anyway, good niight and thanks again for the help

vestal orchid
oak remnant
vestal orchid
#

in a distributive T extends U, T must be a naked generic type parameter, which means it must come from a type parameter, and it has to be a single identifier

oak remnant
#

just to clarify

vestal orchid
#

i dont know what "T but not X<T>" is supposed to mean

oak remnant
#

ah