A slightly imperfect but still effective improvement on the solution by @T.J Crowder
This approach restricts the use of unions but doesn't validate the correctness of value types
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 instead of argument 2, still a valid error scenario
someFunc<"quantity" | "age">("quantity", 10) // Should not show an error since both quantity and age are numbers
// Functionality remains intact
someFunc("name", "John") // OK
someFunc("name", 10) // Desired error generated
someFunc("quantity", "John") // Desired error shown
someFunc("quantity", 10) // No issues
https://i.sstatic.net/GlJCj.png
IsUnion
The code may flag errors in unexpected places and overlook certain cases, but ultimately it guides you towards the correct type
While not perfect, it functions effectively, demonstrating the importance of prioritizing accuracy over assumption
However, this method may be challenging for newcomers, leading to frustration with troubleshooting that could potentially shake their confidence
Explaining this concept is complex and perhaps TypeScript should bear responsibility for such intricacies