Skip to content

ReturnType<> of function intersection inconsistent. #31601

Closed

Description

@AnyhowStep

TypeScript Version: TS Playground version (3.4.1), 3.5.0-dev.20190523

Search Terms: ReturnType, function, intersection

Code

declare const f: & (() => { x: 4 }) & (() => { y: 3 });
//OK
//z is of type {x:4}
const z = f();
//OK
const hasX: { x: 4 } = z;
//Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
const hasY: { y: 3 } = z;

/**
 * Expected:
 * OK
 * z is of type {x:4}
 * 
 * Actual:
 * Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
 * z is of type {y:3}
 */
const z2: ReturnType<typeof f> = f();
/**
 * Expected: OK
 * Actual: Property 'x' is missing in type '{ y: 3; }' but required in type '{ x: 4; }'.
*/
const hasX2: { x: 4 } = z2;
/**
 * Expected: Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
 * Actual: OK
*/
const hasY2: { y: 3 } = z2;

Expected behavior:

Since function intersections are implemented as overloads,

always return the first return type.

Actual behavior:

Inconsistent return type.

Playground Link: Playground

Related Issues:

Activity

  1. jack-williams commented on May 26, 2019

    @jack-williams
    Collaborator

    I think this is working as intended. Conditional type inference is done from the last overload not the first:

    #21496:

    When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).

  2. AnyhowStep commented on May 26, 2019

    @AnyhowStep
    ContributorAuthor

    Hmmm,

    If the intention is to get the "most permissive catch-all case", wouldn't using the union of return types of each individual overload be better?

    I'm pretty sure I've written overloads where each overload returned different types, and the last one wasn't a "permissive catch-all". Especially for generic functions, where allowing such an overload would makes type checks useless, because the result would be something like unknown or unknown[].


    I guess I'll just treat this as a gotcha' and be careful with how I structure overloads from now on

  3. fatcerberus commented on May 26, 2019

    @fatcerberus

    I’m assuming by the “last overload”, it means the implementation signature, which is the most permissive case by definition.

  4. AnyhowStep commented on May 27, 2019

    @AnyhowStep
    ContributorAuthor

    I just tested it,

    function foo(x : 1): number;
    function foo(x : 2): string;
    function foo(x : 1|2): number | string {
        throw new Error("Not implemented");
    }
    
    //This is `string`
    type r = ReturnType<typeof foo>;
    
    type ReturnType_1<T> = (
        T extends (x: 1) => infer R ?
        R :
        never
    );
    
    /*
        Kind of sad this is `never` and not `number`.
        If we really are talking overloads here,
        then shouldn't ReturnType_1<> try to find the
        overload that matches the type `(x : 1) => any`?
    */
    //never
    type rt_1 = ReturnType_1<typeof foo>;
    
    type ReturnType_2<T> = (
        T extends (x: 2) => infer R ?
        R :
        never
    );
    
    //string
    type rt_2 = ReturnType_2<typeof foo>;

    Playground

  5. typescript-bot commented on May 30, 2019

    @typescript-bot
    Contributor

    This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  6. AnyhowStep commented on May 30, 2019

    @AnyhowStep
    ContributorAuthor

    Wait, what about this?

    #31601 (comment)

    Surely it's worth considering =/

    It would be nice if ReturnType_1<> was number and not never.
    Is there a reason to not make ReturnType_1<> return number?

  7. AnyhowStep commented on Nov 30, 2019

    @AnyhowStep
    ContributorAuthor

    I randomly came across this,
    image

    It made me think of how TS implements function overloads using intersection types.
    It made me chuckle.

  8. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions