Microsoft/TypeScript

Methods are not accepted as implementation of abstract members with function type

Open

#51,261 opened on Oct 21, 2022

View on GitHub
 (6 comments) (1 reaction) (0 assignees)TypeScript (48,455 stars) (6,726 forks)batch import
Experience EnhancementHelp WantedSuggestion

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 call method that implements the interface, and others do constructor(public readonly call: Sig, ...otherArgs) so that they have an injectable call member. In either case, the consumer of the api just does myObj.call(blah, blah, blah) and it Does The Right Thing. I am declaring the named Sig to 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

Contributor guide