I have the following code example:
class Stupid {
private cache: Map<any, any> = new Map();
get<T>(key: string): T {
return this.cache.get(key);
};
}
class Smart<T> extends Stupid {
get(key: string): T {
super.get<T>(key);
}
}
I have valid reasons for wrapping the Stupid class (other than the generics) which are not apparent in this barebone reproduction of the problem, nonetheless I would like to know if this is somehow possible.
Please also note that the class Stupid is a node dependency. I can't change that implementation.
The purpose would be to not have a generic type argument on each method, but to move it to the wrapping class (class Smart) and use that in super calls, to provide the required generic to the extended class (class Stupid).
Please feel free to use this playground for experimentation.
The
get()method inStupidhas the problematic generic call signature<T>(key: string) => T, which means that it will return a value of any typeTthat the caller specifies. This is demonstrably impossible:In the above, you're calling
stupid.get("xyz")twice at runtime, but the compiler thinks the first call returns anumberand the second one returns astring, which is exceptionally unlikely. The only reason the method implementation type checks is because the return type ofcache.get()is the intentionally unsafeanytype.Let's not worry too much about the type safety of
Stupid.get()and instead look at the problem you're having when subclassing it:The reason that doesn't work is because subclasses must be assignable to their superclasses. If
Smart<T> extends Stupid, then everySmart<T>instance is also aStupidinstance, and should be usable accordingly:Smart<number>.get()returns anumber, butStupid.get()returns any type the caller wants, such asstring. These are not compatible behaviors, and so the compiler complains. If you wantSmartto truly be a subclass ofStupid, you'd need to makeget()generic the same way as it is inStupid, which is not what you're trying to do.So how can we proceed? Presumably we shouldn't worry too much about type safety, since there's no way to guarantee that
get()returns the proper type for eitherStupidorSmart. Instead we will just do what's necessary to make it compile. Usually that will require something like a type assertion to tell the compiler that some value is of some type.Here's one way to do it:
What we're doing is pretending that
Stupidbehaves likeSmart. First we create anISmart<T>interface which is the same asStupidexcept that the generic is moved to where you want it. Then we assign theStupidconstructor to a new variable calledFakeSmartand assert thatFakeSmarthas the type of a generic constructor. SoFakeSmartis justStupidat runtime, but the compiler thinks it is of a proper superclass ofSmart<T>.And then we declare that
Smart<T> extends FakeSmart<T>. At runtime this is that same as your original code, but now at compile time there are no errors because theget()method ofSmart<T>is compatible with theget()method ofFakeSmart<T>. Note that the callsuper.get(key)doesn't take a type argument becauseFakeSmart.get()doesn't take a type argument.So there you go. Personally I'd be worried about code like this where you can tell the compiler simultaneous contradictory things about the types, but if you're not the one writing
Stupidthen I guess the best you can do is wrap it in something more reasonable.Playground link to code