Generic function constraints degrade in conditionas but work in higher order functions
🔎 Search Terms
- generic
- constraint
- higher order generic
- higher order generic function
- constraint generic
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=4.0.5#code/PTAEAUCcFMGdsgNzqAxgewHawC6QIYCWmOso6AZqBZgLABQOAngA7SgDqBLAStDgFdImAIKgAvKAA8Y6AA8c0TABMyAzAGtM6AO6YA2gF0ANKB4A+ABQ0AXKEsA6J-kgBzWHZEBKCebM-xcwZQeycHF3dPAL8Abx1uDzMAXwYGEFAAEWhXAmUUDGw8IhIySlAAFVAcdFB1LV06RlZ2LnxefiFMACEpcr9JSvlFFTJHZzdE4goEUG9fUCmZnmCQ0AB+UPHI2ejQOIS7HhT6VdA7TGhkSFT6PNQAGxd2AtxQWHQAW2gAMUw7KXAAEZTOAAExWIF2IGmFigqGg3b6aEQUGGG53R4wNBYV7xNp8QTCER2VrtQmiBgYp7YwqgPFkzpdEncAmMqTMNhld5fX5BeiU6APakvHCgOR2ADk+AARqgJQKhViRaAmHZAaCAMw3dIAYRxRWIpHIVG5P0woGpLBg8CQ0GUDGV+AkdJZHSJllNvy8ljkpiYPnS+zaiX0UtlEtM6o1hmOjqdknprPdibdog9nzNXm9vpVAbAQZYiQLIbDcsjmpjxzSYD1hQIhtKJozv1AeRy+DyyiqNTq2j0Dv1oGlzpT5K6Pr9eb29JDvYapjnehjQA
💻 Code
// Preserves constraints of fn
type WrapReturnA = <A extends unknown[], R>(fn: (...args: A) => R) =>
(...args: A) => {wraps: R}
// Degrades constraints of T to unknown
type WrapReturnB<T> = T extends (...args: infer A) => infer R
? (...args: A) => {wraps: R}
: never
declare const someFn: <P1, P2>(P1: P1, p2: P2) => [P1, P2]
declare const wrapReturnA: WrapReturnA
declare const wrapReturnB: WrapReturnB<typeof someFn>
declare const x: 'abc'
declare const y: 123
// Constraints of someFn are preserved
const a = wrapReturnA(someFn)(x, y) // {wraps: ['abc', 123]}
const aa = wrapReturnA(wrapReturnA(someFn))(x, y) // {wraps: {wraps: ['abc', 123]}}
// Constraints of someFn degraded to unknown
const b = wrapReturnB(x, y) // {wraps: [unknown, unknown]}
🙁 Actual behavior
Wrapping (intercepting/decorating) a function (to for example wrap the return value) with generic constraints by inferring its arguments and return value in a generic type degrades the function's constraints to unknown:
type WrapReturnB<T> = T extends (...args: infer A) => infer R
? (...args: A) => {wraps: R}
: never
The constraints are however preserved and propagated if the type is a generic (higher order) function:
type WrapReturnA = <A extends unknown[], R>(fn: (...args: A) => R) =>
(...args: A) => {wraps: R}
🙂 Expected behavior
It would be great if the generic constraints of a function wrapped with inference in a conditional type would not be degraded to unknown.
Additional information about the issue
No response
It's a current design limitation. A conditional type like this can't produce a generic function, type parameters are erased and replaced with what got inferred for them (and that can be their constraints, like unknown). It's not about what is produced by a call to wrapReturnB. It's just about what wrapReturnB has been computed as initially:
declare const wrapReturnB: WrapReturnB<typeof someFn>
// ^? const wrapReturnB: (P1: unknown, p2: unknown) => { wraps: [unknown, unknown]; }
Thanks for the clarification @Andarist. Is there any other way how to derive a type from a generic function (to tweak its signature) and keep the constraints?
You already have an example of that in your playground. A runtime-level call can produce a new "adapted" generic signature and you are already returning one from this call: wrapReturnA(someFn). The return type of this is <P1, P2>(P1: P1, p2: P2) => { wraps: [P1, P2]; }
Yeah I know, but I meant purely on the type level
I don't think it's possible today.
This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.