TypeScript: Abstract classes that implement interfaces shouldn't require method signatures

TypeScript Version: 2.7

Search Terms: abstract class implements interface

Code

interface FooFace {
    foo();
}
abstract class FooClass implements FooFace {
    //         ^^^^^^^^
    // Class 'FooClass' incorrectly implements interface 'FooFace'.
    //   Property 'foo' is missing in type 'FooClass'.
    //
    // unless the following is uncommented:
    //abstract foo();
}

// contrast:
abstract class BarClass {
    abstract bar();
}
abstract class BarSubClass extends BarClass {
    // no `abstract bar();` required
}

Expected behavior: Abstract classes that implement interfaces don’t need to define abstract type definitions to satisfy interface.

Actual behavior: They do.

Playground Link

Related Issues:

  • #19847, where typescript fails to see that a superclass does implement a method (contrast this ticket, where typescript fails to see that a class need not implement a method)

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 70
  • Comments: 34 (11 by maintainers)

Most upvoted comments

2 years later…

this seems like an obvious and basic feature for any object-oriented language: how come it hasn’t been implemented ?

Apparently this can be worked-around with class and interface merging:

interface FooFace {
    foo();
}
abstract class FooClass implements FooFace {}
interface FooClass extends FooFace {}

let x: FooClass;
x.foo(); // no error

That seems redundant, though, I still think this should be fixed.

https://github.com/Microsoft/TypeScript/issues/6413#issuecomment-170737032

FYI, just dropping “Ugh can’t believe it’s not fixed already” comments does not change our view of feature priorities. There are outstanding issues with “just doing it” outlined above that haven’t been addressed yet. Please engage in meaningful discourse with the content of the thread so far instead of idly complaining.

Can’t believe this still a problem omg

Uh, It’s going to be three years!

So, how can I help to solve this issue?

If I knew how to solve it, I’d have done so already 🙃. The desirability isn’t the missing piece of this - it’s clear that this would be nice - but rather that we lack a solution without negative side effects that outweigh what is ultimately a pure convenience thing.

We’d need some suggestion for how to implement this in a way that isn’t starkly surprising or bad in any of the outlined scenarios.

Ok came here to say wtf … How can this be missing ?

Another simple option, but it is still redundant is:

export interface IonicLifeCycle {

    ionViewWillEnter();
    ionViewDidEnter();
    ionViewWillLeave();
    ionViewDidLeave();
}

and the abstract class :

export abstract class Base implements IonicLifeCycle {

    abstract ionViewWillEnter();
    abstract ionViewDidEnter();
    abstract ionViewWillLeave();
    abstract ionViewDidLeave();
}

There actually is a way to copy optional members concretely and have a subclass declare that they don’t want to implement it [playground]:

interface I {
  x?: number;
  m?: (x: I) => void;
}
abstract class A implements I {
  x?: number;          // proposal: implicit if no 'x' is specified
  m?: (x: I) => void;  // proposal: implicit if no 'm' is specified
}
class C extends A {
  x?: never;  // "I don't want an x property"
  m?: never;  // "I'm not going to implement this and nobody else better either"
}
class D extends C {
  x?: string;  // error: Type 'string | undefined' is not assignable to type 'undefined'.
  m(x: I) {}   // error: Type '(x: I) => void' is not assignable to type 'undefined'.
}

Today’s state is both error-prone and unergonomic, and the only sound thing to do if a class wants to declare it won’t implement an optional property is to carry around the tombstone to guarantee nothing implements it in an incompatible way. Making that (presumably rare case) explicit with a “never” seems like the best approach.

Same, most OO languages have the similar feature

Same here, I cannot imagine that there’s a problem to solve that, from my p.o.v. it’s just a fix in the compiler and won’t lead to any issue on runtime. Any news about it?

I also encountered this issue. This is what I want to do:


interface IQueue<T> {
	run: (job: Agenda.Job<T>, done: (err?: Error) => void) => Promise<void>;
}

abstract class Queue<T> implements IQueue<T> {
	public abstract run; // This will be implemented in classes
}

class Q extends Queue<IJob> {
	async run(job, done) { } // implementation
}

But run in Q has typings of (any, any), when I expect for it to have typings of (job: Agenda.Job<IJob>, done: (err?: Error) => void)

@RyanCavanaugh Thank you for the thoughtful response, sorry for my delayed reply.

I don’t understand why this option seems bad?

If we copy down optional members and create optional concrete members, then we’re going to copy down optional properties (I’m presupposing here that differentiating properties and methods is right out) and you won’t have any way for you to say “I don’t want to have that property”.

@shicks is exactly right: the way you say “I don’t want to have that property” should be the same as the way you do so when extending the interface, with never:

interface I { x?: number; }
interface J extends I { x: never }

// contrast:
interface K extends I {}
abstract class L implements I {}
class M implements {}

By contrast, you expect K['x'] to be number | undefined. I’m actually completely surprised that L['x'] and M['x'] are invalid rather than also number | undefined.

This is all intuitive enough for required members but we can’t figure out what should happen to optional members. All choices seem bad.

If we copy down optional members and create optional or non-optional abstract members, then there’s no way for your derived class to say “I don’t want to implement that member”.

If we copy down optional members and create optional concrete members, then we’re going to copy down optional properties (I’m presupposing here that differentiating properties and methods is right out) and you won’t have any way for you to say “I don’t want to have that property”.

If we don’t copy down optional members, then we’ve created a “hole” in your type that will allow a derived class to accidently overwrite them with a mismatched type:

interface I { x?: number; }
abstract class A implements I { }
// Would not be an error, but could break at runtime
class B extends A { x: string }

The last problem is of course present today, but doing some of the copying would seem to create the appearance that we were doing more than was actually happening.

Overall it seems like we’d be trading off understandability for convenience here.

@RyanCavanaugh Yes, definitely. In my real-world use case (in which I’m currently using the interface/class merging workaround), my interface actually only has optional methods, I just thought I’d leave that out of this example for simplicity. abstract Class implements Interface should imply interface Class extends Interface.