Repository: TypeScript
Issue: Proposal: new "invalid" type to indicate custom invalid states
Labels: Suggestion,In Discussion

## Proposal

A new `invalid` type that is not assignable to or from any other types. This includes not being assignable to or from `any` or `never`. It probably shouldn't even be assignable to `invalid` itself if that is possible, although I doubt that one really matters. I'd additionally suggest that, unlike other types, `invalid | any` is not reduced to `any` and `invalid & never` is not reduced to `never`.

The idea is to make sure that there is a compile error any time an `invalid` type is inferred or otherwise pops up in a users code.

`invalid` types would come from conditional types to represent cases where the conditional type author either expects the case to never happen, or expects that it might happen but intentionally wants that case to cause a compile error indicating to the user that something is invalid with the code they wrote.

The `invalid` type should also allow optionally passing an error message that would be displayed to the user when they encounter a compile error caused by the type that could give them a better idea of exactly what the problem is and how to fix it. 

## Motivating Examples
### Allowing either `true` or `false` but not `boolean` - https://github.com/Microsoft/TypeScript/issues/23493#issuecomment-384369226

```ts
type XorBoolean<B extends boolean> = boolean extends B ? invalid<'only literal true or false allowed'> : boolean

declare function acceptsXorBoolean<B extends boolean & XorBoolean<B>>(arg: B): void

acceptsXorBoolean(true) // allowed
acceptsXorBoolean(false) // allowed

declare const unknownBoolean: boolean
acceptsXorBoolean(unknownBoolean)
// would have error message: 
// Argument of type 'boolean' is not assignable to parameter of type invalid<'only literal true or false allowed'>
```
It's possible to write the above example today([playground link](https://www.typescriptlang.org/play/#src=type%20XorBoolean%3CB%20extends%20boolean%3E%20%3D%20boolean%20extends%20B%20%3F%20never%20%3A%20boolean%0D%0A%0D%0Adeclare%20function%20acceptsXorBoolean%3CB%20extends%20boolean%20%26%20XorBoolean%3CB%3E%3E(arg%3A%20B)%3A%20void%0D%0A%0D%0AacceptsXorBoolean(true)%20%2F%2F%20allowed%0D%0AacceptsXorBoolean(false)%20%2F%2F%20allowed%0D%0A%0D%0Adeclare%20const%20unknownBoolean%3A%20boolean%0D%0AacceptsXorBoolean(unknownBoolean)%0D%0A%2F%2F%20Argument%20of%20type%20'boolean'%20is%20not%20assignable%20to%20parameter%20of%20type%20'never'.)) using `never` instead of `invalid`, but it generates an error message saying: `Argument of type 'boolean' is not assignable to parameter of type 'never'.` which is very likely to be confusing to a user who encounters it. 

