Skip to content

Instantly share code, notes, and snippets.

@rob3c
Last active February 10, 2020 00:37
Show Gist options
  • Save rob3c/6cbaad2bd8314a4d2a8b578ceac1058b to your computer and use it in GitHub Desktop.
Save rob3c/6cbaad2bd8314a4d2a8b578ceac1058b to your computer and use it in GitHub Desktop.
Clarifying intersection vs union typescript expectations in function parameters
const myFnMap = {
fnA: ({pa}: {pa: string}) => `fnA(${pa})`,
fnB: ({pb}: {pb: string}) => `fnB(${pb})`,
};
type MyFnMap = typeof myFnMap;
// as expected: 'fnA' | 'fnB'
type MyFnKey = keyof MyFnMap;
// as expected: (({ pa }: { pa: string; }) => string) | (({ pb }: { pb: string; }) => string)
type MyFn = MyFnMap[MyFnKey];
// as expected: { pa: string; } | { pb: string; }
type MyFnParam = Parameters<MyFn>[0];
function callMappedFn<
TKey extends keyof MyFnMap,
>(
key: TKey,
arg: Parameters<MyFnMap[TKey]>[0]
): string {
// Get the function for the specific key and let's see what happens...
const fn = myFnMap[key];
// Surprise! (maybe)
// `fn` expects the intersection { pa: string; } & { pb: string; }
// instead of the union { pa: string; } | { pb: string; }
// that one might expect (or at least hope for when writing this lol).
//
// So the specific `arg` for the specific `key` doesn't seem to match
// the params in the expected way, even though it seems to be constrained
// by the typing. It's an illusion, though: typescript is correct - at
// least according to its current capabilities - if it wants every call
// to succeed without further checking on our part.
//
// `result` is still `string` as expected, even though `args` gets squiggles.
const result = fn(arg);
return result;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment