#Distributive object type infers union rather than specific value

137 messages ยท Page 1 of 1 (latest)

sinful wharfBOT
#
Daniell#4062

Preview:```ts
enum ApplicationCommandType {
ChatInput,
Message,
User,
}

type A = {
type?: ApplicationCommandType.ChatInput
}

type B = {
type: ApplicationCommandType.Message
}

type C = {
type: ApplicationCommandType.User
}

type SlashCommand = A

type ContextMenuCommand = B | C
...```

compact egret
#

How can I make this infer a specific type rather than a union?

sudden nimbus
#

typescript can't infer the types for some reason

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
T extends Extract<
SlashCommand | ContextMenuCommand,
{type?: A}

,
A extends ApplicationCommandType
(
command: Command<T> & {type: A}
) => command
...```

sudden nimbus
#

this works though

#

although it's a bit ugly

#

the idea is you have a separate generic parameter to force inference to work

sinful wharfBOT
#
Daniell#4062

Preview:```ts
enum ApplicationCommandType {
ChatInput,
Message,
User,
}

type A = {
type?: ApplicationCommandType.ChatInput
description: string
}

type B = {
type: ApplicationCommandType.Message
value: string
}

type C = {
type: ApplicationCommandType.User
...```

compact egret
#

I actually solved it by mapping over the enum instead, but there is just 1 more thing I'm trying to achieve which is accessing the rest of the object in Command, is that possible?

#

So basically I want to a conditional type on any value in ApplicationCommandData, I tried but when I make that generic it infers a union for the interaction again :/

#

My attempt was export const createCommand = <T extends ApplicationCommandData>(command: Command<T["type"]>

compact egret
#

So that I can do [interaction: ChatInputCommandInteraction, options: Something<TheApplicationCommandDataGeneric["description"]]> for example

sudden nimbus
#

uhm

#

that kinda sorta doesn't work

#

because Command depends on Interaction

#

which would also depend on Command with the feature you want

#

but also

#

why not manually make a union

compact egret
#

What would that union look like? I've been trying the whole day yesterday if you remember ๐Ÿ˜… I just want to infer the parameters of execute correctly while inferring the options array I had in here yesterday

#

Which I changed to description for sake of ease

sinful wharfBOT
sudden nimbus
#

!ts

sinful wharfBOT
#
enum ApplicationCommandType {
  ChatInput,
  Message,
  User
}

type A = {
  type?: ApplicationCommandType.ChatInput
  description: string
}

type B = {
  type: ApplicationCommandType.Message
  value: string
}

type C = {
  type: ApplicationCommandType.User
  value: string
}

type ChatInputCommandInteraction = string;
type ContextMenuCommandInteraction = number;

type Command<D extends string> = 
| { type: ApplicationCommandType.ChatInput; description: D; execute(interaction: ChatInputCommandInteraction, options: [D]): Promise<unknown>; }
| { type: ApplicationCommandType.Message | ApplicationCommandType.User; description: D; execute(interaction: ContextMenuCommandInteraction, options: [D]): Promise<unknown>; }

export const createCommand = <C extends Command<D>, D extends string = C["description"]>(
  command: C
) => command;

createCommand({
  type: ApplicationCommandType.ChatInput,
  description: "test",
  async execute(interaction, options) {
//                           ^? - (parameter) options: [string]
  }
})```
sudden nimbus
#

this is pretty nasty too tbh

#

but hey

#

if it works it works

#

plus, avoiding conditional types is always a plus

sinful wharfBOT
#
n_n#2622

Preview:ts ... export const createCommand = <const C extends Command<string>>( command: C ) => command; ...

sudden nimbus
#

alternatively, with ts 5.0 beta

#

wait no

#

broken

#

the type of options is wrong?

#

which is weird bc the type of description is correct

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
C extends Command<D>,
D extends string = C["description"]

(
command: C & {description: D}
) => command
...```

sudden nimbus
#

kinda ew

#

but it works

sudden nimbus
# sinful wharf

i've seen a bunch of questions that end up needing something like this lately

#

idk if it's just something about ts 4.9, or if everyone just needs something like this all of a sudden

compact egret
#

What also makes it hard is that not every union has a description :/ can I create a repo for you with the actual typings? Because I notice I'm just creating the ideal situation atm

sudden nimbus
#

sure

compact egret
#

So in case of ApplicationCommandType.ChatInput, I'd like execute: async (interaction, options) => {}, Where options is OptionsAsObject<ApplicationCommandData["options"]>

sudden nimbus
#

wait

compact egret
#

Options only exist in ChatInputApplicationCommandData

sudden nimbus
#

playground says ApplicationCommandOptionType isn't a thing

compact egret
#

Yea that's why I couldn't make an accurate playground

#

It doesn't resolve the latest typings

sinful wharfBOT
sudden nimbus
#

(repro, i haven't started looking at it yet)

compact egret
#

Ah lol

sudden nimbus
#

!:narrow

sinful wharfBOT
#
T6#2591

Preview:ts type _Narrow<T, U> = [U] extends [T] ? U : Extract<T, U> type Narrow<T = unknown> = | _Narrow<T, 0 | number & {}> | _Narrow<T, 0n | bigint & {}> | _Narrow<T, "" | string & {}> | _Narrow<T, boolean> | _Narrow<T, symbol> | _Narrow<T, []> | _Narrow<T, { [_: PropertyKey]: Narrow }> | (T extends object ? { [K in keyof T]: Narrow<T[K]> } : never) | Extract<{} | null | undefined, T> ...

#
n_n#2622

Preview:```ts
...
export const createCommand = <
C extends Command<OptionsAsObject<O>>,
O extends Narrow<Option[]>

(
command: C & {options: O}
) => command
...```

sudden nimbus
#

something like this maybe

#

@compact egret ^

compact egret
# sudden nimbus something like this maybe

I had something like this based on your answers from yesterday but if you change type to ApplicationCommandType.Message, it allows excess properties like options and complains about name which is actually required

#

And with ChatInput it errors when options is not present while it should be optional

#

In the original repo it does have correct behaviour when you play around with the type and removal of options / description for reference

sudden nimbus
#

ok yeah idk

#

honestly the easiest way is just to extract execute out to a separate parameter

#

and put it back in later

#

after all, the hard part is inferring the object type and making the same object more specific

compact egret
#

honestly the easiest way is just to extract execute out to a separate parameter
You mean having this method separate and typing it manually?

sudden nimbus
#

no

#

createCommand(command, execute)

#

then you can avoid all the complicated types

#

make the generic for Command like C extends Narrow<Command>

#

and execute would be ExecuteTypeFor<C>

compact egret
#

Where did the generic for Command go ๐Ÿค”

compact egret
sudden nimbus
#

oops

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
C extends Narrow<Command>

(
command: C,
execute: CommandExecute<C>
) => ({...command, execute})
...```

sudden nimbus
#

got distracted

#

@compact egret ^- this is what i'm talking baout

compact egret
sudden nimbus
#

woops

#

typo

compact egret
#

Now it's ApplicationCommandType.Message, so the description shouldn't be in the object because MessageApplicationCommandData doesn't allow it

#

Same for options

sudden nimbus
#

wiat

#

what's MessageApplicationCommandData

compact egret
#
export interface BaseApplicationCommandOptionsData {
  name: string;
  nameLocalizations?: LocalizationMap;
  description: string;
  descriptionLocalizations?: LocalizationMap;
  required?: boolean;
  autocomplete?: never;
}

export interface UserApplicationCommandData extends BaseApplicationCommandData {
  type: ApplicationCommandType.User;
}

export interface MessageApplicationCommandData extends BaseApplicationCommandData {
  type: ApplicationCommandType.Message;
}

export interface ChatInputApplicationCommandData extends BaseApplicationCommandData {
  description: string;
  descriptionLocalizations?: LocalizationMap;
  type?: ApplicationCommandType.ChatInput;
  options?: ApplicationCommandOptionData[];
}

export type ApplicationCommandData =
  | UserApplicationCommandData
  | MessageApplicationCommandData
  | ChatInputApplicationCommandData;
#

Oh seems like description is required, but anything but ChatInput shouldn't allow options

#

ApplicationCommandData is essentially the shape for validation for the object

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
C extends Narrow<ApplicationCommandData>

(
command: ForceInference<
C,
Pick<
C,
keyof C &
keyof (ApplicationCommandData & {
type: C["type"]
})

,
execute: CommandExecute<C>
) => ({...command, execute})
...```

sudden nimbus
#

@compact egret fixed

#

(probably)

#

oh

#

no autocomplete :(

compact egret
#

Yup

sudden nimbus
#

yeah honestly

#

i'd just recommend using the const generic modifier

sinful wharfBOT
#
n_n#2622

Preview:ts ... export const createCommand = <const C extends ApplicationCommandData>( command: C, execute: CommandExecute<C> ) => ({ ...command, execute }); ...

compact egret
sudden nimbus
#

cuz i'm stupid

#

turns out const generic parameters just...

#

... don't work (?!) with constraints

compact egret
#

Well def not stupid, you know your way around shortcomings / quirks way better

#

So you're saying that this is where the wall is hit right? No way around inferring this part

sudden nimbus
#

ok

#

one last (?!) thing

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
C extends ApplicationCommandData &
Narrow<ApplicationCommandData>

(
command: C,
execute: CommandExecute<C>
) => ({...command, execute})
...```

sudden nimbus
#

try this

compact egret
sudden nimbus
#

oh well :/

#

the least bad option is probably to make only options a generic parameter

#

i guess

compact egret
#

I don't think that would work though? Because then you'd need some default for the cases where it doesn't exist which would break inference too?

sudden nimbus
#

oh right

#

yeah, dang

#

i guess you could make both type and options generic

compact egret
#

What would that look like ๐Ÿค”

sudden nimbus
#
createCommand = <Type extends ApplicationCommandType, Options extends Narrow<Option[]>>(command: C & { type: Type; options?: Options; }, execute: CommandExecute<Type, Options>)
#

something like this

sinful wharfBOT
#
Daniell#4062

Preview:```ts
import {} from "discord-api-types"

import {
ApplicationCommandOptionData,
ApplicationCommandOptionType,
Attachment,
Channel,
Role,
User,
ChatInputCommandInteraction,
ContextMenuCommandInteraction,
ApplicationCommandType,
ApplicationCommandData
...```

compact egret
sinful wharfBOT
#
n_n#2622

Preview:```ts
...
type CommandExecute<
Type,
O extends ApplicationCommandOptionData[]

= Type extends ApplicationCommandType.ChatInput
? (
interaction: ChatInputCommandInteraction,
options: OptionsAsObject<O>
) => void
: (
interaction: ContextMenuCommandInteraction
) => void
...```

sudden nimbus
#

yeah you forgot to fully modify the CommandExecute helper type

compact egret
#

(Not ChatInput)

#

It does validate description etc though

sudden nimbus
#

ah yeah that's because of the options?: Options

sinful wharfBOT
#
n_n#2622

Preview:```ts
...
export const createCommand = <
Type extends ApplicationCommandType,
Options extends Narrow<
ApplicationCommandOptionData[]

(
command: ApplicationCommandData & {
type: Type
} & (Type extends ApplicationCommandType.ChatInput
? {options: Options}
: {}),
execute: CommandExecute<Type, Options>
) => ({...command, execute})
...```

sudden nimbus
#

here's a fix

compact egret
# sudden nimbus here's a fix

Thanks a lot for this, I feel less bad now I was stuck the whole day yesterday because the API I was going for turns out to just not be possible.. Can't explain why though. Could you explain the Narrow type and why it's needed? And also dm your paypal if you want

sudden nimbus
#

it's needed because normally:

  • tuples infer as arrays
  • strings, numbers, booleans infer as strings, numbers, booleans instead of literal types
  • object are fine tho
#

in this case you need the option name. i think tuples inferring as arrays isn't that big of a deal in this case

compact egret
#

So now it's only needed to get the literal types and not because array > tuple issue

#

createCommand = <Type extends ApplicationCommandType, const Options extends ApplicationCommandOptionData[]>( wish this did magic here

sudden nimbus
#

yeah ๐Ÿ˜”

compact egret
#

Well Prettier doesn't work with that syntax anyway so now at least I can move on from this and implement the actual functions with formatting ๐Ÿ˜… thanks again