question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Assigning a callable return type of a generic function directly to some generic parameter results in error

See original GitHub issue

TypeScript Version: above 3.3.3

Code

To see the actual behavior depending on a version of TS it is better to open the code below in the playground

type Callable<T> = { (t: T): any; };


declare function utilA<T>(
  callable: Callable<T>,
  fn: (t: T) => any
): any

declare function utilB<T>(
  callable: Callable<T>,
  params: {
    fn: (t: T) => any
  }
): any

declare function utilC<T>(
  params: {
    callable: Callable<T>,
    fn: (t: T) => any
  }
): any

/**
 * Case 1: directly assigning a callable
 */

declare const callable: Callable<number>

utilA(
  callable,
  t => t.toExponential() // t is a number, OK
)

utilB(
  callable,
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable,
  fn: t => t.toExponential() // t is a number, OK
})

/**
 * Case 2: directly assigning the result of a function returning a callable
 */

declare function computeCallableA(): Callable<number>

utilA(
  computeCallableA(),
  t => t.toExponential() // t is a number, OK
)

utilB(
  computeCallableA(),
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable: computeCallableA(),
  fn: t => t.toExponential() // t is a number, OK
})

/**
 * Case 3: directly assigning the result of a generic function returning a callable
 */

declare function computeCallableB<T>(t: T): Callable<T>

utilA(
  computeCallableB(100),
  t => t.toExponential() // t is a number, OK
)

utilB(
  computeCallableB(100),
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable: computeCallableB(100), // Error
  fn: t => t.toExponential() // t is an unknown
})

/**
 * The last case is strange IMO. The result type of `computeCallableB` is immediately known.
 * 
 * Hovering the last `utilC` shows the following:
 * 
 * function utilC<number>(params: {
 *   callable: Callable<number>;
 *   fn: (t: number) => any;
 * }): any
 * 
 * Despite the fact that TS infers generic `T` of `utilC`, assignment to `callable` param
 * gives: "Type 'Callable<number>' is not assignable to type 'Callable<unknown>'.".
 * 
 * It is hard to understand whats going on. More than that, everything works in TS v3.3.3.
 * In newer versions the behavior is broken.
 * 
 * This has an impact on the library I help to maintain. Such use cases are not rare and
 * people are forced to assign the result of `computeCallableB(100)` to a variable first,
 * and then to a `callable` parameter, which affects DX:
 */

const computeCallableBResult = computeCallableB(100)

utilC({
  callable: computeCallableBResult, // This works!
  fn: t => t.toExponential() // t is a number, OK
})

Expected behavior:

The invocation of

utilC({
  callable: computeCallableB(100), // Error
  fn: t => t.toExponential() // t is an unknown
})

gives no errors.

Actual behavior:

Strange behavior in versions above 3.3.3.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:10
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
andrewbranchcommented, Apr 6, 2020

That’s desired behavior. I think this bit in the new handbook is relevant.

0reactions
SantoJambitcommented, Apr 6, 2020

@andrewbranch ah, thanks 😃 Is that wanted or an existing bug?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Typescript ReturnType of generic function - Stack Overflow
This is my currently working solution for extracting un-exported internal types of imported libraries (like knex):
Read more >
typing — Support for type hints — Python 3.11.1 documentation
The Python runtime does not enforce function and variable type annotations. ... All varieties of TypeVar are permissible as parameters for a generic...
Read more >
Common issues and solutions - mypy 0.991 documentation
There are several common reasons why obviously wrong code is not flagged as an error. The function containing the error is not annotated....
Read more >
Google Python Style Guide
They express the operation directly, without extra method calls. A function that uses default operators is generic. It can be used with any...
Read more >
1. Typechecking - Hack and HHVM [Book] - O'Reilly
The syntax for function return types is the simplest. ... passing a function more arguments than it has parameters doesn't result in an...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found