How to distinguish different functions signature with conditional type checks?
Asked Answered
S

1

3

I'd like to be distinguish the following function types in conditional type checks:

type SyncFn = () => void;
type AsyncFn = (data: number) => Promise<void>;
type SyncFnWithArg = (data: number) => void;

So I can then use the KeyOfType that @Titian Cernicova-Dragomir posted and get the keys within a given interface that match a given type.

I tried the following:

type SyncFn = () => void;
type AsyncFn = (data: number) => Promise<void>;
type SyncFnWithArg = (data: number) => void;

interface Foo {
    a?: string;
    b?: number;
    c: number;
    d: string;
    f1?: SyncFn;
    f2?: AsyncFn;
    f3?: SyncFnWithArg;
}

// note: `KeyOfType` from https://mcmap.net/q/387187/-typescript-keyof-returning-specific-type
type KeyOfType<T, V> = keyof { [P in keyof T as T[P] extends V? P: never]: any }

type KeyOfTypeOptionalIncluded<T, Condition> = KeyOfType<T, Condition | undefined>


let onlyStrings: KeyOfTypeOptionalIncluded<Foo, string>;
onlyStrings = 'a' // βœ… working as expected πŸŽ‰
onlyStrings = 'b' // βœ… erroring out as expected πŸŽ‰
onlyStrings = 'd' // βœ… working as expected πŸŽ‰


let onlySyncFn: KeyOfTypeOptionalIncluded<Foo, SyncFn>;
onlySyncFn = 'f1' // βœ… working as expected πŸŽ‰
onlySyncFn = 'f2' // βœ… erroring out as expected πŸŽ‰
onlySyncFn = 'f3' // βœ… erroring out as expected πŸŽ‰

let onlyAsyncFn: KeyOfTypeOptionalIncluded<Foo, AsyncFn>;
onlyAsyncFn = 'f1' // βœ… erroring out as expected πŸŽ‰
onlyAsyncFn = 'f2' // βœ… working as expected πŸŽ‰
onlyAsyncFn = 'f3' // βœ… erroring out as expected πŸŽ‰

let onlySyncFnWithArg: KeyOfTypeOptionalIncluded<Foo, SyncFnWithArg>;
onlySyncFnWithArg = 'f1' // 😭 should error out 😭
onlySyncFnWithArg = 'f2' // 😭 should error out 😭
onlySyncFnWithArg = 'f3' // βœ… working as expected πŸŽ‰

TS Playground

The problem is that onlySyncFnWithArg is being typed as "f1" | "f2" | "f3" whereas it should be "f3"....

enter image description here


I also noticed that if I modify AsyncFn and remove its argument then I have more problems since the type definition for onlySyncFn is now incorrect since now it's "f1" | "f2" instead of only being "f1" as it is in the first TS Playground above.

Second TS Playground

I guess that's related with how function overloading in typescript is done, but I don't really know, so that's why I'm reaching out for help.... maybe it's not related, but are we able to do such function type distinction in TS?

Sandisandidge answered 1/11, 2022 at 6:44 Comment(0)
I
1

The problem can be addressed by changing the KeyOfType type as follows:

  1. Check both directions of the type relationship (A extends B and B extends A):
  2. Wrap the types used in the conditional type clause in tuples ([A] extends [B]).
type KeyOfType<T, V> = keyof {
  [P in keyof T as [T[P]] extends [V]
    ? [V] extends [T[P]]
      ? P
      : never
    : never
  ]: any
}

Find a playground example here, and an interesting discussion here about various ways to test for type equality (each with their own caveats).

Infantryman answered 1/11, 2022 at 7:21 Comment(1)
oh... that makes sense!! and that's completely new to me (checking both directions)! thank you so much! πŸŽ‰ – Sandisandidge

© 2022 - 2024 β€” McMap. All rights reserved.