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://stackoverflow.com/questions/49752151/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?

awdk
  • 1,477
  • 12
  • 17

1 Answers1

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).

Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
  • oh... that makes sense!! and that's completely new to me (checking both directions)! thank you so much! – awdk Nov 01 '22 at 23:22