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

Trying to get the type of a generic method of a class results in a parse error #53410

Closed
mckravchyk opened this issue Mar 21, 2023 · 10 comments
Closed

Comments

@mckravchyk
Copy link

mckravchyk commented Mar 21, 2023

Bug Report

🔎 Search Terms

get type of a generic class method parse error

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about it.

⏯ Playground Link

Playground link with relevant code

💻 Code

class Foo {
    public bar<T extends string>(arg: T): T {

    }
}

type fType = Foo['bar']<'test'> // ; expected

🙁 Actual behavior

I'm not able to get the type of a generic method in a class. It results in a parse error.

🙂 Expected behavior

Getting type of a generic method of a class should be possible.

Additional information

I need this to get the return type of a generic function from within to cast the return result (a workaround to to the bug with generic conditionals not resolving in certain cases).

It's not possible to just create a separate type to hold a reference to the method type because that type is not going to be a generic, and if you try to make a generic type out of it, it will result in the same problem. I'm not aware of any workarounds.

@MartinJohns
Copy link
Contributor

MartinJohns commented Mar 21, 2023

I don't know what <'test'> is supposed to mean in your example. You can get the return type using ReturnType<Foo['bar']>. However, as this is an unbound generic type you'll either get the type of the constraint (if any), or unknown.

@mckravchyk
Copy link
Author

mckravchyk commented Mar 21, 2023

The T generic of the method.

ReturnType<Foo['bar']> would not do, it would only default to what the extends clause is, but I need a more narrow subtype. I can get this with functions but I cannot do this with methods because of this issue.

@MartinJohns
Copy link
Contributor

I don't know what you mean by "a more narrow subtype". At that point there is no more narrow subtype. The function is generic, the type is provided at call site, there's no concrete type available.

@mckravchyk
Copy link
Author

mckravchyk commented Mar 21, 2023

My real world case is that the generic extends { id: string } so the actual generic can have more properties defined in the shape, i.e. { id: string, title: string} In that case, doing ReturnType<Foo['bar']> would default the generic to { id: string } but I need to be able to pass the generic in that return type so I can get { id: string, title: string} for instance. It's more complex than that too, the return type is a conditional that depends on the input generic, generic type narrowing does not work fully, so I need to cast the return type as precisely the return type of the method with the generic argument that was passed so I can bypass the error with casting.

The first code snippet in this comment is what I'm trying to do, except with classes: #53318 (comment)

The core issue here is that it's not possible to get the full type of a class method (as a generic).

@jcalz
Copy link
Contributor

jcalz commented Mar 21, 2023

This is working as intended, not a bug. Instantiation expressions are expressions, not types. You can't write

type X = <T>(x: T) => T[];
type Y = X<string>; // <-- errror

Instead you'd need to write

type X = <T>(x: T) => T[];
declare const x: X;
const y = x<string>;
type Y = typeof y;

You simply must drop to value space to do this; it can't be done purely at the type level. In your code above it could look like

declare const fooBar: Foo['bar'];
type Ftype = typeof fooBar<'test'>

@MartinJohns
Copy link
Contributor

MartinJohns commented Mar 21, 2023

@jcalz Makes it clear what he actually (probably) wanted and what the meaning of the <"test"> was: Wanting to get the return type after providing a concrete type for the generic argument.

@fatcerberus
Copy link

Good catch, @jcalz - I knew it was an attempt at an instantiation expression, but I admit I also thought it was a bug that it didn’t work. See, what’s frustrating is that you can do…

type Fun<T> = (x: T) => T;
type StringFun = Fun<string>;  // (x: string) => string

…and that makes perfect sense, so it does ultimately seem natural to expect this kind of thing to work. It’s that pesky subtle distinction between “generic function type” and “concrete function type with generic type parameters” that gets in the way.

@whzx5byb
Copy link

It is possible to access a method type from prototype, so type fType = typeof Foo.prototype.bar<string> will work for you.

@mckravchyk
Copy link
Author

mckravchyk commented Mar 21, 2023

Thanks, much appreciated. Yes, that was a little confusing, especially since I thought that in the example type x = Foo['bar'] would be a regular type. @whzx5byb 's solution is perfect.

Edit: The prototype solution is not going to work if the class has a generic too.

@fatcerberus
Copy link

@mckravchyk Yeah, there’s a confusing distinction here in that type Foo<T> = () => T is a generic type that evaluates to a concrete, non-generic function type (i.e. it’s a type function), while type Foo = <T>() => T is the concrete, non-generic type of a generic function. Foo['bar'] gives you the latter, so you can’t pass type parameters to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants