Why can't typescript infer class's method return type as generic

32 Views Asked by At

This is working correctly:

abstract class X<T extends Record<PropertyKey, any> > {
  abstract index(): T
}

class Y extends X<{name:string}> {
  index() {
    return {name:"Test"}
  }
}

But this causes an error:

abstract class X<T extends Record<PropertyKey, any> > {
  abstract index(): T
}

class Y extends X {
  index():{name:string} {
    return {name:"Test"}
  }
}

The error:

Generic type 'X' requires 1 type argument(s).ts(2314)

This might not be a problem but what if the class has more generic. Like this I need to retype EmptyObject for the generic even though it's the default.

type EmptyObject = Record<PropertyKey, never>

abstract class X<E extends Record<PropertyKey, any> = EmptyObject, T extends Record<PropertyKey, any> = EmptyObject> {
  abstract index(data: E): T
}

// I want to just do "extends X" and make typescript infer the rest
class Y extends X<EmptyObject, {name:string}> {
  index(data: EmptyObject): {name:string} {
    return {name:"Test"}
  }
}
2

There are 2 best solutions below

0
jcalz On BEST ANSWER

TypeScript cannot currently infer a class's generic type argument from its implementation. It's a missing feature, requested in microsoft/TypeScript#39180. It's listed as "awaiting more feedback", meaning that they want to see a lot of community engagement and support for the feature before they consider implementing it. It's been around for a long time and there has been very little engagement, so I'd say it's quite unlikely for it ever to appear in the language. You could give it a and explain your use case, but it's probably not going to change anything.

Type inference generally does not work that way in TypeScript. Generic type arguments are only inferred when you call a generic function. They are not inferred contextually. For the foreseeable future, you will need to manually specify the generic type arguments yourself.

1
Harrison On

What if you modified the second code block to:

abstract class X<T = Record<PropertyKey, any> > {
  abstract index(): T
}

class Y extends X {
  index():{name:string} {
    return {name:"Test"}
  }
}

I went straight to the third block, with some tinkering around the types, I got to this:

type BaseObjectType = Record<PropertyKey, any>

abstract class X<E = BaseObjectType> {
  abstract index(data: E): E;
}

class Y<T extends BaseObjectType> extends X<T> {
  index(data: T):T {
    return {
      ...data,
      name: "Test",
    };
  }
}

Or if you wanted to maintain the BaseObjectType, you could do either

type BaseObjectType = Record<PropertyKey, any>

or

type BaseObjectType = Record<PropertyKey, any> & { name: "Test" };

And combine it with just returning the name property from index

abstract class X<E = BaseObjectType> {
  abstract index(data: E): E;
}

class Y<T extends BaseObjectType> extends X<T> {
  index(data: T): BaseObjectType {
    return {
      name: "Test",
    };
  }
}