question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow specifying interface implements clauses for the static side of classes

See original GitHub issue

Search Terms

class static side syntax interface type expression

Suggestion

Currently, you can only specify the static side interface of a class with a declaration. From the handbook:

const Clock: ClockConstructor = class Clock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
      console.log("beep beep");
  }
}

When I first wanted to do this (before looking at the docs), I tried to do it in this fashion:

class Clock: ClockConstructor implements ClockInterface {
  ...
}

And I was surprised to see that it didn’t work. My proposal is to make this a valid syntax as it’s more intuitive and understandable.

I believe that forcing class expressions conflicts with TypeScript’s design goals:

  1. Produce a language that is composable and easy to reason about.

Why use a class expression when there is no need for it? Why change your actual JavaScript logic for something that exists only in TypeScript and not in your production code.

Use Cases

Anywhere you need to set the interface of the static side of a class without having a need to specify it as an expression.

Examples

Take the example from the playground:

interface ClockConstructor {
  new (hour: number, minute: number);
}

interface ClockInterface {
  tick();
}

class Clock: ClockConstructor implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
      console.log("beep beep");
  }
}

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:88
  • Comments:37 (3 by maintainers)

github_iconTop GitHub Comments

41reactions
RyanCavanaughcommented, Oct 15, 2019

Ref #14600

We discussed #14600 at length and agreed that the syntax static T in the implements list of a class would be a reasonable place to allow this:

interface X {
  x: string;
}
interface Y {
  y: string;
}
interface Z {
  z: string;
}

// OK example
class C implements Y, static X, Z {
  static x: string = "ok";
  y = "";
  z = "";
}

// Error, property 'x' doesn't exist on 'typeof D'
class D implements static X {
}

Why not static members in interfaces?

#14600 proposed the syntax

interface X {
  static x: string;
}

This is problematic for a couple reasons.

First, it seems to create a totally meaningless thing:

interface X {
  static x: string;
}
function fn(arg: X) {
  // arg has... no members?
}
// Is this a legal call?
// There doesn't seem to be any reason to reject it,
// since 'fn' can't illegally access anything that doesn't exist
fn({ });

Second, there would be no way to access a static member through an interface type that declared it!

interface X {
  static x: string;
}
function fn(arg: X) {
  // How do I get to ctor(arg).x ?
}

You would need to have some auxiliary keyword to turn an interface into the static version of itself:

interface X {
  static x: string;
}
// Syntax: ????
function fn(arg: unstatic X) {
  arg.x; // OK
}

Simply moving the static logic to the class syntax makes this substantially simpler, and (critically) allows classes to implement types that weren’t written using the hypothetical interface I { static property: T } syntax.

Why implements static T, not static implements T ?

In the presumably-usual case of one instance and one static heritage member, this is substantially cleaner:

class WidgetMaker implements Factory, static FactoryCtor {
  // ...
}

vs

class WidgetMaker implements Factory static implements FactoryCtor {
  // ...
}
12reactions
minecrawlercommented, Jun 7, 2020

Here’s my two cents:

I wish I could add static methods to my interfaces. The interfaces describe the API of an object. At the moment, that seems to be an instantiated class, however, when writing the interfaces and classes, we actually implement interfaces on classes, which are more akin to a messy mix of constructor and prorotype. A) Being able to write static methods inside a class alone is proof of that we cannot separate the two when working with class sugar.

From the docs:

In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.

B) I think that means that an interface is the description of the capabilities a class provides, which may include static methods - which we cannot declare with today’s interfaces.

So, if we use A) and B) as a line of thinking, there’s a mismatch. Which is why I would like to suggest a re-alignment between what classes provide and what interfaces provide - in the most intuitive way. Just let an interface look like a class declaration, with the class being the definition. It’s something which was possible way back in other languages (C++) as well, and helps have a clear contract.

interface IA {
  static x: string
  n: number

  static foo(): void
  bar(): void
}

class A implements IA {
  static x: string = 'Hello';
  n: number = 42;

  static foo(): void {}
  bar(): void {}
}

Also, let’s not forget that it is valid code to pass a constructor as parameter, which allows us to do quite a bit of useful stuff and might benefits a lot from having an interface with static declarations. That’s also something which has to be included in such a contract.

function foo<T extends Object>(obj: { new(): T }, ...args: any[]) {
  const instance: T = new (obj.prototype.constructor.bind(obj, ...Array.from(arguments).slice(1)))();
  const constructor = instance.constructor;
  const typeName = constructor.name;

  // ...
}

There were some doubts about this suggestion, so let me address them:

// arg has… no members?

Yes, if there are no non-static methods declared then arg has no members. If that situation makes sense should, however, not be part of a discussion about a language’s capabilities, since it leads to exactly this kind of highly hypothetical situation, which cannot be assessed at a language level. Maybe it does make sense for the logic? Maybe it increases the developer experience? No one can make any good argument without a concrete case, and then we are talking about a concrete case instead of the language, so let’s not talk about it here.

// How do I get to ctor(arg).x ?

arg.constructor.x, which is valid code, and I needed it in several projects, which heavily make use of the type system to create a sound library usage (take a look at this project, for example)

// Is this a legal call? // There doesn’t seem to be any reason to reject it, // since ‘fn’ can’t illegally access anything that doesn’t exist fn({ });

No, it’s not a legal call. {} does not implement the interface, so the type checker should reject it. fn cannot access arg.constructor.x, which is valid JS and valid TS, so there would be an obvious bug in TS if the above was allowed.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Class cannot implement TypeScript interface when using ...
An implements clause on a class declaration tells the compiler to check that instances of the class are assignable to the implemented type....
Read more >
Static Methods Not Supported in TypeScript Interfaces - Medium
Allow specifying interface implements clauses for the static side of classes · Issue #33892 ·…
Read more >
Reading 13: Interfaces
A class implements an interface if it declares the interface in its implements clause, and provides method bodies for all of the interface's...
Read more >
Interfaces - Learning Java [Book] - O'Reilly
A class in Java can declare that it implements an interface and then go about implementing the required methods. A class that implements...
Read more >
Chapter 9. Interfaces - Oracle Help Center
A class may be declared to directly implement one or more interfaces, meaning that any instance of the class implements all the abstract...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found