Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't infer return type of generic function #55435

Closed
AFatNiBBa opened this issue Aug 19, 2023 · 6 comments
Closed

Can't infer return type of generic function #55435

AFatNiBBa opened this issue Aug 19, 2023 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@AFatNiBBa
Copy link

🔎 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

@jcalz
Copy link
Contributor

jcalz commented Aug 19, 2023

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.

@AFatNiBBa
Copy link
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

@fatcerberus
Copy link

It seems like you want specifically #40179

@jcalz
Copy link
Contributor

jcalz commented Aug 20, 2023

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

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Aug 21, 2023
@steveluscher
Copy link

steveluscher commented Aug 22, 2023

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

steveluscher added a commit to solana-labs/solana-web3.js that referenced this issue Aug 22, 2023
# Summary

This _so_ close to works. You can see the types in action here:

```
const address = 'abc' as Base58EncodedAddress;

/** No encoding */
const noEncodingWithContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
withContext: true,
});
const noEncodingFalseContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    withContext: false,
});
const noEncodingNoContextConfig = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address);

/** Base58 */
const base58EncodingWithContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base58',
    withContext: true,
});
const base58EncodingFalseContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base58',
    withContext: false,
});
const base58EncodingNoContextConfig = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base58',
});

/** Base64 */
const base64EncodingWithContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64',
    withContext: true,
});
const base64EncodingFalseContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64',
    withContext: false,
});
const base64EncodingNoContextConfig = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64',
});

/** Base64-zstd */
const base64ZStdEncodingWithContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64+zstd',
    withContext: true,
});
const base64ZStdEncodingFalseContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64+zstd',
    withContext: false,
});
const base64ZStdEncodingNoContextConfig = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'base64+zstd',
});

/** JSON parsed */
const jsonParsedEncodingWithContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'jsonParsed',
    withContext: true,
});
const jsonParsedEncodingFalseContext = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'jsonParsed',
    withContext: false,
});
const jsonParsedEncodingNoContextConfig = (null as unknown as GetProgramAccountsApi).getProgramAccounts(address, {
    encoding: 'jsonParsed',
});
```

There's _one_ TypeScript bug, though, that makes `rpc.getProgramAccounts()` ultimately lose all of its type information. It's because of this: microsoft/TypeScript#55435 (comment)
@typescript-bot
Copy link
Collaborator

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

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

6 participants