8

I think it's clearer to try it on TS Playground:

function identity<T extends (...args: any[]) => any>(fn: T): T {
  return fn;
}

function fn(args: {
  cb: (foo: number) => void,
}) {}

fn({
  cb: identity((foo /* infers number */) => {}),
});

function fn2(args: {
  cb?: (foo: number) => void,
}) {}

fn2({
  cb: identity((foo /* doesn't infer number */) => {}),
});

function fn3(args: {
  cb: (foo: number) => void,
} | {}) {}

fn3({
  cb: identity((foo /* infers number */) => {}),
});

For fn and fn3, TS was able to infer that foo is a number. However, for fn2, TS just typed foo as any. The typing for fn2 and fn3 are functionally the same thing, so I'm wondering why TS couldn't infer the type of foo.

The realistic use-case for this is React's useCallback, I was trying to infer the argument types for functions that pass through useCallback.

Why does TS behave like this? Is there a less hacky solution?

0

2 Answers 2

3

I think the crucial step here is the inference of identitys return type:

 let result: ((foo: number) => void | undefined) = identity(...);

As the return type has a base constraint of (...args: any[]) => any this rule of the Typescript specification applies:

The inferred type argument for each type parameter is the union type of the set of inferences made for that type parameter. However, if the union type does not satisfy the constraint of the type parameter, the inferred type argument is instead the constraint.

~ TypeScript Language Specification (outdated), Contextual Signature Instantiation

As undefined (the optional value) does not satisfy the constraint, the constraint is taken, and T gets inferred as (...args: any[]) => any.

By removing the constraint, T gets inferred correctly

Sign up to request clarification or add additional context in comments.

Comments

0

The problem is that in fn2 the cb arg is optional, so identity can't infer from that. So you want to explicit tell TS that that arg can be undefined like so:

function fn2(args: {
  cb: (foo: number) => void | undefined
}) {}

This should do the trick and make the infer work again.

TS Playground link

2 Comments

Doing it like this makes cb no longer optional and needs to be supplied as undefined Why wouldn't identity be able to infer from an optional parameter?
I'm not really aware of the why the identity right now can't infer from the optional value, but thinking at the use case of a useCallback it should work correctly since you won't use a useCallback without a callback function inside, am I wrong?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.