Repository: TypeScript
Issue: Allow conditional check on generic parameters
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

- generics
- constraints
- conditional
- check

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

<!-- A summary of what you'd like to see added or changed -->

Allow conditional constraints on generic parameters.

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

1. You can constrain generics by type calculation, like this:

```typescript
type EQ<A, B> = A extends B ? B extends A ? true : false : false

function f<A, B> where [EQ<A,B>] (a:A, b:B) { ... }

f(1,2) // ok
f(1,'a') // err
```

This describes a function whose two arguments must be of the same type.

The `where` field provides an list, each item of this list is a type calculation, and the function can only be called if each item is `true`.

2. For simplicity, this constraint is only useful when calling the function, and does not affect the judgment of this generic type inside the function.
```typescript
type IsString<A> = A extends String ? true : false

function f<A> where [IsString<A>] (a:A) {
    // Inside the function, don't know that the type of A is a string.
}

f('a') // ok
f(1) // err
```

3. This check is post-processing, that is, when calling the function, the type of the generic type is first deduced through parameters, etc., and finally it is checked whether the constraints in `where` are satisfied. So this won't affect the existing generic parameter logic.

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

I think this provides two benefits:

1. Can describe generic conditions more freely, not just subtype constraints.

For example, there is a function that expects to enter a phone number:

```typescript
type IsPhoneNumber<A extends string> = ...

function f<A extends string> where [IsPhoneNumber<A>] (a:A) { ... }
```

2. Allows describing the relationship between multiple generics, and is more expressive.

For example, a function takes two arguments, both of which are items in a given list, and the arguments cannot be duplicates:

```typescript
type EQ<A, B> = A extends B ? B extends A ? true : false : false
type NOT<A>=A extends false? true: false
type Include<list, item> = list extends [] ? false : list extends [infer x, ...infer xs] ? item extends x ? true : Include<xs, item> : false

type list = ['a', 'b', 'c']

function f<A, B> where [Include<list, A>, Include<list, B>, NOT<EQ<A,B>>] (a:A, b:B) { ... }
```

---

My workaround now is to do a type check on the return value of the function and return `never` if it doesn't match the condition:

```typescript
type EQ<A, B> = A extends B? B extends A? true: false: false

function f<A, B>(a:A, b:B): EQ<A,B> extends true? string: never { ... }
```

But this has many problems.

First, the return value is inferred as a conditional type and I need to convert it manually:

```typescript
type EQ<A, B> = A extends B? B extends A? true: false: false

function f<A, B>(a:A, b:B): EQ<A,B> extends true? string: never { return 'a' as any }
```

Second, when the wrong parameter is entered, the type of the return value is `never`, which is different from reporting an error.
Although most of the time, when I try to use a value of type `never`, I get an error.