Skip to content

ReturnType<F> in parameter of generic function breaks inference when F has one implicitly typed param #33042

Closed
@AnyhowStep

Description

@AnyhowStep

TypeScript Version: 3.5.1

Search Terms:

  • ReturnType
  • Implicit type annotation
  • Generic function
  • Parameter

Code

This is me opening a new issue for #29133

I have three separate code snippets, please bear with me =(

//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;

declare function noReturnTypeInParam<
    F extends Callback<3|5>
>(f: F): ReturnType<F>

//Expected: const noArg: 3
//Actual  : const noArg: 3
const noArg = noReturnTypeInParam(
    //Return type is 3 
    () => 3
);
//Expected: const withArg: 3
//Actual  : const withArg: 3
const withArg = noReturnTypeInParam(
    //Return type is 3 
    t => 3
);

Playground

//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;

//Forgive the contrived example
declare function withReturnTypeInParam<
    F extends Callback<3|5>,
>(
    f: F & (
        ReturnType<F> extends 3 ?
        unknown :
        [
            "Only 3 allowed, received",
            ReturnType<F>
        ]
    )
): ReturnType<F>

//Expected: const noArg: 3
//Actual  : const noArg: 3
const noArg = withReturnTypeInParam(
    //Return type is 3 
    () => 3
);
//Expected: const withArg: 3
//Actual  : const withArg: 3|5
const withArg = withReturnTypeInParam(
    /*
        Expected: No error
        Actual  :
            Argument of type
            '(t: any) => 3'
            is not assignable to parameter of type
            'Callback<3 | 5> & ["Only 3 allowed, received", 3 | 5]'.
    */
    //Return type is `3`
    //function(t: any): 3
    t => 3
);

//Expected: const withArgAndExplicitReturnTypeAnnotation: 3
//Actual  : const withArgAndExplicitReturnTypeAnnotation: 3|5
const withArgAndExplicitReturnTypeAnnotation = withReturnTypeInParam(
    /*
        Expected: No error
        Actual  :
            Argument of type
            '(t: any) => 3'
            is not assignable to parameter of type
            'Callback<3 | 5> & ["Only 3 allowed, received", 3 | 5]'.
    */
    //We *force* the return type to be 3
    //Return type is `3`
    //function(t: any): 3
    (t) : 3 => (3 as 3)
);

//Expected: const withExplicitlyTypedArg: 3
//Actual  : const withExplicitlyTypedArg: 3
const withExplicitlyTypedArg = withReturnTypeInParam(
    /*
        Expected: No error
        Actual  : No error
    */
    //Return type is `3`
    //function(t: any): 3
    (t : any) => 3
);

Playground

//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;

//Forgive the contrived example
declare function returnTypeAsTypeParam<
    T extends 3|5,
>(
    f: Callback<
        & T
        & (
            T extends 3 ?
            unknown :
            [
                "Only 3 allowed, received",
                T
            ]
        )
    >
): T

//Expected: const noArg: 3
//Actual  : const noArg: 3
const noArg = returnTypeAsTypeParam(
    //Return type is 3 
    () => 3
);
//Expected: const withArg: 3
//Actual  : const withArg: 3
const withArg = returnTypeAsTypeParam(
    /*
        Expected: No error
        Actual  : No error
    */
    //Return type is `3`
    //function(t: any): 3
    t => 3
);

//Expected: const withArgAndExplicitReturnTypeAnnotation: 3
//Actual  : const withArgAndExplicitReturnTypeAnnotation: 3
const withArgAndExplicitReturnTypeAnnotation = returnTypeAsTypeParam(
    /*
        Expected: No error
        Actual  : No error
    */
    //We *force* the return type to be 3
    //Return type is `3`
    //function(t: any): 3
    (t) : 3 => (3 as 3)
);

Playground

Related Issues:

#29133

#32540 (comment)


I hope this is concise enough ><
I've commented all the relevant locations with what is expected and what actually happens.

Snippets 2 (withReturnTypeInParam) and 3 (returnTypeAsTypeParam) are expected to have the same behaviour because they mean the same thing semantically (to me).

They are just expressed differently syntactically.

I just have trouble understanding why snippet 2 breaks unless I have an explicit type annotation. I'm 99% sure it has to be a bug.

And I have trouble understanding why snippet 3 is OK

Metadata

Metadata

Assignees

Labels

Working as IntendedThe behavior described is the intended behavior; this is not a bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions