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 🎉
The problem is that onlySyncFnWithArg
is being typed as "f1" | "f2" | "f3"
whereas it should be "f3"
....
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.
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?
The problem can be addressed by changing the KeyOfType
type as follows:
A extends B
and B extends A
):[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).