-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Description
π Search Terms
"error type", "conditional type fallback", "partial type constructor"
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
It would be useful for type level computations to be able to produce a type error.
A lot of TypeScript code uses the conditional types and inferred types features to perform computations on types. Type level computation itself however is not very strongly typed, since there is no kind system. This limits the extent to which we can make guarantees about the input of a given type-level function, which means we often accept overly wide input, parse it using conditional and inferred types, and then dispatch the fallback case of the conditional type (which one often expects or hopes never arises in practice) to some meaningless or dummy type, usually never.
It's worth noting that never is not the type level analogue of an error: it's a meaningful type in the type system, and term level use of a computed type that has strayed into a never-producing fallback case can sometimes escape detection and result in term level misuse.
I think it would be more honest for evaluation of partial type level functions to fail with an author-determined type error, rather than just picking an arbitrary (usually uninhabited type), and hoping that this will cause enough problems downstream to be noticed when we enter the fallback case.
Another use for the ability to explicitly construct type errors would be to allow library authors to replace generic type errors like subsumption failures (often on complicated types) with meaningful type errors relevant to users of the library.
π Motivating Example
Right now the library author writes:
type Source<S extends string> = S extends `${infer A} -> ${infer B}` ? A : never
And hopes the user understands the type error arising from:
const a: Source<"A => B"> = A
// => Type '"a"' is not assignable to type 'never'.(2322)
Of course things can get even worse with: const f: (x: Source<"A => B">) => Whatever.
With this feature the library author would write:
type Source<S extends string> = S extends `${infer A} -> ${infer B}` ? A : TypeError<`Expected a string of the form "_ -> _", got ${S}`>
and immediately get a type error:
const a: Source<"A => B"> = A
// => Expected a string of the form "_ -> _", got "A => B"
There are also motivating examples to be had for rewriting complicated subsumption type errors into errors legible in the business logic of a library, but not motivating enough for me to make one up right now (the semantics need to be worked out a bit too).
Also I'm aware that you can do something like "extends ${string} -> ${string}" in this simple example, but in general it is not always possible to constrain the input to make the fallback case of the conditional unreachable.
π» Use Cases
- What do you want to use this for?
Conditional and inferred types, library-specific custom error messages
- What shortcomings exist with current approaches?
Unexpected or accidental use of type functions implemented with the (semantically incorrect) fallback never can lead to term level misuse of the library and bugs.
- What workarounds are you using in the meantime?
Using extends clauses and excessive type parameterization heavily to make type-level functions as total as possible.