#Inference sometimes fails with Narrow type

25 messages · Page 1 of 1 (latest)

barren kernelBOT
#
Daniell#4062

Preview:```ts
...
type _Narrow<T, U> = [U] extends [T]
? U
: Extract<T, U>

export type Narrow<T = unknown> =
| _Narrow<T, 0 | (number & {})>
| _Narrow<T, 0n | (bigint & {})>
| _Narrow<T, "" | (string & {})>
| _Narrow<T, boolean>
| _Narrow<T, symbol>
| (T extends object
? {[K in keyof T]: Narrow<T[K]>}
: never)
...```

#
T6#2591

Preview:ts // @ts-ignore import {} from "discord-api-types" import { ApplicationCommandOptionData, ApplicationCommandOptionType, User, Channel, Role, Attachment, ApplicationCommandType, ChatInputCommandInteraction, ApplicationCommandData, Awaitable, ...

wraith tusk
#

The issue was... a couple things, but not the narrow type

#

The = never was causing problems

#

And the intersection with ApplicationCommandData was having problems

#

Hence the Omit "options"

zinc monolith
#

Ahh thanks.. still confused how that narrow type works though, how can it ever pass the other cases besides object?

#

Because isn't almost everything object

wraith tusk
#

The way Narrow is able to infer literal types is because of this quirk:

#
let x = { a: 1 } satisfies { a: number }
let y = { a: 1 } satisfies { a: number & {} | 0 }
#

!ts x y

barren kernelBOT
#
let x: {
    a: number;
} /* 1:5 */``````ts
let y: {
    a: 1;
} /* 2:5 */```
wraith tusk
#

In y, because number & {} | 0 includes a literal number, it tries to infer 1 as a literal number, to see if it matches

#

Even though it doesn't match 0, it still matches number & {}

#

So the literal type is retained

#

The & {} on number prevents the union from being collapsed

#

If you had number | 0, it would just become number, and it wouldn't work

#

The issue is that if you have

type Homomorphic<T> = { [K in keyof T]: T[K] }
type X = Homomorphic<number>
#

!ts X

barren kernelBOT
#
type X = number /* 2:6 */```
wraith tusk
#

If you call a homomorphic mapped type on something like number, it returns it verbatim

#

Which means that Narrow<number>, without the T extends object, would be number & {} | 0 | number, which collapses to number | number & {}, and doesn't infer a literal

#

Since number doesn't extend object, wrapping the homomorphic mapped type with T extends object prevents that from happening

zinc monolith
#

Thanks for the explanation, btw I used never to not include options in the resulting object when it's not passed 😅

#

It's fine though, it's not usable anyway