Repository: TypeScript
Issue: Allow circular constraints
Labels: Suggestion,Awaiting More Feedback

# Suggestion


## 🔍 Search Terms

Circular constraints, "Type parameter 'T' has a circular constraint.(2313)"

## ✅ Viability Checklist

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

Often constraints are truly circular, that is to say we want type-checking an parameter based on itself, for instance consider this...

```ts
declare const placeOrder: <T extends Order<T>>(order: T) => void

type Order<Self> =
  { ids: number[]
  , urgent: Self["ids" & keyof Self]
  }

declare const orderA: 1
declare const orderB: 2
declare const orderC: 3

placeOrder({ ids: [orderA, orderB], urgent: [orderA] })
// okay, good

placeOrder({ ids: [orderA, orderB], urgent: [orderC] })
// nope, `orderC` can't be marked as urgent as it's not being placed
```

Here the circular constraint `T extends Order<T>` compiles because it's not immediately circular, but in case of immediately circular the compiler complaints and doesn't allow compiling it...

```ts
type Basket = { bananas: number, apple: number }
declare const updateBasket: <T extends ShallowExact<T, Partial<Basket>>(basket: T) => void
//                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type parameter 'T' has a circular constraint.(2313)

updateBasket(({ banana: basket.bananas + 1, apple: 10 }))
// no error

type ShallowExact<T, U> =
  T extends U
    ? U extends unknown
        ? { [K in keyof T]: K extends keyof U ? T[K] : never }
        : never
    : U
```

As a workaround we could make the circular constraint non-immediate by using a variadic argument...

```ts
type Basket = { bananas: number, apple: number }
declare const updateBasket: <T extends [ShallowExact<T[0], Partial<Basket>]>(...basket: T) => void

updateBasket(({ banana: basket.bananas + 1, apple: 10 }))
// Type 'number' is not assignable to type 'never'

type ShallowExact<T, U> =
  T extends U
    ? U extends unknown
        ? { [K in keyof T]: K extends keyof U ? T[K] : never }
        : never
    : U
```

But this makes the type unnecessarily complicated, in some scenarios even more complicated...

```ts
type Basket = { bananas: number, apple: number }
declare const updateBasket:
  <F extends [(state: Basket) => ShallowExact<ReturnType<F[0]>, Partial<Basket>>]>(...f: F) => void

updateBasket(basket => ({ banana: basket.bananas + 1, apple: 10 }))
// Type 'number' is not assignable to type 'never'

type ShallowExact<T, U> =
  T extends U
    ? U extends unknown
        ? { [K in keyof T]: K extends keyof U ? T[K] : never }
        : never
    : U
```

This could have simply been the following if TS allowed circular constraints...

```ts
type Basket = { bananas: number, apple: number }
declare const updateBasket:
  <T extends ShallowExact<T, Partial<Basket>>(f: (state: Basket) => T) => void

updateBasket(basket => ({ banana: basket.bananas + 1, apple: 10 }))
// no error

type ShallowExact<T, U> =
  T extends U
    ? U extends unknown
        ? { [K in keyof T]: K extends keyof U ? T[K] : never }
        : never
    : U
```

So the feature request is to allow writing circular constraints.