#Why doesn't my discriminator narrow my type?

1 messages ยท Page 1 of 1 (latest)

dark larkBOT
#
Bawdy Ink Slinger#9429

Preview:```ts
interface ConfigMoment<T extends AnyVersion> {
variables: Variable<T>
}

interface ConfigState<T extends AnyVersion> {
history: ConfigMoment<T>[]
}

interface TwineConfigSave<
T extends AnyVersion = AnyVersion

{
state: ConfigState<T>
version?: T
...```

small river
tame gorge
#

No in this case it's just because you aren't expanding the union

#

Using a distributive conditional fixes that:

dark larkBOT
#
angryzor#9490

Preview:```ts
interface ConfigMoment<T extends AnyVersion> {
variables: Variable<T>
}

interface ConfigState<T extends AnyVersion> {
history: ConfigMoment<T>[]
}

type TwineConfigSave<
T extends AnyVersion = AnyVersion

= T extends T
? {
state: ConfigState<T>
version?: T
}
...```

tame gorge
#

Here I put it in the TwineConfigSave type directly but you could also use an intermediary type:

dark larkBOT
#
angryzor#9490

Preview:```ts
interface ConfigMoment<T extends AnyVersion> {
variables: Variable<T>
}

interface ConfigState<T extends AnyVersion> {
history: ConfigMoment<T>[]
}

interface TwineConfigSave<
T extends AnyVersion = AnyVersion

{
state: ConfigState<T>
version?: T
...```

tame gorge
#

@small river

small river
# tame gorge <@311705916959490049>

you aren't expanding the union

  1. Can you elaborate on what this means?

  2. I see the difference but I don't understand why you had to do that. Is there a place in the handbook that explains?

  3. Before you answered I read the documentation and thought the reason it wasn't working was because:

When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can narrow out the members of the union.

source: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions

I thought the issue was one of these things I've bolded:

I don't have a type AllSaves = a | b | c anywhere.

And my version numbers aren't literal. But maybe since I'm using an as const array for the version numbers, it counts as literal types?

#

!helper

rocky plank
#

you never responded to anyone? Can you elaborate?

small river
rocky plank
#

yes, you're the one who pinged !helper

#

what do you still need help with

small river
#

Are those not visible?

flat creek
#

you don't have a discriminated union, because you don't have a union
TwineConfigSave would need to be a discriminated union in order to have a discriminant, like in angryzor's playgrounds
there, it's a union due to the type being distributive, due to the conditional
distributive conditionals act like this: T<A | B> = T<A> | T<B>
since AnyVersion is a union, this also makes TwineConfigSave or TwineConfigSaves in angryzor's playgrounds unions

#

your version numbers are literal due to as const, yes

#

note how the final type is a type rather than an interface in angryzor's examples. interfaces can't be unions, so they also can't be discriminated unions

rocky plank
small river
#

you don't have a discriminated union, because you don't have a union
Okay that is what I thought too. i.e., "I don't have a type AllSaves = a | b | c anywhere."

small river
#

So is T extends T a hack to make it conditional so that it can be distributed distributive?

#

@flat creek ^

flat creek
#

yes

#

well not really a hack

#

but yes that's what it does

small river
flat creek
#

it's like the identity function but for conditionals, imo

small river
flat creek
#

yeah

tame gorge
small river
#

I don't know... To me that feels hacky. It feels like committing this code to production:

if (b === true) {
  return true;
} else {
  return false;
}
#

Although I can recognize how my example is purely pointless

tame gorge
#

basically whenever you use a conditional with a "naked type parameter" that contains a union it will expand the union and apply the true branch of the conditional for every element of the union

#

instead of treating the union as 1 thing

small river
#

the T extends T seems like a workaround to needing a conditional to get distribution

tame gorge
#

It's a very common pattern

#

you could also use T extends unknown

small river
tame gorge
#

in this case both are just trivially true

small river
#

Is there a difference between that and T extends any there?

tame gorge
#

every thing extends unknown, and T extends T is just a tautology

#

T extends any would also work but any is evil so I avoid it

flat creek
#

T extends any and any extends T are always true because any doesn't care about logic

small river
tame gorge
#

indeed, and for unknown only T extends unknown is true

#

and never extends T is also trivially true

#

unknown is the "top type", never is the "bottom type"

flat creek
#

shoot, have i been referring to them inversed

tame gorge
#

everything extends unknown, and never extends everything

small river
#

Although in my unfamiliar eyes, the word any documents the purpose of the T extends <anything> better

tame gorge
#

ok but that's just because of the english language

#

unknown is actually what any should always have been

small river
#

I'm sure once I become familiar with seeing everyone use T extends T, it won't seem strange to me anymore

tame gorge
#

instead any is a wildcard type, it extends everything and everything extends it

#

it is therefore not type safe

#

hence why I call it evil

small river
small river
tame gorge
#

Ah ok gotcha

#

Sorry for explaining something you already knew then ^^'

#

Anyway, you could also do this if you really don't like the conditional approach I guess:

type TwineConfigSaves<T extends AnyVersion = AnyVersion> = { [K in T]: TwineConfigSave<K> }[T]
#

But again it's just abusing a distributive mechanic in the typing system to expand that union

small river
#

Technically, isn't this the same result without the distributive mechanic?

tame gorge
#

How so?

small river
#

Which sentence are you asking about?

small river
#

I didn't know there were NONconditional distributive mechanics

tame gorge
#

Well, true I guess

small river
#

(I understand the code you are showing, it's the terms I'm fuzzy on)

tame gorge
#

In that last code it's more like explicit distribution through a mapped type

small river
#

Okay thank you so much

tame gorge
#

Yw!

small river
#

This is been really challenging for me. I feel like I tried to tackle something that requires knowledge of all the advanced typescript concepts at once

#

instead of one by one

tame gorge
#

Well, you're trying to do type level programming, and that has indeed kind of a steep learning curve.

#

Because you often can't just use 1 TLP concept at the time, you have to use them all at once

small river
#

Yeah :T

tame gorge
#

But you're making good progress, this is quite advanced stuff!

#

Good luck with your project ๐Ÿ™‚

small river
#

!resolved

#

no wait, I may have just figure that out.