Skip to content

Referencing ReturnType<FooT> in generic param, and passing a function that has one param causes incorrect type inference... #29133

Closed

Description

TypeScript Version: 3.3.0-dev.20181129

Search Terms: ReturnType, Generic Param, Generic Function Argument

Code

//Innocent enough.
//Take a T, return a T
type Foo<T extends number> = (t: T) => T;

//=== NOT using ReturnType<FooT> in Generic Parameters ==
declare function bar<
    T extends number,
    FooT extends Foo<T>
>(t: T, foo: FooT): ReturnType<FooT>
//OK! `barResult` is of type `3`
const barResult = bar(
    3 as (3|5),
    () => 3
);
//OK! `barResult2` is of type `3`
const barResult2 = bar(
    3 as (3|5),
    //`t` is of type `3|5`
    //Return type is 3 
    t => 3
);

//=== Using ReturnType<FooT> in Generic Parameters ==
declare function baz<
    T extends number,
    FooT extends Foo<T>,
    //The same as bar<> but we reference ReturnType<FooT> here
    Ret extends ReturnType<FooT>
>(t: T, foo: FooT, ret: Ret): ReturnType<FooT>
//OK! `bazResult` is of type `3`
const bazResult = baz(
    3 as (3|5),
    () => 3,
    3
);
//NOT OK! `bazResult2` is of type `3|5`!
const bazResult2 = baz(
    3 as (3|5),
    //`t` is of type `3|5`
    //Return type is `3`
    t => 3,
    3
);

//NOT OK! `bazResult3` is of type `3|5`!
const bazResult3 = baz(
    3 as (3|5),
    //`t` is of type `3|5`
    //Return type is `3`
    //We *force* the return type to be 3
    (t) : 3 => (3 as 3),
    3
);

Expected behavior:

bazResult2 and bazResult3 should be of type 3, the same as bazResult

Actual behavior:

bazResult2 and bazResult3 are of type 3|5, different from bazResult (which is of type 3)

The difference between bazResult and bazResult2 is literally 3 characters but it causes the inferred types to be very different.

Playground Link: Here


I don't even know how to describe this...

My real use case is more complicated than the above example but I need the return type of a generic function to be inferred correctly to perform stronger compile-time type checks.

The strange thing is that () => 3 will give me the correct return type, but t => 3 will not.
In my own code, using the argument t is absolutely necessary.

As far as I've seen, this problem only occurs both the following are true,

  1. ReturnType<FooT> is used in a generic param constraint
  2. The concrete type of FooT has parameters (Like t => 3 has parameters, () => 3 does not)

If either of the above are false, this problem does not occur

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    UnactionableThere isn't something we can do with this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions