#Conditional Return type not narrowing

20 messages · Page 1 of 1 (latest)

steady musk
#
type Repliable = CommandInteraction | AnySelectMenuInteraction | ButtonInteraction | ModalSubmitInteraction | MessageComponentInteraction

export class ComponentManager<Itype extends Repliable> {

  constructor(public interaction: Itype) {}

  public getData(): Itype extends AnySelectMenuInteraction ? Itype["values"] : Itype extends ButtonInteraction ? Itype["customId"] : null {

    if (this.interaction.isAnySelectMenu()) return this.interaction.values
    if (this.interaction.isButton()) return this.interaction.customId
    if (this.interaction.isChatInputCommand()) return null
  }
}

The end goal I am looking for with this code is to narrow the return type of getData function based on the generic type passed into it. However I keep running into Type 'string[]' is not assignable to type 'Itype extends AnySelectMenuInteraction ? Itype["values"] : Itype extends ButtonInteraction<CacheType> ? Itype["customId"] : null'.ts(2322) for all of my returns despire them being inside a guard.

How can I force my getData function to know its return type is a string[] when its initialized with an AnySelectMenuInteraction, and a string when it is initialized with a ButtonInteraction and so forth.

Thanks in advance,

floral rampart
#

@steady musk Conditional return types aren't really type-safe.

#

You can use them to provide typings for a function, but you're pretty much always going to need a lot of unsafe casting to make it work.

#

An alternative is function overloads - they're also unsafe, but the type system more "stays out of the way".

#

I also often suggest trying to structure things so that there's a code-path for known, specific types, and a code-path for the unknown case, and you don't need these sort of patterns.

#

That might mean not using the class and generics in this case, though.

steady musk
# floral rampart <@802218076527394837> Conditional return types aren't really type-safe.

I initially was under the assumption that I wouldnt even need the return type and that based on the combination of class generic and type guards it would know which value is being returned. It knows the correct types of interaction.values and interaction.customId. It just for some reason is getting combining all possible return types despite me explicitly telling it when I initialize the class. Are you suggesting that I just split this into 3 separate methods?

floral rampart
#

Yeah, it's natural to think that the type system might support conditional return types, but it really doesn't.

#

I'm suggesting that getData() would probably just return the mix and yeah, if you already know that it's a particular type, you'd use some other mechanism, like a specific function.

#

Not sure it'd really need separate methods, though. It seems like if you know you have a cm: ComponentManager<AnySelectMenu> then you'd just be able to do cm.interaction.values without needing any sort of indirection.

steady musk
floral rampart
#

@steady musk No, I don't think that's any different than having the generic available on the class itself.

floral rampart
#

BTW, to be clear, if you want to do the conditional return type thing you can, it can be a useful type, it's just not something that's really checked and made safe by the compiler.

#

I think you basically just have to add as any or as never to all your return places.

steady musk
# floral rampart I think you basically just have to add `as any` or `as never` to all your return...
  public getData() {

    if (this.interaction.isAnySelectMenu()) return this.interaction.values
    if (this.interaction.isButton()) return this.interaction.customId
    if (this.interaction.isChatInputCommand()) return null
  }

with this there are no TS errors and the return type of getData turns into string or string[] (which are the correct types) so I dont think I would need to type assertion them as any would I? It sounds like I am just stuck checking what type I get returned from getData when I use it since typescript cant really narrow it down on its own even with the guards in place

#

Is that correct?

floral rampart
#

More or less.

#

My point is, though, that if you don't know - at the call site - what kind of interaction you have, then getting back string | string[] | null is kind of inevitable.

#

If you do know - at the call site - what kind of interaction you have, then you could bypass getData and avoid the issue.