Skip to content

Can't infer return type of generic function #55435

Closed as not planned
Closed as not planned

Description

🔎 Search Terms

infer generic function return type

🕗 Version & Regression Information

  • Before version 3.5.1 the syntax wasn't supported
  • Before and during version 4.8.4 there was an additional bug for which the type of a (In the example) was [ true, true ]
  • 5.2.0-beta doesn't fix the issue

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.1.6#code/FAehAIHUHsCcGsDO5C8G4WR3gG9jl+ALgJ4AOApuAMIAWpAxvADwAqANOAArikAe+pAdgBNkACm4AucAEN+hAJTgAvAD5wAI2jQANqRmrFHLrwHDwYyUwUqCsAK7kA-DfvhJAMylbEpANzAceGBQcEjgAO4AlvhU4LTQALbEOtxGxLCkiIgR0PyIAbhEZOAAkogActD4AIzM+pQ09MxsDACqyubgLVaqLUZ8QshV4E4eXuSS+Hakyn54BCTkUkrgANol5ZU1AEzKbKUV1QxVqgC6s4EQuAB6Dv5zQTAIyOn4trD8EfwA5gQ04J-EWz4fLzIqlJhTWrLah0RiscC1DqWJSqJgzEGFchqZZrcGQyb2XbrCH2Bijbync64ILXW4AX38QQAItAMvwAOT4cIhMwAIVsP2IUlg+DkWBBzNZiA5XPg-GgYX++GQAKBGIWlE8WgYADE+iZkDrbPxaPhsvw2ABBWBfZA8fqmdJSQQ5LSEcDGuUK-grE51PX2g1mAB0oeFtsk1tt3X+-DcpFgnWGSck-FIADcE1TQeRaNCtQxERJwMjrPC1kM-RLLuAbncLuADlxM-xwDlpG20wBaIWwKTxUh8RNZRJaCJuCKkQTgTOwLI5dVFChamoB4wDcBGk1mnJsNHLNcO0TF0uqT7xxO9Jy9VMZrOL8jTgzLrRaGpFiwx+HHat4Ou-lk2U5bkEHCKIYjiUceFSdJMnNPI5kxLh81fd80SRGM1nhLZwBONgf3uGt-0I4InnCGhW2iBNFnSGVwC+AQEwiWgELwJDShqfdnwLDoqhjAlSDYLDcPRRCNTcHF1hqASiQ4o5KV-WlgDpIA

💻 Code

// Works 👍
{
    type Check<T, P extends (x: any) => boolean> = P extends (x: T) => true ? true : false;

    // Works with complex expressions
    type IsNot1<T> = Check<T, <U>(x: U) => U extends 1 ? false : true>;
    type a = [ IsNot1<2>, IsNot1<1> ];
    //   ^? type a = [ true, false ]

    // Works returning the input
    type IsTrue<T> = Check<T, <T>(x: T) => T>;
    type b = [ IsTrue<true>, IsTrue<false> ];
    //   ^? type b = [ true, false ]
}

// Doesn't work (Bug part)
{
    // Doesn't know its input
    type Call<F extends Function, Args extends readonly unknown[]> = F extends (...args: Args) => infer U ? U : never;
    type c = Call<<T>(x: T) => T, [ 1 ]>
    //   ^? type c = unknown

    // Not even on a one-parameter simplified version
    type Call1<F extends Function, T> = F extends (x: T) => infer U ? U : never;
    type d = Call1<<T>(x: T) => T, 1>
    //   ^? type d = unknown

    // Doesn't work with complex expressions
    type e = Call1<<T>(x: T) => [ T, 2 ], 1>
    //   ^? type e = [ unknown, 2 ]

    // Works when there aren't generics
    type Is1<T> = Call<(x: 1) => true, [ T ]>;
    type f = [ Is1<true>, Is1<1> ];
    //   ^? type f = [ never, true ]
}

🙁 Actual behavior

Types c, d and e[0] are unknown

🙂 Expected behavior

