Skip to content

Incorrectly inferred return type of method of a generic class #60406

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

Closed
tavwizard opened this issue Nov 4, 2024 · 3 comments
Closed

Incorrectly inferred return type of method of a generic class #60406

tavwizard opened this issue Nov 4, 2024 · 3 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@tavwizard
Copy link

🔎 Search Terms

function return type incorrectly inferred from type argument

🕗 Version & Regression Information

Looks like this is the behavior in every version I tried and I can't find information about inferring types for the non-generic methods in generic classes.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.6.3#code/JYOwLgpgTgZghgYwgAgJIhgewDwBVkQAekIAJgM7LlhSgDmyAPlTfQNoC6AfMgN4CwAKGQjkANzgAbYKThhMUAFzIAFAEpkAXh64A3EIC+QoQklxylAMIBGPAWIQyldFmzVaIBs3ftuPAcKioFgA-Mp6QqLiUjJyCtbqfJFRolAQYACuUCDIYAAWwOQAdMGYIUUS0rLyUPqBIkb10VVxUABM6uFsAOSVsTXdHEzIGWQQMKAQpElNUWmZ2bkFxaXlfdUKdVGNjSaYINTICFrIIBAA7sg22C44Pp5cXOp1CPuHwGInCCUYZRUxG1qyAA9MDcgBPAAOKEKqkS2hYHjoGmYo1I40mpD2BzA4msX3+LRqCTUuhBYLAUJhlBU8J49y8iN8KJGYwmZyxgleOPEbQJ61aHVJ5Ih0OQsNpGgRDJZaIxHKEQA

💻 Code

interface Info<T extends string | string[]> {
    validator: () => T;
}

class C1<T extends Info<string | string[]>> {
    info?: T;
    validator1() {
        return this.info?.validator;
    }
    validator2(): T['validator'] | undefined {
        return this.info?.validator;
    }
}

const c = new C1<Info<string>>();
const iv = c.info?.validator; // type is (() => string) | undefined
const v1 = c.validator1(); // type is (() => string | string[]) | undefined
const v2 = c.validator2(); // type is (() => string) | undefined

🙁 Actual behavior

Return type of validator method for class C1<Info<string>> is (() => string | string[]) | undefined

🙂 Expected behavior

Return type of validator method for class C1<Info<string>> should be (() => string) | undefined

Additional information about the issue

Looks like the return type for generic class methods is only inferred via the type argument constraint and does not change in a particular instance of the class.

@jcalz
Copy link
Contributor

jcalz commented Nov 4, 2024

Seems like #33181. Indexed accesses into generic objects with non-generic keys tend to widen those generics to their constraints first. So if you have x of generic type X extends {y: string}, then x.y will almost certainly be just string and not X["y"]. This isn't "incorrect" per se (although depending on the variance of the generic then it can be incorrect), but it's wider than you were hoping for. I don't know there's much they can do here other than saying "if you want that behavior you should annotate things as such, like you did with validator2."

@tavwizard
Copy link
Author

This is a very simple example and it is easy to set type but lets add some additional code.
So the problem is still with us here

interface Info<T extends string | string[]> {
    validator: () => T;
}

class C1<T extends Info<string | string[]>> {
    info?: T;
    validator1() {
        return this.info?.validator;
    }
    validator2(): T['validator'] | undefined {
        return this.info?.validator;
    }
    message1() {
        return this.validator1()?.();
    }
    message2() {
        return this.validator2()?.();
    }
    message3(): ReturnType<T['validator']> | undefined {
        // error, Type 'string | string[] | undefined' is not assignable to type 'ReturnType<T["validator"]> | undefined'.
        // Type 'string' is not assignable to type 'ReturnType<T["validator"]>'.(2322)
        return this.validator2()?.();
    }
}

const c = new C1<Info<string>>();
const iv = c.info?.validator; // type is (() => string) | undefined
const v1 = c.validator1(); // type is (() => string | string[]) | undefined
const v2 = c.validator2(); // type is (() => string) | undefined
const m1 = c.message1(); // fail, type is string | string[] | undefined 
const m2 = c.message2(); // fail, type is string | string[] | undefined 
const m3 = c.message3(); // ok, type is string | undefined but there is a compile error in message3

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Nov 4, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Not a Defect" 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 Nov 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants