When dealing with a value of type T extends infer R ? R : never
, it is likely that the compiler will struggle to make any meaningful inferences for T
. This is because the evaluation of such a conditional type may happen too late for T
to be inferred properly. Since this type essentially boils down to just T
, we'll simply refer to it as T
moving forward.
In my opinion, the signature and implementation of the wrap()
function should appear like this:
function wrap<T extends ((...args: any[]) => any)>(
wrapped: T & { flag?: string }): T & { flag?: string } {
let wrapper = function () {
// ...
return wrapped(...arguments);
} as T & { flag?: string };
wrapper.flag = wrapped.flag;
return wrapper;
}
This means that we are using the generic parameter T
to represent the function type, while keeping the {flag?: string}
as a distinct non-generic object type that intersects with T
. The rationale behind this approach has to do with how optional properties are handled:
In TypeScript, an object type without a property can be assigned to an object with an optional property of any type. For instance, the type {foo: string}
can be assigned to the type {foo: string, bar?: number}
. In the case of a generic type like
T extends {foo: string, bar?: number}
, where
T
could potentially lack the
bar
property entirely.
For your situation, if you have a generic type
T extends ((...args: any[])=>any) & {flag?: string}
, and provide a function without a known
flag
property for inference on
T
, the compiler might only infer
T
as the function type without the
flag
property. Consequently, both the input function and its output would be missing the
flag
property, resulting in undesired outcomes.
To prevent the compiler from losing track of the optional flag
property in both input and output contexts, we explicitly include it in both, ensuring consistency throughout.
I hope this explanation clarifies things for you. Best of luck!
Playground link to code