Description
π Search Terms
is:issue "defines it as instance member function"
π Version & Regression Information
5.1.6
β― Playground Link
π» Code
Given this example of trying to create a superclass that allows you to register functions, and subclasses that define whether these functions must be async or sync:
type Entry = () => any;
type MaybeEntry = Entry | undefined;
type EntrySyncOrAsync<IsAsync> = IsAsync extends true ? Promise<Entry> : Entry;
type MaybeEntrySyncOrAsync<IsAsync> = IsAsync extends true ? Promise<MaybeEntry> : MaybeEntry;
class Registry<IsAsync extends true | false = false> {
private registry: Map<string, () => EntrySyncOrAsync<IsAsync>> = new Map();
public registerEntry = (
id: string,
getter: () => EntrySyncOrAsync<IsAsync>
) => {
this.registry.set(id, getter);
};
// Define getEntry as an arrow function. If we don't use an arrow function, calls to
// super.getEntry from a subclass will bind `this` to the subclass. We want to keep it
// bound to the superclass so we can access the private `registry`
public getEntry: (id: string) => MaybeEntrySyncOrAsync<IsAsync> = (
id
) => {
const getDefinition = this.registry.get(id); // Without using arrow function, this errors because `this.registry` is undefined
if (!getDefinition) {
return undefined as IsAsync extends true ? Promise<undefined> : undefined;
}
return getDefinition();
};
}
export class RegistryPublic extends Registry<true> {
// Define getEntry as a regular class method. If we use an arrow function to please the Typescript compiler,
// then `this` will get rebound to the subclass instead of the superclass
public async getEntry(
id: string
): Promise<MaybeEntry> {
return await super.getEntry(id);
}
}
export class RegistryServer extends Registry<false> {
public getEntry(id: string): MaybeEntry {
return super.getEntry(id);
}
}
π Actual behavior
RegistryPublic
and RegistryServer
error with Class 'Registry<type entry>' defines instance member property 'getEntry', but extended class 'RegistryPublic' defines it as instance member function.(2425)
π Expected behavior
No error. This code executes perfectly fine at runtime.
Ways to alter this code to get Typescript to accept it are:
- Changing the superclass to define
getEntry
as a regular method instead of an arrow function - Changing the subclasses to define
getEntry
as an arrow function instead of a regular method - Changing the name of the superclass's arrow function to something like
_getEntry
and callingsuper._getEntry
in the subclass'sgetEntry
But any of these approaches cause this
to end up rebound to the subclass, losing access to this.registry
and causing the code to crash at runtime.
Additional information about the issue
This is similar to #27965, but I don't think it's a duplicate specifically because of this behavior around this
binding. There doesn't seem to be another way to properly bind this
to a superclass than this arrow function overriding pattern.