Types c, d and e[0] should have all been 1.
I tried to infer the return type of some generics functions when they got supplied with certain types, I can check wether the return type is something else (true inside Check in this example) but I cannot actually get it

Additional information about the issue

No response

Activity

  1. jcalz commented on Aug 19, 2023

    @jcalz
    Contributor

    TS doesn't have true call types of the sort you'd need for this to work.

    Looks like a duplicate of #52035 (or at least this comment) and refers back to #6606.

  2. AFatNiBBa commented on Aug 20, 2023

    @AFatNiBBa
    Author

    I think this is something that has to be looked into because it has great potential and it almost works.
    Consider the following:

    type FilterObject<T, P extends (v: any, k: any, t: any) => boolean> = { [k in keyof T as P extends (v: T[k], k: k, t: T) => true ? k : never]: T[k] };
    
    type Test = { ab: 1, ac: 2, cb: 3, ad: 1, g: "g" };
    
    type a = FilterObject<Test, (v: 1) => true>;
    //   ^? type a = { ab: 1, ad: 1 }
    
    type b = FilterObject<Test, <K>(_: any, k: K) => K extends `a${string}` ? false : true>;
    //   ^? type b = { cb: 3, g: "g" }
    
    type c = FilterObject<Test, <V, K>(v: V, k: K) => K extends V ? true : false>;
    //   ^? type c = { g: "g" }

    It understands what the return type is, so why can't it infer it

  3. fatcerberus commented on Aug 20, 2023

    @fatcerberus

    It seems like you want specifically #40179

  4. jcalz commented on Aug 20, 2023

    @jcalz
    Contributor

    Yeah #40179 was the one I was searching for but "call types" wasn't mentioned there

  5. steveluscher commented on Aug 22, 2023

    @steveluscher

    I'm also blocked from making one of my more complicated types generic because of this. The satisfies keyword is able to expand all of the possible return types of doThing below, so can we make infer do the same?

    type WrapConfig = { wrap?: boolean };
    
    type ReturnValue<TWrapConfig extends WrapConfig | void, T> =
      TWrapConfig extends object
      ? TWrapConfig['wrap'] extends true ? T[]
      : T
      : T;
    
    function doThing<TWrapConfig extends WrapConfig | void = void>(
      config?: TWrapConfig
    ): ReturnValue<TWrapConfig, string> {
      return (config?.wrap ? ['hi'] : 'hi') as ReturnValue<TWrapConfig, string>;
    }
    
    // Test that the function return type is parametric on the shape of the arguments
    doThing({ wrap: true }) satisfies string[];
    doThing({ wrap: false }) satisfies string;
    doThing({}) satisfies string;
    doThing() satisfies string;
    
    // Test that the function, without type parameter, spans the union of return types
    doThing satisfies ((...args: any[]) => string[] | string);
    
    // Try to infer the return type without supplying the type parameter
    type DoThingReturn = typeof doThing extends (...args: any[]) => infer R ? R : unknown;
    (null as unknown as DoThingReturn) satisfies string[] | string; // ERROR
    Output
    "use strict";
    function doThing(config) {
        return ((config === null || config === void 0 ? void 0 : config.wrap) ? ['hi'] : 'hi');
    }
    // Test that the function return type is parametric on the shape of the arguments
    doThing({ wrap: true });
    doThing({ wrap: false });
    doThing({});
    doThing();
    // Test that the function, without type parameter, spans the union of return types
    doThing;
    null; // ERROR
    Compiler Options
    {
      "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictPropertyInitialization": true,
        "strictBindCallApply": true,
        "noImplicitThis": true,
        "noImplicitReturns": true,
        "alwaysStrict": true,
        "esModuleInterop": true,
        "declaration": true,
        "target": "ES2017",
        "jsx": "react",
        "module": "ESNext",
        "moduleResolution": "node"
      }
    }

    Playground Link: Provided

  6. typescript-bot commented on Aug 25, 2023

    @typescript-bot
    Contributor

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

  7. locked as resolved and limited conversation to collaborators on Oct 22, 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