Allow circular constraints
See original GitHub issueSuggestion
š Search Terms
Circular constraints, āType parameter āTā has a circular constraint.(2313)ā
ā Viability Checklist
My suggestion meets these guidelines:
- 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 feature would agree with the rest of TypeScriptās 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ā¦
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ā¦
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ā¦
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ā¦
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ā¦
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.
Issue Analytics
- State:
- Created a year ago
- Comments:11 (1 by maintainers)
Top GitHub Comments
Smart, I must have skimmed too hard. Thanks for the example
Well, itās not, here again what youāre doing is what Iād call a āworkaroundā to not have a circular constraint. What I would call simple is embracing the fact that the type of
order
depends upon itās value and hence there is a circular constraint by definition independent of code.I can increase the requirement even more, how would one refactor thisā¦ (Iām increasing the requirement slowly to keep it as minimal as possible.)
Again Iām just trying to prove this claim of mineā¦