not so ideal foolproof improvement over @T.J Crowder solution
It stops you from using union but does not check the correctness of value type
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
type SomeType = {
name: string;
quantity: number;
age: number
};
const someFunc = <Key extends keyof SomeType>(
key: IsUnion<Key> extends true ? "No Union!" : Key,
value: SomeType[Key],
) => {
// ...
};
someFunc<"name" | "quantity">("name", 10) // error at argument 1, ideally should error at argument 2, but still an error regardless, a true negative case
someFunc<"quantity" | "age">("quantity", 10) // should not error, because both quantity and age are number, a false negative case
// still works as normal
someFunc("name", "John") // OK
someFunc("name", 10) // Error as desired
someFunc("quantity", "John") // Error as desired
someFunc("quantity", 10) // OK
playground
IsUnion
as shown in the code, not only it error on the wrong argument, there is also a false negative case
but does it matter? No it doesn't
because you are forced to fix errors by removing the union regardless, clearing the false negative case will not open up to another type error
so the end result is, it will still leads you to the correct type
It is not an ideal method, but it works, it is an example of better be safe than wrong
One problem is this is not newbies friendly, it requires the study of the background problem to understand why the code is written in such way and why the error appear on the wrong argument or appear for no reason.
For those newbies, they will end up in unfruitful troubleshooting every single time, and we all know that the next thing they do is to doubt the purpose of their very own existence
There is no way you can explain this like you explaining to a 5 years old, so TS should take the blame