Methods are not accepted as implementation of abstract members with function type
#51,261 opened on Oct 21, 2022
Description
Bug Report
I am declaring a named type with the signature for an abstract method(ish)* on a base class. But I am not able to use the Signature type directly to declare the method, instead I need to pluck off Parameters<Sig> and ReturnType<Sig> to declare a "real" method. Given how methods usually behave in TS interfaces, this is certainly surprising.
- To expand on the motivation for doing this: The real-world use case involves a "pluggable" method where some subclasses have a
callmethod that implements the interface, and others doconstructor(public readonly call: Sig, ...otherArgs)so that they have an injectablecallmember. In either case, the consumer of the api just doesmyObj.call(blah, blah, blah)and it Does The Right Thing. I am declaring the namedSigto avoid repeating it everywhere.
🔎 Search Terms
abstract method member function TS2425
🕗 Version & Regression Information
When did you start seeing this bug occur?
Repros in oldest and nightly in playground.
This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
"Bugs" that have existed in TS for a long time are very likely to be FAQs; refer to https://github.com/Microsoft/TypeScript/wiki/FAQ#common-bugs-that-arent-bugs
I'll admit this is certainly similar to the third from the last issue in that list:
However, that summary and the linked issue are all about bivariance/invariance/contravariance which controls matching semantics for functions with different signatures. In this case, the signatures are identical, so that difference doesn't apply. This appears to be a different difference (at least to me). If this is intentional, perhaps the FAQ should be updated to focus less on variance. Ideally, explaining why even identical signatures are rejected.
Issue #27965 also seems related, but that case is a bit different. It was declaring an actual member, which may exist in JS land on the base, while this issue is specifically about adding an abstract member, which I would expect to be more like adding something to the interface portion of a class type, since it explicitly isn't set by the base in JS. Also that case tried to use any as the "function" type of the member, and I am using an exact-matching function type.
⏯ Playground Link
Playground link with relevant code
💻 Code
type Sig = (n: number) => number
abstract class Base {
abstract member: Sig;
abstract method(...args: Parameters<Sig>): ReturnType<Sig> // This works, but is something only a type theorist would love.
}
class DerivedMethods extends Base {
member(n: number) { return n; } // <---- Error here ☹
method(n: number) { return n; }
}
class DerivedMembers extends Base {
member!: Sig;
method!: Sig; // Note: this works in the opposite direction!
}
🙁 Actual behavior
Class 'Base' defines instance member property 'member', but extended class 'DerivedMethods' defines it as instance member function.
🙂 Expected behavior
Compile without error