It's because TypeScript doesn't have polymorphic this types for static methods. There's a longstanding open issue, microsoft/TypeScript#5863, asking for such a feature. For now it's not part of the language, but luckily comments on that issue describe a workaround that should get you the behavior you're looking for:
class Base {
static create<T extends Base>(this: new () => T) {
return new this()
}
}
Here we are making create() a generic method with a parameter of type T extends Base that can only be called on an object whose this context is a zero-arg constructor that returns a value of type T.
Therefore the compiler knows that Sub.create() will return a Sub and that Base.create() will return a Base:
Sub.create().fn() // okay
Base.create().fn() // Property 'fn' does not exist on type 'Base'
This also has the advantage over your existing code in that the following is an error now:
class Oops extends Base {
x: string;
constructor(x: string) {
super();
this.x = x.toUpperCase();
}
}
Oops.create(); // error!
The Oops class does not have a zero-arg constructor, and so the implementation of Oops.create() will end up calling new Oops() without the required parameter and cause a runtime error when it tries to call toUpperCase() on undefined.
Okay, hope that helps; good luck!
Playground link to code