Comment by eyelidlessness
12 hours ago
I think they’re talking about something slightly different, and they allude to it by saying the useful complex types on the happy path become less useful when something goes wrong.
What I believe they’re encountering is that type errors—as in mistaken use of APIs, which are otherwise good when used correctly—become harder to understand with such complex types. This is a frequent challenge with TypeScript mapped and conditional types, and it’s absolutely just as likely with good APIs as bad ones. It’s possible to improve on the error case experience, but that requires being aware of/sensitive to the problem, and then knowing how to apply somewhat unintuitive types to address it.
For instance, take this common form of conditional type:
type CollectionValue<T> = T extends Collection<infer U>
? U
: never;
The never case can cause a lot of confusion, especially at a distance where CollectionValue may be invoked indirectly. It can often be a lot easier to understand why a type error occurs by producing another incompatible type in the same position:
type CollectionValue<T> = T extends Collection<infer U>
? U
: 'Expected a Collection';
(I’ve used somewhat simplistic examples, because I’m typing this on my phone. But hopefully the idea is clear enough for this discussion!)
What about the (incredibly unlikely, i'll admit) scenario where somebody attempts to pass the literal 'Expected a Collection' as an instance of this type? What's the best way to insert a warning, but also guarantee the type is unsatisfiable?
('Expected a Collection' & never)?
Perhaps you could make it a private Symbol? Then it should be impossible semantically to use it from the outside.