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

Inconsistent Type Inference in Generic Function #59638

Closed
daniel-sima-abra opened this issue Aug 15, 2024 · 7 comments
Closed

Inconsistent Type Inference in Generic Function #59638

daniel-sima-abra opened this issue Aug 15, 2024 · 7 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@daniel-sima-abra
Copy link

daniel-sima-abra commented Aug 15, 2024

🔎 Search Terms

"type inference", "generics", "generic function", "generic type inference", "unknown inference", "generic unknown"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, including the nightly version.

⏯ Playground Link

link

💻 Code

interface URItoKind<A> {
  readonly ['Maybe']: Maybe<A>;
}
type URIS = keyof URItoKind<any>;
type Kind<URI extends URIS, A> = URItoKind<A>[URI];

class Maybe<A> {
  static of: <A>(a: A) => Maybe<A>;
}

declare function test1<F extends URIS, A, B>(f: (a: A) => Kind<F, B>): Maybe<B>;
declare function test2<F extends URIS>(): <A, B>(f: (a: A) => Kind<F, B>) => Maybe<B>;

const func = (x: number) => Maybe.of(x.toString()); // (x: number) => Maybe<string>
const t1 = test1(func); // Maybe<unknown> - Doesn't work
const t2 = test2()(func); // Maybe<string> - Works Fine

🙁 Actual behavior

Type inference does not work for test1.

🙂 Expected behavior

I see no reason why type inference shouldn't work here same as in test2.

@Andarist
Copy link
Contributor

The first one doesn't really infer F. It only looks like it got inferred because that's the only kind you have in URItoKind. What you might see as "inferred" there is just the constraint.

The second one is different because at the time that you get to inferring A and B TypeScript has already settled on what F is (just based on the constraint). In there, the signature that goes through inference is <A, B>(f: (a: A) => Maybe<B>) => Maybe<B>. You can see how the return type of that callback is already instantiated to Maybe<B> and it's not a deferred generic indexed access (Kind<F, B>) like in the first one.

@daniel-sima-abra
Copy link
Author

You can add readonly ['Array']: Array<A>; into URItoKind and it still works the same way.

@Andarist
Copy link
Contributor

Yes, you can see now how the first function doesn't infer "Maybe" for F here either. My point is that those two examples are very different. One deals with a deferred generic indexed access. The compiler is still trying to figure out what F could be (and it even fails to do it altogether). The other one doesn't suffer from this problem because F is already instantiated in a sense. So this time the compiler infers into a union type.

Your post implied that both are largely the same but they are not. I'm not saying that it's impossible to improve this - that I don't know ;p but those two situations deal with a different level of complexity. My educated guess is that if it would have to be improved it would have to be done here

@jcalz
Copy link
Contributor

jcalz commented Aug 15, 2024

SO question

@daniel-sima-abra
Copy link
Author

Thank you for your explanation. I guess my question is, if this is an expected behavior or a bug, and if there is anything I can do right now to make test1 work.

@RyanCavanaugh
Copy link
Member

While it would be nice for this to work, we'd need #30134 to make this work in the general case where one type parameter depends on the resolution of the previous one. The current algorithm isn't well equipped to insert an extra round of inference here to settle on F before trying to figure out B, and it's not super apparent to me that it'd be tractable to do so (consider the case where URItoKind is very large).

The example is a bit under-specified to give more advice. As written it seems kind of ambiguous, e.g. test2 works but gives different answers depending on the order of Maybe1 and Maybe2.

interface URItoKind<A> {
  readonly ['Maybe1']: Maybe<[A, unknown]>;
  readonly ['Maybe2']: Maybe<[unknown, A]>;
}
type URIS = keyof URItoKind<any>;
type Kind<URI extends URIS, A> = URItoKind<A>[URI];

class Maybe<A> {
    _inferHelper!: A;
  static of: <A>(a: A) => Maybe<A>;
}

declare function test1<F extends URIS, A, B>(f: (a: A) => Kind<F, B>): [F, B, Maybe<B>];
declare function test2<F extends URIS>(): <A, B>(f: (a: A) => Kind<F, B>) => [F, B, Maybe<B>];

declare const func: (x: number) => Maybe<[string, number]>;

const t1 = test1(func); // Maybe<unknown> - Doesn't work

const t2 = test2()(func)[2];

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Aug 15, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Design Limitation" 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 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

5 participants