Repository: TypeScript
Issue: `static abstract` methods and properties
Labels: Suggestion,Needs Proposal

This is a continuation of #14600 which had two separate features proposed in the same issue (static members in interfaces *and* abstract static class members)

## Search Terms

static abstract method property properties implement concrete

## Suggestion

Currently, this code is illegal:
```ts
abstract class A {
    static abstract doSomething(): void;
}

// Should be OK
class B extends A {
    static doSomething() { }
}

// Should be an error; non-abstract class failed to implement abstract member
class C extends A {

}
```

It should be legal to have `abstract static` (`static abstract`?) members.

## Use Cases

(what are they?)

## Unresolved Questions

**What calls of abstract static methods are allowed?**

Let's say you wrote a trivial hierarchy
```ts
abstract class A {
    static abstract doSomething(): void;
}
class B extends A {
    static doSomething() { }
}
```

For an expression `x.doSomething()`, what are valid `x`s?

#### Option 1: All of them

Because `this` isn't generic in `static` members, we should simply allow all invocations of abstract static methods. Otherwise code like this would be illegal:
```ts
abstract class A {
  static abstract initialize(self: A): void;
  static createInstance() {
    const a = new this();
    this.initialize(a);
    return a;
  }
}
```

However, this means that TypeScript would miss straight-up crashes:
```ts
// Exception: 'this.initialize' is not a function
A.createInstance();
```

 * **Pros**: Ergonomic
 * **Cons**: Literally allows the runtime-crashing code `A.doSomething()`, which seems like a fairly large design deficit

#### Option 2: None of them

Allowing crashes is bad, so the rule should be that `static abstract` methods simply don't exist from a type system perspective except to the extent that they enforce concrete derived class constraints:

```ts
abstract class A {
    static abstract doSomething(): void;
}
class B extends A {
    static doSomething() { }
}

// Error, can't call abstract method
A.doSomething();

// This call would work, but it'd still be an error
const Actor: typeof A = B;
Actor.doSomething();

function indirect(a: { doSomething(): void }) {
  a.doSomething();
}

// Error, can't use abstract method 'doSomething' to satisfy concrete property
indirect(A);
// OK
indirect(B);
```

This is unergonomic because it'd be impossible to write a function that dealt with an arbitrary complex constructor function without tedious rewriting:
```ts
abstract class Complicated {
    static abstract setup(): void;
    static abstract print(): void;
    static abstract ship(): void;
    static abstract shutdown(): void;
}

function fn(x: typeof Complicated) {
  // Error, can't call abstract method
  x.setup();
  // Error, can't call abstract method
  x.print();
  // Error, can't call abstract method
  x.ship();
  // Error, can't call abstract method
  x.shutdown();
}
```

We know this is a problem because people get tripped up by it constantly when they try to `new` an abstract class:

https://www.reddit.com/r/typescript/comments/bcyt07/dynamically_creating_instance_of_subclass/
https://stackoverflow.com/questions/57402745/create-instance-inside-abstract-class-of-child-using-this
https://stackoverflow.com/questions/49809191/an-example-of-using-a-reference-to-an-abstract-type-in-typescript
https://stackoverflow.com/questions/53540944/t-extends-abstract-class-constructor
https://stackoverflow.com/questions/52358162/typescript-instance-of-an-abstract-class
https://stackoverflow.com/questions/53692161/dependency-injection-of-abstract-class-in-typescript
https://stackoverflow.com/questions/52358162/typescript-instance-of-an-abstract-class

For `abstract` constructor signatures, the recommended fix of using `{ new(args): T }` is pretty good because a) you need to be explicit about what arguments you're actually going to provide anyway and b) there's almost always exactly one signature you care about, but for `static abstract` methods/properties this is much more problematic because there could be *any* number of them.

This also would make it impossible for concrete `static` methods to invoke `abstract static` methods:
```ts
abstract class A {
  static abstract initialize(self: A): void;
  static createInstance() {
    const a = new this();
    // Error
    this.initialize(a);
    return a;
  }
}
```
On the one hand, this is *good*, because `A.createInstance()` definitely *does* crash. On the other hand, this literally the exact kind of code you *want* to write with abstract methods.

One solution would be the existence of an `abstract static` method *with a body*, which would be allowed to invoke other `abstract static` methods but would be subject to invocation restrictions but *not* require a derived class implementation. This is also confusing because it would seem like this is just a "default implementation" that would still require overriding (that is the bare meaning of `abstract`, after all):
```ts
abstract class A {
    abstract static initialize() {
        console.log("Super class init done; now do yours");
    }
}
// No error for failing to provide `static initialize() {`, WAT?
class B extends A { }
```

An alternative would be to say that you can't call *any* `static` method on an `abstract` class, even though that would ban trivially-OK code for seemingly no reason:
```ts
abstract class A {
    static foo() { console.log("Everything is fine"); }
}
// Can't invoke, WAT?
A.foo();
```

 * **Pros**: Correctly prevents all crashes
 * **Cons**: Extremely unergonomic at use cases; effectively bans concrete `static` methods from calling same-class `abstract` methods

#### Option 3: Indirection is sufficient

Why not just split the baby and say that the *direct* form `A.doSomething()` is illegal, but `expr.doSomething()` where `expr` is of type `typeof A` is OK as long as `expr` isn't *exactly* `A`.

This creates the *dread inconsistency* that a trivial indirection is sufficient to defeat the type system and cause a crash:
```ts
// Error; crash prevented!
A.doSomething();
const p = A;
// OK, crashes, WAT?
p.doSomething();
```

It's also not entirely clear what "indirection" means. Technically if you write
```ts
import { SomeStaticAbstractClass as foo } from "./otherModule";
foo.someAbstractMethod();
```
then `foo` isn't *exactly* the declaration of SomeStaticAbstractClass itself - it's an alias. But there isn't really anything distinguishing that from `const p = A` above.

 * **Pros**: Catches "bad by inspection" instances while still allowing "maybe it works" code
 * **Cons**: Extremely inconsistent; simply appears to function as if TypeScript has a bug in it. Unclear what sufficient indirection means in cases of e.g. module imports

#### Option 4: Indirection, but with generics

Maybe a trivial indirection as described in Option 3 isn't "good enough" and we should require you to use a constrained generic instead:
```ts
// Seems like you're maybe OK
function fn<T extends typeof A>(x: T) {
    x.doSomething();
}

// Good, OK
fn(B);
// A fulfills typeof A, fair enough, crashes, WAT?
fn(A);
```

This turns out to be a bad option because many subclasses don't actually meet their base class static constraints due to constructor function arity differences:
```ts
abstract class A {
    constructor() { }
    foo() { }
}

class B extends A {
    constructor(n: number) {
        super();
    }
    bar() { }
}

function fn<T extends typeof A>(ctor: T) {
    // Want to use static methods of 'ctor' here
}
// Error, B's constructor has too many args
fn(B);
```
This isn't even code we *want* people to write -- a generic type parameter used in exactly one position is something we explicitly discourage because it doesn't "do anything".

 * **Pros**: Maybe a slightly better variant of option 3
 * **Cons**: Just a more complicated system with the same failure modes

#### Option 5: Something else?

Anyone able to square this circle?

## Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).