### Preventing duplicate keys - https://github.com/Microsoft/TypeScript/issues/23413#issuecomment-381369843
```ts
type ArrayKeys = keyof any[]
type Indices<T> = Exclude<keyof T, ArrayKeys>
type GetUnionKeys<U> = U extends Record<infer K, any> ? K : never
type CombineUnion<U> = { [K in GetUnionKeys<U>]: U extends Record<K, infer T> ? T : never }
type Combine<T> = CombineUnion<T[Indices<T>]>

declare function combine<
  T extends object[] &
    {
      [K in Indices<T>]: {
        [K2 in keyof T[K]]: K2 extends GetUnionKeys<T[Exclude<Indices<T>, K>]> ? invalid<"Duplicated key"> : any
      }
    } & { "0": any }
    >(objectsToCombine: T): Combine<T>


const result1 = combine([{ foo: 534 }, { bar: "test" }]) // allowed

const error1 = combine([{ foo: 534, dupKey: "dup1" }, { bar: "test", dupKey: "dup2" }]) // error
```
Today([playground link](https://www.typescriptlang.org/play/#src=type%20ArrayKeys%20%3D%20keyof%20any%5B%5D%0D%0Atype%20Indices%3CT%3E%20%3D%20Exclude%3Ckeyof%20T%2C%20ArrayKeys%3E%0D%0Atype%20GetUnionKeys%3CU%3E%20%3D%20U%20extends%20Record%3Cinfer%20K%2C%20any%3E%20%3F%20K%20%3A%20never%0D%0Atype%20CombineUnion%3CU%3E%20%3D%20%7B%20%5BK%20in%20GetUnionKeys%3CU%3E%5D%3A%20U%20extends%20Record%3CK%2C%20infer%20T%3E%20%3F%20T%20%3A%20never%20%7D%0D%0Atype%20Combine%3CT%3E%20%3D%20CombineUnion%3CT%5BIndices%3CT%3E%5D%3E%0D%0A%0D%0Adeclare%20function%20combine%3C%0D%0A%20%20T%20extends%20object%5B%5D%20%26%0D%0A%20%20%20%20%7B%0D%0A%20%20%20%20%20%20%5BK%20in%20Indices%3CT%3E%5D%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20%5BK2%20in%20keyof%20T%5BK%5D%5D%3A%20K2%20extends%20GetUnionKeys%3CT%5BExclude%3CIndices%3CT%3E%2C%20K%3E%5D%3E%20%3F%20never%20%3A%20any%0D%0A%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D%20%26%20%7B%20%220%22%3A%20any%20%7D%0D%0A%20%20%20%20%3E(objectsToCombine%3A%20T)%3A%20Combine%3CT%3E%0D%0A%0D%0A%0D%0Aconst%20result1%20%3D%20combine(%5B%7B%20foo%3A%20534%20%7D%2C%20%7B%20bar%3A%20%22test%22%20%7D%5D)%0D%0A%0D%0Aconst%20error1%20%3D%20combine(%5B%7B%20foo%3A%20534%2C%20dupKey%3A%20%22dup1%22%20%7D%2C%20%7B%20bar%3A%20%22test%22%2C%20dupKey%3A%20%22dup2%22%20%7D%5D)%0D%0A)) using `never` instead of `invalid` the error message for `error1` is: 

```
Argument of type '[{ foo: number; dupKey: string; }, { bar: string; dupKey: string; }]' is not assignable to parameter of type 'object[] & { "0": { foo: any; dupKey: never; }; "1": { bar: any; dupKey: never; }; } & { "0": any...'.
  Type '[{ foo: number; dupKey: string; }, { bar: string; dupKey: string; }]' is not assignable to type '{ "0": { foo: any; dupKey: never; }; "1": { bar: any; dupKey: never; }; }'.
    Types of property '"0"' are incompatible.
      Type '{ foo: number; dupKey: string; }' is not assignable to type '{ foo: any; dupKey: never; }'.
        Types of property 'dupKey' are incompatible.
          Type 'string' is not assignable to type 'never'
```
which would be basically impossible to understand if you didn't expect the function would reject duplicated keys. Using `invalid<"Duplicated key">` however the error message could read:

```
Argument of type '[{ foo: number; dupKey: string; }, { bar: string; dupKey: string; }]' is not assignable to parameter of type 'object[] & { "0": { foo: any; dupKey: invalid<"Duplicated key">; }; "1": { bar: any; dupKey: invalid<"Duplicated key">; }; } & { "0": any...'.
  Type '[{ foo: number; dupKey: string; }, { bar: string; dupKey: string; }]' is not assignable to type '{ "0": { foo: any; dupKey: invalid<"Duplicated key">; }; "1": { bar: any; dupKey: invalid<"Duplicated key">; }; }'.
    Types of property '"0"' are incompatible.
      Type '{ foo: number; dupKey: string; }' is not assignable to type '{ foo: any; dupKey: invalid<"Duplicated key">; }'.
        Types of property 'dupKey' are incompatible.
          Type 'string' is not assignable to type 'invalid<"Duplicated key">'
```
Which gives a very clear hint that the problem is that `dupKey` is duplicated.

### Conditional cases which should never happen
I could also see `invalid` potentially being used for some conditional types where there is a branch that presumably never gets taken because you are just using the conditional type for the `infer` capability. For example at the end of #21496 there is a type:
```ts
type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : never;
```
Maybe `invalid<"should never happen">` is used instead of `never` for the false branch so it's easier to track down the problem if it ever turns out the assumption that the branch will never be taken is wrong. (Of course if `T` is `any`, both the true and false branches are always taken so you might not want to change it away from `never`, but at least there'd be the option)  

## Related Issues
#20235 - _Generics: Cannot limit template param to specific types_ - Could benefit from an approach like `XorBoolean` above.
#22375 - _how do i prevent non-nullable types in arguments_ - Solution here is basically the same idea as `XorBoolean`. The error message for this specific issue is already understandable but it shows there is more interest in the pattern.
#13713 - _[feature request] Custom type-error messages_ - Similar sounding idea, but it seems to be focused on changing the wording of existing error messages.

## Search Terms
invalid type, custom error message, generic constraint, conditional types