As a continuation #2947 issue, which allows the abstract
modifier on method declarations but disallows it on static methods declarations, I suggest to expand this functionality to static methods declarations by allowing abstract static
modifier on method declarations.
The related problem concerns static
modifier on interface methods declaration, which is disallowed.
In some cases of using the abstract class and its implementations I may need to have some class-dependent (not instance-dependent) values, that shoul be accessed within the context of the child class (not within the context of an object), without creating an object. The feature that allows doing this is the static
modifier on the method declaration.
For example (example 1):
abstract class AbstractParentClass {
}
class FirstChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class FirstChildClass';
}
}
class SecondChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class SecondChildClass';
}
}
FirstChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class FirstChildClass'
SecondChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class SecondChildClass'
But in some cases I also need to acces this value when I only know that the accessing class is inherited from AbstractParentClass, but I don't know which specific child class I'm accessing. So I want to be sure, that every child of the AbstractParentClass has this static method.
For example (example 2):
abstract class AbstractParentClass {
}
class FirstChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class FirstChildClass';
}
}
class SecondChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class SecondChildClass';
}
}
abstract class AbstractParentClassFactory {
public static getClasses(): (typeof AbstractParentClass)[] {
return [
FirstChildClass,
SecondChildClass
];
}
}
var classes = AbstractParentClassFactory.getClasses(); // returns some child classes (not objects) of AbstractParentClass
for (var index in classes) {
if (classes.hasOwnProperty(index)) {
classes[index].getSomeClassDependentValue(); // error: Property 'getSomeClassDependentValue' does not exist on type 'typeof AbstractParentClass'.
}
}
As a result, the compiler decides that an error occurred and displays the message: Property 'getSomeClassDependentValue' does not exist on type 'typeof AbstractParentClass'.
In some cases, the interface logic implies that the implementing classes must have a static method, that has the predetermined list of parameters and returns the value of exact type.
For example (example 3):
interface Serializable {
serialize(): string;
static deserialize(serializedValue: string): Serializable; // error: 'static' modifier cannot appear on a type member.
}
When compiling this code, an error occurs: 'static' modifier cannot appear on a type member.
The solution to both problems (1.1 and 1.2) is to allows the abstract
modifier on static method declarations in abstract classes and the static
modifier in interfaces.
The implementation of this feature in JavaScript should be similar to the implementation of interfaces, abstract methods and static methods.
This means that:
For example, this TypeScript code (example 4):
interface Serializable {
serialize(): string;
static deserialize(serializedValue: string): Serializable;
}
abstract class AbstractParentClass {
public abstract static getSomeClassDependentValue(): string;
}
class FirstChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class FirstChildClass';
}
}
class SecondChildClass extends AbstractParentClass implements Serializable {
public serialize(): string {
var serialisedValue: string;
// serialization of this
return serialisedValue;
}
public static deserialize(serializedValue: string): SecondChildClass {
var instance = new SecondChildClass();
// deserialization
return instance;
}
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class SecondChildClass';
}
}
should be compiled to this JS code:
var AbstractParentClass = (function () {
function AbstractParentClass() {
}
return AbstractParentClass;
}());
var FirstChildClass = (function (_super) {
__extends(FirstChildClass, _super);
function FirstChildClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
FirstChildClass.getSomeClassDependentValue = function () {
return 'Some class-dependent value of class FirstChildClass';
};
return FirstChildClass;
}(AbstractParentClass));
var SecondChildClass = (function (_super) {
__extends(SecondChildClass, _super);
function SecondChildClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
SecondChildClass.prototype.serialize = function () {
var serialisedValue;
// serialization of this
return serialisedValue;
};
SecondChildClass.deserialize = function (serializedValue) {
var instance = new SecondChildClass();
// deserialization
return instance;
};
SecondChildClass.getSomeClassDependentValue = function () {
return 'Some class-dependent value of class SecondChildClass';
};
return SecondChildClass;
}(AbstractParentClass));
abstract static
modifierabstract static
or static
modifierstatic
modifierstatic
modifierAll the other properties of abstract static
modifier should be inherited from abstract
and static
modifiers properties.
All the other properties of static
interface method modifier should be inherited from the interface methods and static
modifier properties.
abstract static
modifier of an abstract class method and static
modifier of an interface method.abstract static
modifier of an abstract class method and static
modifier of an interface method. Proposed feature have to fix these errors.Static interface methods generally don't make sense, see #13462
Holding off on this until we hear more feedback on it.
A major question we had when considering this: Who is allowed to call an abstract static
method? Presumably you can't invoke AbstractParentClass.getSomeClassDependentValue
directly. But can you invoke the method on an expression of type AbstractParentClass
? If so, why should that be allowed? If not, what is the use of the feature?
Static interface methods generally don't make sense, see #13462
In the discussion of #13462 I didn't see why static
interface methods are senseless. I only saw that their functionality can be implemented by other means (which proves that they are not senseless).
From a practical point of view, the interface is a kind of specification - a certain set of methods that are mandatory for their implementation in a class that implements this interface. The interface doesn't only define the functionality an object provides, it is also a kind of contract.
So I don't see any logical reasons why class
may have static
method and interface
doesn't.
If we follow the point of view that everything that can already be implemented in the language does not need any improvements of syntax and other things (namely, this argument was one of the main points in the discussion of #13462), then guided by this point of view, we can decide that the while
cycle is redundant because it can be implemented using for
and if
together. But we are not going to do away with while
.
A major question we had when considering this: Who is allowed to call an
abstract static
method? Presumably you can't invokeAbstractParentClass.getSomeClassDependentValue
directly. But can you invoke the method on an expression of typeAbstractParentClass
? If so, why should that be allowed? If not, what is the use of the feature?
Good question. Since you were considering this issue, could you please share your ideas about this?
It only comes to my mind that on the compiler level the case of direct call of AbstractParentClass.getSomeClassDependentValue
will not be tracked (because it cannot be tracked), and the JS runtime error will occur. But I'm not sure whether this is consistent with the TypeScript ideology.
I only saw that their functionality can be implemented by other means (which proves that they are not senseless).
Just because something is implementable, doesn't mean it makes sense. 😉
abstract class Serializable {
abstract serialize (): Object;
abstract static deserialize (Object): Serializable;
}
I want to force implementation of static deserialize method in Serializable's subclasses.
Is there any workaround to implement such behaviour?
What's the latest take on this? I just tried to write an abstract static property of an abstract class and was genuinely surprised when it wasn't allowed.
What @patryk-zielinski93 said. Coming from some years of projects in PHP that we do convert to TS we DO want static methods in interfaces.
A very common use case this will help with is React components, which are classes with static properties such as displayName
, propTypes
, and defaultProps
.
Because of this limitation, the typings for React currently include two types: a Component
class, and ComponentClass
interface including the constructor function and static properties.
To fully type check a React component with all its static properties, one has two use both types.
Example without ComponentClass
: static properties are ignored
import React, { Component, ComponentClass } from 'react';
type Props = { name: string };
{
class ReactComponent extends Component<Props, any> {
// expected error, but got none: displayName should be a string
static displayName = 1
// expected error, but got none: defaultProps.name should be a string
static defaultProps = { name: 1 }
};
}
Example with ComponentClass
: static properties are type checked
{
// error: displayName should be a string
// error: defaultProps.name should be a string
const ReactComponent: ComponentClass<Props> = class extends Component<Props, any> {
static displayName = 1
static defaultProps = { name: 1 }
};
}
I suspect many people are currently not using ComponentClass
, unaware that their static properties are not being type checked.
Related issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16967
Is there any progress on this? Or any workaround for constructors on abstract classes?
Here's an example:
abstract class Model {
abstract static fromString(value: string): Model
}
class Animal extends Model {
constructor(public name: string, public weight: number) {}
static fromString(value: string): Animal {
return new Animal(...JSON.parse(value))
}
}
@roboslone
class Animal {
static fromString(value: string): Animal {
return new Animal();
}
}
function useModel<T>(model: { fromString(value: string): T }): T {
return model.fromString("");
}
useModel(Animal); // Works!
Agreed that this is an extremely powerful and useful feature. In my view, this feature is what makes classes 'first class citizens'. Inheritance of class/static methods can and does make sense, particularly for the static method factory pattern, which has been called out here by other posters several times. This pattern is particularly useful for deserialization, which is a frequently performed operation in TypeScript. For instance, it makes perfect sense to want to define an interface that provides a contract stating that all implementing types are instantiable from JSON.
Not allowing abstract static factory methods requires the implementor to create abstract factory classes instead, unnecessarily doubling the number of class definitions. And, as other posters have pointed out, this is a powerful and successful feature implemented in other languages, such as PHP and Python.
New to Typescript, but I am also surprised this isn't allowed by default, and that so many people are trying to justify not adding the feature with either:
Another simple use case: (ideal way, which does not work)
import {map} from 'lodash';
export abstract class BaseListModel {
abstract static get instance_type();
items: any[];
constructor(items?: any[]) {
this.items = map(items, (item) => { return new this.constructor.instance_type(item) });
}
get length() { return this.items.length; }
}
export class QuestionList extends BaseListModel {
static get instance_type() { return Question }
}
Instead, the list instance ends up exposing the instance type directly, which is not relevant to the list instance itself and should be accessed through the constructor. Feels dirty. Would feel much dirtier if we were talking about a record model, in which specify a set of default values via the same sort of mechanism, etc.
I was really excited to use real abstract classes in a language after being in ruby/javascript for so long, only to end up dismayed by the implementation restrictions - The above example was just my first example of encountering it, though I can think of many other use cases where it would be useful. Mainly, just as a means of creating simple DSL's/configuration as part of the static interface, by ensuring that a class specifies a default values object or whatever it may be. - And you may be thinking, well that's what interfaces are for. But for something simple like this, it doesn't really make sense, and only ends up in things being more complicated than they need to be (the subclass would need to extend the abstract class and implement some interface, makes naming things more complicated, etc).
I had this similar requirement for my project two times. Both of them were related to guarantee that all subclasses provide concrete implementations of a set of static methods. My scenario is described below:
class Action {
constructor(public type='') {}
}
class AddAppleAction extends Action {
static create(apple: Apple) {
return new this(apple);
}
constructor(public apple: Apple) {
super('add/apple');
}
}
class AddPearAction extends Action {
static create(pear: Pear) {
return new this(pear);
}
constructor(public pear: Pear) {
super('add/pear');
}
}
const ActionCreators = {
addApple: AddAppleAction
addPear: AddPearAction
};
const getActionCreators = <T extends Action>(map: { [key: string]: T }) => {
return Object.entries(map).reduce((creators, [key, ActionClass]) => ({
...creators,
// To have this function run properly,
// I need to guarantee that each ActionClass (subclass of Action) has a static create method defined.
// This is where I want to use abstract class or interface to enforce this logic.
// I have some work around to achieve this by using interface and class decorator, but it feels tricky and buggy. A native support for abstract class method would be really helpful here.
[key]: ActionClass.create.bind(ActionClass)
}), {});
};
Hope this can explain my requirements. Thanks.
For anyone looking for a workaround, you can use this decorator:
class myClass {
public classProp: string;
}
interface myConstructor {
new(): myClass;
public readonly staticProp: string;
}
function StaticImplements <T>() {
return (constructor: T) => { };
}
@StaticImplements <myConstructor>()
class myClass implements myClass {}
const getActionCreators = <T extends Action>(map: { [key: string]: {new () => T, create() : T}}) => {
since we're calling through a type parameter, the actual base class with its hypothetical abstract
static
factory method will never be involved. The types of the subclasses are related structurally when the type parameter is instantiated.
What I'm not seeing in this discussion are cases where the implementation is invoked by way of the type of the base class as opposed to some synthesized type.
It is also relevant to consider that, unlike in languages such as C# where abstract
members are actually _overridden_ by their implementations in deriving classes, JavaScript member implementations, instance or otherwise, never override inherited numbers, but rather _shadow_ them.
My 5 cents here, from a user perspective point of view is:
For the interfaces case, it should be allowed the _static_ modifier
Interfaces define contracts, to be fulfilled by implementing classes. This mean that interfaces are abstractions on a higher level than classes.
Right now what I can see is that classes can be more expressive than interfaces, in a way that we can have static method in classes but we can not enforce that, from the contract definition itself.
In my opinion that feels wrong and hindering.
Is there any technical background making this language feature hard or impossible to implement?
cheers
Interfaces define contracts, to be fulfilled by implementing classes. This mean that interfaces are abstractions on a higher level than classes.
Classes have two interfaces, two implementation contracts, and there is no getting away from that.
There are reasons why languages like C# don't have static members to interfaces as well. Logically, interfaces are the public surface of an object. An interface describes the public surface of an object. That means it doesn't contain anything that isn't there. On instances of classes, static methods are not present on the instance. They only exist on the class/constructor function, therefore they should only be described in that interface.
On instances of classes, static methods are not present on the instance.
can you elaborate on that? This is not C#
They only exist on the class/constructor function, therefore they should only be described in that interface.
That we got it and it's what we want to change.
Interfaces define contracts, to be fulfilled by implementing classes. This mean that interfaces are abstractions on a higher level than classes.
I fully agree on that. See the Json interface example
Hi @kitsonk, please could you elaborate more on:
Classes have two interfaces, two implementation contracts, and there is no getting away from that.
I did not understand that part.
Logically, interfaces are the public surface of an object. An interface describes the public surface of an object. That means it doesn't contain anything that isn't there.
I agree. I don't see any contradiction to what I said. I even said more. I said an interface is a contract for a class to be fulfilled.
On instances of classes, static methods are not present on the instance. They only exist on the class/constructor function, therefore they should only be described in that interface.
Not sure I understood this right, it is clear what it say, but not why you say it. I can explain why I think is still valid my statement. I am passing along interfaces as parameters all the time to my methods, this mean I have access to interface methods, please note that I am not using interfaces here as a way to define data structure but to define concrete objects that get created/hydrated elsewhere. So when I have:
fetchData(account: SalesRepresentativeInterface): Observable<Array<AccountModel>> {
// Method Body
}
In that method body, I can indeed use account
methods. What I would like to be possible to do is to be able to use static methods from SalesRepresentativeInterface
which were enforced already to be implemented in whatever the class I am receiving in account
. Maybe I am having a very simplistic or completely wrong idea on how to use the feature.
I think that allowing the static
modifier will allow me to do something like: SalesRepresentativeInterface.staticMethodCall()
Am I wrong ?
cheers
@davidmpaz : your syntax isn't quite right, but close. Here's an example usage pattern:
interface JSONSerializable {
static fromJSON(json: any): JSONSerializable;
toJSON(): any;
}
function makeInstance<T extends JSONSerializable>(cls: typeof T): T {
return cls.fromJSON({});
}
class ImplementsJSONSerializable implements JSONSerializable {
constructor(private json: any) {
}
static fromJSON(json: any): ImplementsJSONSerializable {
return new ImplementsJSONSerializable(json);
}
toJSON(): any {
return this.json;
}
}
// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable);
Unfortunately, for this to work, we need two features from TypeScript: (1) static methods in interfaces and abstract static methods; (2) the ability to use typeof
as a type hint with generic classes.
@davidmpaz
I did not understand that part.
Classes have two interfaces. The constructor function and the instance prototype. The keyword class
essentially is sugar for that.
interface Foo {
bar(): void;
}
interface FooConstructor {
new (): Foo;
prototype: Foo;
baz(): void;
}
declare const Foo: FooConstructor;
const foo = new Foo();
foo.bar(); // instance method
Foo.baz(); // static method
@jimmykane
can you elaborate on that? This is not C#
class Foo {
bar() {}
static baz() {}
}
const foo = new Foo();
foo.bar();
Foo.baz();
There is no .baz()
on instances of Foo
. .baz()
only exists on the constructor.
From a type perspective, you can reference/extract this two interfaces. Foo
refers to the public instance interface and typeof Foo
refers to the public constructor interface (which would include the static methods).
To follow up on @kitsonk's explanation, here is the example above rewritten to achieve what you want:
interface JSONSerializable <T>{
fromJSON(json: any): T;
}
function makeInstance<T>(cls: JSONSerializable<T>): T {
return cls.fromJSON({});
}
class ImplementsJSONSerializable {
constructor (private json: any) {
}
static fromJSON(json: any): ImplementsJSONSerializable {
return new ImplementsJSONSerializable(json);
}
toJSON(): any {
return this.json;
}
}
// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable);
Note here that:
implements
clause is not really needed, every time you use the class, a structural check is done and the class will be validated to match the interface needed. typeof
to get the constructor type, just make sure your T
is what you intend to capture@mhegazy : you've had to remove my toJSON
call in the JSONSerializable interface. Although my simple example function makeInstance
only calls fromJSON
, it's very common to want to instantiate an object, then use it. You've removed my ability to make calls on what's returned by makeInstance
, because I don't actually know what T
is inside makeInstance
(particularly relevant if makeInstance
is a class method used internally to create an instance, then use it). Of course I could do this:
interface JSONSerializer {
toJSON(): any;
}
interface JSONSerializable <T extends JSONSerializer> {
fromJSON(json: any): T;
}
function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
return cls.fromJSON({});
}
class ImplementsJSONSerializer implements JSONSerializer {
constructor (private json: any) {
}
static fromJSON(json: any): ImplementsJSONSerializer {
return new ImplementsJSONSerializer(json);
}
toJSON(): any {
return this.json;
}
}
// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);
And now I know that my T
will have all the methods available on JSONSerializer
. But this overly verbose and difficult to reason about (wait, you're passing ImplementsJSONSerializer
, but this isn't a JSONSerializable
, is it? Wait, you're duck-typing??). The easier-to-reason about version is:
interface JSONSerializer {
toJSON(): any;
}
interface JSONSerializable <T extends JSONSerializer> {
fromJSON(json: any): T;
}
function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
return cls.fromJSON({});
}
class ImplementsJSONSerializer implements JSONSerializer {
constructor (private json: any) {
}
toJSON(): any {
return this.json;
}
}
class ImplementsJSONSerializable implements JSONSerializable<ImplementsJSONSerializer> {
fromJSON(json: any): ImplementsJSONSerializer {
return new ImplementsJSONSerializer(json);
}
}
// returns an instance of ImplementsJSONSerializable
makeInstance(new ImplementsJSONSerializable());
But as I pointed out in a previous comment, we now must create a factory class for every class we want to instantiate, which is even more verbose than the duck-typing example. That's certainly a workable pattern done in Java all the time (and I assume C# as well). But it's unnecessarily verbose and duplicative. All that boilerplate goes away if static methods are allowed in interfaces and abstract static methods are allowed in abstract classes.
no need for the additional class. classes can have static
members.. you just do not need the implements
clause. in a nominal type system you really need it to assert the "is a" relationship on your class. in a structural type system, you do not really need that. the "is a" relationship is checked on every use anyways, so you can safely drop the implements
clause and not loose safety.
so in other words, you can have one class that has a static fromJSON
and whose instances has a toJSON
:
interface JSONSerializer {
toJSON(): any;
}
interface JSONSerializable<T extends JSONSerializer> {
fromJSON(json: any): T;
}
function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
return cls.fromJSON({});
}
class ImplementsJSONSerializer {
constructor (private json: any) {
}
static fromJSON(json: any): ImplementsJSONSerializer {
return new ImplementsJSONSerializer(json);
}
toJSON(): any {
return this.json;
}
}
// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);
@mhegazy I already pointed that out as a viable option. See my first 'duck-typing' example. I argued that it is unnecessarily verbose and difficult to reason about. I then asserted that the version that is easier to reason about (factory classes) is even more verbose.
No-one disagrees that it's possible to work around the lack of static methods in interfaces. In fact, I'd argue there's a more elegant workaround by using functional styles over factory classes, though that's my personal preference.
But, in my view, the cleanest and easiest-to-reason about way to implement this exceptionally common pattern is using abstract static methods. Those of us who have come to love this feature in other languages (Python, PHP) miss having it in TypeScript. Thanks for your time.
I then asserted that the version that is easier to reason about (factory classes)
not sure i agree that this is easier to reason about.
But, in my view, the cleanest and easiest-to-reason about way to implement this exceptionally common pattern is using abstract static methods. Those of us who have come to love this feature in other languages (Python, PHP) miss having it in TypeScript.
And this issue is tracking getting this added. I just wanted to make sure future visitors to this thread understand that this is doable today without an implements
clause.
@kitsonk right I missed that.
Guys, so I have like LocationModule, which should have method for getting options to store in database, from which it should recreate itself.
Every LocationModule have their own options with type for recreation.
So now I have to create factory with generics for recreation and even then I do not have compile time checks. You know, beating the purpose here.
At first I was like "well no static for interfaces so let's stick with abstract class BUT even this would be dirty, since now I have to change everywhere in my code type from interface to abstract class so checker would recognize methods I'm looking for.
Here:
export interface LocationModuleInterface {
readonly name: string;
getRecreationOptions(): ModuleRecreationOptions;
}
export abstract class AbstractLocationModule implements LocationModuleInterface {
abstract readonly name: string;
abstract getRecreationOptions(): ModuleRecreationOptions;
abstract static recreateFromOptions(options: ModuleRecreationOptions): AbstractLocationModule;
}
And then I stumble upon well, static cannot be with abstract. And it cannot be in interface.
Guys, seriously, aren't we protecting not implementing this only for not implementing this?
I love TypeScript passionately. Like crazy I mean. But there is always but.
Instead of having forced implementation of static method I would have to double check like I was doing with everything in plain old JavaScript.
Architecture is already full of patterns, cool decisions etc, but some of them are just to overcome issues with using such simple things like interface static method.
My factory will have to check at runtime static method existence. How is that?
Or even better:
export interface LocationModuleInterface {
readonly name: string;
getRecreationOptions(): ModuleRecreationOptions;
dontForgetToHaveStaticMethodForRecreation();
}
If you're using the class object polymorphically, which is the only reason for it to implement an interface, it does not matter if the interface(s)that specifies the requirements for the static side of the class is syntactically referenced by the class itself because it will be checked at the use site and issue a design time error if the requisite interface(is) is not implemented.
@malina-kirn
See my first 'duck-typing' example.
This issue has no bearing on whether or not duck typing is used. TypeScript is duck typed.
@aluanhaddad Your implication is not correct. I can understand your point that implements ISomeInterface
primary function is for using the class object polymorphically. However it CAN matter whether the class references the interface(s) that describe the class object's static shape.
You imply that implicit type inference and usage site errors cover all use cases.
Consider this example:
interface IComponent<TProps> {
render(): JSX.Element;
}
type ComponentStaticDisplayName = 'One' | 'Two' | 'Three' | 'Four';
interface IComponentStatic<TProps> {
defaultProps?: Partial<TProps>;
displayName: ComponentStaticDisplayName;
hasBeenValidated: 'Yes' | 'No';
isFlammable: 'Yes' | 'No';
isRadioactive: 'Yes' | 'No';
willGoBoom: 'Yes' | 'No';
}
interface IComponentClass<TProps> extends IComponentStatic<TProps> {
new (): IComponent<TProps> & IComponentStatic<TProps>;
}
function validateComponentStaticAtRuntime<TProps>(ComponentStatic: IComponentStatic<TProps>, values: any): void {
if(ComponentStatic.isFlammable === 'Yes') {
// something
} else if(ComponentStatic.isFlammable === 'No') {
// something else
}
}
// This works, we've explicitly described the object using an interface
const componentStaticInstance: IComponentStatic<any> = {
displayName: 'One',
hasBeenValidated: 'No',
isFlammable: 'Yes',
isRadioactive: 'No',
willGoBoom: 'Yes'
};
// Also works
validateComponentStaticAtRuntime(componentStaticInstance, {});
class ExampleComponent1 implements IComponent<any> {
public render(): JSX.Element { return null; }
public static displayName = 'One'; // inferred as type string
public static hasBeenValidated = 'No'; // ditto ...
public static isFlammable = 'Yes';
public static isRadioactive = 'No';
public static willGoBoom = 'Yes';
}
// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent1, {});
class ExampleComponent2 implements IComponent<any> {
public render(): JSX.Element { return null; }
public static displayName = 'One';
public static hasBeenValidated = 'No';
public static isFlammable = 'Yes';
public static isRadioactive = 'No';
public static willGoBoom = 'Yes';
}
// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent2, {});
In the above example explicit type declaration is required; not describing the shape of the class object (static side) results in a design time error at the usage site even though the actual values conform to the expected shape.
Furthermore, the lack of a way to reference the interface(s) describing the static side, leave us with the only option being to explicitly declare the type on each individual member; and then to repeat that on every class.
Building on my previous post, I feel that the static
modifier in interfaces is a really good solution to some use cases that need to be resolved. I apologize for how big this comment has grown but there is a lot that I want to illustrate.
1) There are times when we need to be able to explicitly describe the shape of the static side of a class object, such as in my example above.
2) There are times when shape checking at the definition site is highly desirable, and times such as in @OliverJAsh 's example where the use site is in external code that won't be checked and you need to shape check at the definition site.
In regards to number 2, many posts I've read suggest shape checking as the use site is more than sufficient. But in cases where the use site is in a galaxy module far, far away.... or when the use site is in an external location that won't be checked this is obviously not the case.
Other posts suggest #workarounds
to shape check at the definition site. While these dreaded workarounds will allow you to check the shape at the definition site (in some cases), there are problems:
#workaroundsToMakeTheWorkaroundsWork
is not fun... or productive... but mostly not fun.Here is an example of the workaround I've seen lately to force shape checking at the definition site...
// (at least) two separate interfaces
interface ISomeComponent { ... }
interface ISomeComponentClass { ... }
// confusing workaround with extra work to use, not very intuitive, etc
export const SomeComponent: ISomeComponentClass =
class SomeComponent extends Component<IProps, IState> implements ISomeComponent {
...
}
Now try using that workaround with an abstract class thrown in
interface ISomeComponent { ... }
// need to separate the static type from the class type, because the abstract class won't satisfy
// the shape check for new(): ...
interface ISomeComponentStatic { ... }
interface ISomeComponentClass {
new (): ISomeComponent & ISomeComponentStatic;
}
// I AM ERROR. ... cannot find name abstract
export const SomeComponentBase: ISomeComponentStatic =
abstract class SomeComponentBase extends Component<IProps, IState> implements ISomeComponent {
...
}
export const SomeComponent: ISomeComponentClass =
class extends SomeComponentBase { ... }
We'll I guess we'll work around that too... sigh
...
abstract class SomeComponentBaseClass extends Component<IProps, IState> implements ISomeComponent {
...
}
// Not as nice since we're now below the actual definition site and it's a little more verbose
// but hey at least were in the same module and we can check the shape if the use site is in
// external code...
// We now have to decide if we'll use this work around for all classes or only the abstract ones
// and instead mix and match workaround syntax
export const SomeComponentBase: ISomeComponentStatic = SomeComponentBaseClass;
But Wait, There's More! Let's see what happens if we're using generics....
interface IComponent<TProps extends A> { ... }
interface IComponentStatic<TProps extends A> { ... }
interface IComponentClass<TProps extends A, TOptions extends B> extends IComponentStatic<TProps> {
new (options: TOptions): IComponent<TProps> & IComponentStatic<TProps>;
}
abstract class SomeComponentBaseClass<TProps extends A, TOptions extends B> extends Component<TProps, IState> implements IComponent<TProps> {
...
}
// Ruh Roh Raggy: "Generic type .... requires 2 type argument(s) ..."
export const SomeComponentBase: IComponentStatic = SomeComponentBaseClass;
// ".... requires 1 type argument(s) ... " OH NO, We need a different workaround
export const SomeComponent: IComponentStatic =
class extends SomeComponentBase<TProps extends A, ISomeComponentOptions> {
...
}
At this point if you have a use site within code that will be checked by typescript, you should probably just bite the bullet and rely on that even if it is really far away.
If you're use site is external convince the community/maintainers to accept the static
modifier for interface members. And in the meantime you'll need another workaround. It's not at the definition site but I believe you can use a modified version of a workaround described in #8328 to achieve this. See the workaround in the comment from @mhegazy on May 16th 2016 courtesy of @RyanCavanaugh
Forgive me if I'm missing key points. My understanding of the hesitancy to support static
for interface members is primarily a dislike of using the same interface + implements keyword to describe both the shape of the constructor and the shape of the instance.
Let me preface the following analogy by saying I love what typescript offers and greatly appreciate those who've developed it and the community that puts a lot of thought and effort into dealing with the plethora of feature requests; doing their best to implement the features that are a good fit.
But, the hesitancy in this case appears to me as a desire to preserve 'conceptual purity' at the cost of usability. Again sorry if this analogy is way off base:
This reminded of switching from Java
to C#
. When I first saw C#
code doing this
C#
var something = "something";
if(something == "something") {
...
}
the alarm bells started sounding in my head, the world was ending, they needed to be using "something".Equals(something)
, etc
==
is for reference equals! You have a separate .Equals(...)
for string comparison...
and the string literal needs to come first so you don't get a null ref calling .Equals(...) on a null variable...
and... and... hyperventilating
And then after a couple weeks of using C#
I came to realize how much easier it was to use because of that behavior. Even though it meant giving up the clear distinction between the two, it makes such a dramatic improvement in usability that it is worth it.
That is how I feel about the static
modifier for interface members. It will allow us to continue describing the shape of the instance as we already do, allow us to describe the shape of the constructor function which we can only do with poor workarounds case by case, and allow us to do both in relatively clean and easy way. A huge improvement in usability, IMO.
I'll weigh in on abstract static
in the next comment....
Interfaces always require respecification of member types on implenting classes. (inference of member signatures in _explicitly_ implementing classes is a separate, as yet unsupported, feature).
The reason you are getting errors is unrelated to this proposal. To get around it, you need to use the readonly
modifier on the implementation of class members, static or otherwise, that need to be of literal types; elsewise, string
will be inferred.
Again the implements
keyword is just a specifications of intent, it does not influence the structural compatibility of types, nor introduce nominal typing. That's not to say that it couldn't be useful but it doesn't change the types.
So abstract static
... Not Worth It. Too many problems.
You would need new and complex syntax to declare what will already be checked at the use site implicitly (if the use site will be checked by the typescript compiler).
If the use site is external then what you really need are static
members of an interface... Sorry done plugging.... and good night!
@aluanhaddad True, but even if inference of member signatures in explicitly implementing classes were a supported feature we lack clean ways of declaring the member signatures for static side of the class.
The point I was trying to express was that we lack ways to explicitly
declare the expected structure of the static side of the class and there are cases where that does matter (or will matter in order to support features)
Primarily I wanted to refute this part of your comment "it does not matter if the interface(s) that specifies the requirements for the static side of the class is syntactically referenced".
I was trying to use type inference as an example of why it would matter with future support.
By syntactically referencing the interface here let something: ISomethingStatic = { isFlammable: 'Ys', isFlammable2: 'No' ... isFlammable100: 'No' }
we don't have to explicitly declare the type as 'Yes' | 'No' 100 separate times.
To achieve the same type inference for the static side of a class (in the future) we will need some way to syntactically reference the interface(s).
After reading your latest comment I think that wasn't as clear an example as I was hoping. Perhaps a better example is when the use site for the static side of the class is in external code that won't be checked by the typescript compiler. In such a case we currently have to rely on workarounds that essentially create an artificial use site, provide poor usability/readability, and do not work in many cases.
At the expense of some verbosity, you can achieve a decent level of type safety:
type HasType<T, Q extends T> = Q;
interface IStatic<X, Y> {
doWork: (input: X) => Y
}
type A_implments_IStatic<X, Y> = HasType<IStatic<X, Y>, typeof A> // OK
type A_implments_IStatic2<X> = HasType<IStatic<X, number>, typeof A> // OK
type A_implments_IStatic3<X> = HasType<IStatic<number, X>, typeof A> // OK
class A<X, Y> {
static doWork<T, U>(_input: T): U {
return null!;
}
}
type B_implments_IStatic = HasType<IStatic<number, string>, typeof B> // Error as expected
class B {
static doWork(n: number) {
return n + n;
}
}
Agreed this should be allowed. Following simple use case would benefit from abstract static methods:
export abstract class Component {
public abstract static READ_FROM(buffer: ByteBuffer): Component;
}
// I want to force my DeckComponent class to implement it's own static ReadFrom method
export class DeckComponent extends Component {
public cardIds: number[];
constructor(cardIds: number[]) {
this.cardIds = cardIds;
}
public static READ_FROM(buffer: ByteBuffer): DeckComponent {
const cardIds: number[] = [...];
return new DeckComponent(cardIds);
}
}
@RyanCavanaugh I don't think anyone has mentioned this exactly yet(although I could have missed it in my quick read-through), but in response to:
A major question we had when considering this: Who is allowed to call an abstract static method? Presumably you can't invoke AbstractParentClass.getSomeClassDependentValue directly. But can you invoke the method on an expression of type AbstractParentClass? If so, why should that be allowed? If not, what is the use of the feature?
This could be addressed by implementing part of #3841. Reading through that issue the primary objection raised seems to be that types of constructor functions of derived classes are often not compatible with the constructor functions of their base classes. However the same concern does not appear to apply to any other static methods or fields because TypeScript is already checking that overridden statics are compatible with their types in the base class.
So what I'd propose is giving T.constructor
the type Function & {{ statics on T }}
. That would allow abstract classes that declare an abstract static foo
field to safely access it via this.constructor.foo
while not causing issues with constructor incompatibilities.
And even if automatically adding the statics to T.constructor
isn't implemented we would still be able to use abstract static
properties in the base class by manually declaring "constructor": typeof AbstractParentClass
.
I think many people expect @patryk-zielinski93 example should work. Instead, we should use counterintuitive, verbose and cryptic workarounds. Since we already have 'syntax sugared' classes and static members, why we cannot have such sugar in type system?
Here is my pain:
abstract class Shape {
className() {
return (<typeof Shape>this.constructor).className;
}
abstract static readonly className: string; // How to achieve it?
}
class Polygon extends Shape {
static readonly className = 'Polygon';
}
class Triangle extends Polygon {
static readonly className = 'Triangle';
}
Maybe we could instead/in parallel introduce a static implements
clause? e.g.
interface Foo {
bar(): number;
}
class Baz static implements Foo {
public static bar() {
return 4;
}
}
This has the advantage of being backwards-compatible with declaring separate interfaces for static and instance members, which seems to be the current workaround of choice, if I'm reading this thread correctly. It's also a subtly different feature which I could see being useful in its own right, but that's beside the point.
(statically implements
would be better, but that introduces a new keyword. Debatable if this is worth that. It could be contextual, though.)
No matter how hard I tried to understand arguments of those who are skeptical about OP's proposal, I failed.
Only one person (https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-379645122) answered to you @RyanCavanaugh . I rewrote OP's code to make it bit cleaner and also to illustrate your question and my answers:
abstract class Base {
abstract static foo() // ref#1
}
class Child1 extends Base {
static foo() {...} // ref#2
}
class Child2 extends Base {
static foo() {...}
}
Who is allowed to call an abstract static method? Presumably you can't invoke
Base.foo
directly
If you mean ref#1, then yes, it should never be callable, because well It is abstract, and doesn't even have a body.
You are only allowed to call ref#2.
But can you invoke the method on an expression of type Base?
If so, why should that be allowed?
function bar(baz:Base) { // "expression of type Base"
baz.foo() // ref#3
}
function bar2(baz: typeof Base) { // expression of type Base.constructor
baz.foo() // ref#4
}
ref#3 is an error. No, you cannot invoke "foo" there, because baz
supposed to be an __instance__ of Child1 or Child2, and instances do not have static methods
ref#4 is (should be) correct. You can (should be able to) invoke static method foo there, because baz
supposed to be constructor of Child1 or Child2, which both extend Base and, therefore must have implemented foo().
bar2(Child1) // ok
bar2(Child2) // ok
You can imagine this situation:
bar2(Base) // ok, but ref#4 should be red-highlighted with compile error e.g. "Cannot call abstract method."
// Don't know if it's possible to implement in compiler, though.
// If not, compiler simply should not raise any error and let JS to do it in runtime (Base.foo is not a function).
What is the use of the feature?
See ref#4 The use is when we do not know which one of child classes we receive as argument (it may be Child1 or Child2), but we want to call it's static method and be sure that that method exists.
I am re-writing node.js framework right know, called AdonisJS. It was written in pure JS, so I am transforming it to TS. I cannot change how code works, I am only adding types. Lacking this features makes me very sad.
p.s. In this comment, for simplicity reasons, I wrote about only abstract classes, and did not mention interfaces. But everything I wrote is applicable to interfaces. You just replace abstract class with interface, and everything will be correct. There is possibility, when TS team for some reason (don't know why) would not want to implement abstract static
in abstract class
, however it would be fine to only implement static
word in interfaces, that would make us happy enough, I guess.
p.p.s. I edited class and method names in your questions to comply them with my code.
Couple of thoughts, based on other skeptics' main arguments:
"No, this must not be implemented, but you already can do it this way *writes 15 times more lines of code*".
That's what syntactical sugar is made for. Let's add a little (sugar) to TS. But no, we'd better torture developers' brains who try to break through decorators and dozen of generics instead of adding one simple static
word to interfaces. Why not consider that static
as another sugar to make our lives easier? Same way as ES6 adds class
(which becomes ubiquitous nowadays). But no, let us be nerds and do things old and "right" way.
"There are two interfaces for js classes: for constructor and instance".
Ok, why not give us a way to make interface for constructor then? However simply adding that static
word is much easier, and more intuitive.
And regarding this:
Holding off on this until we hear more feedback on it.
More than one year have passed, and plenty of feedback provided, for how long this issue is going to be held off? Can someone answer, please.
This post may seem kinda harsh... But no, it's a request from the guy who loves TS and at the same time truely cannot find one solid reason why we should go with ugly hacks, when converting our old JS codebase to TS. Apart from this, huuuge thanks to TS team. TS is just wonderful, it changed my life and I enjoy coding and my job more than ever... But this issue is poisoning my experience..
Possible workaround?
export type Constructor<T> = new (...args: any[]) => T;
declare global {
interface Function extends StaticInterface {
}
}
export interface StaticInterface {
builder: (this: Constructor<MyClass>) => MyClass
}
// TODO add decorator that adds "builder" implementation
export class MyClass {
name = "Ayyy"
}
export class OtherClass {
id = "Yaaa"
}
MyClass.builder() // ok
OtherClass.builder() // error
````typescript
interface Applicative extends Apply {
static of(a: A): Applicative;
}
const of = >(c: C, a: A): new C => c.of(a);
````
Serializable
type with static deserialize
method````typescript
interface Serializable {
static deserialize(s: string): Serializable;
serialize(): string;
}
````
````typescript
interface Contract {
static create(x: number): Contract;
static new(x: number): Contract;
}
const factory1 = (c: static Contract): Contract => c.create(Math.random());
const factory2 = (C: static Contract): Contract => new C(Math.random());
const factory3 =
const c1 = factory1(ContractImpl); // Contract
const c2 = factory2(ContractImpl); // Contract
const c3 = factory3(ContractImpl); // ContractImpl
````
````typescript
interface Serializable {
static deserialize(s: string): Serializable;
}
type Serializable = {
static deserialize(s: string): Serializable;
};
abstract class Serializable {
static abstract deserialize(s: string): Serializable;
}
````
typescript
interface Contract {
static new(): Contract;
}
Discuss:
How static call signatures may be expressed?
If we will express them with simply adding static
modifier before call signature,
how we will distinct them from instance method with name 'static'
?
Can we use the same workaround as for 'new'
-named methods?
In such case, it will be definitely a breaking change.
static
type operatortypescript
const deserialize = (Class: static Serializable, s: string): Serializable =>
Class.deserialize(s);
new
type operatortypescript
const deserialize = <C extends static Serializable>(Class: C, s: string): new C =>
Class.deserialize(s);
[[Static]]
internal slot“Static” interface of a type will be stored in the [[Static]]
internal slot:
````typescript
// The type
interface Serializable {
static deserialize(s: string): Serializable;
serialize: string;
}
// will be internally represented as
// interface Serializable {
// [[Static]]: {
// [[Instance]]: Serializable; // See below.
// deserialize(s: string): Serializable;
// };
// serialize(): string;
// }
````
By default have type never
.
[[Instance]]
internal slot“Instance” interface of the type will be stored
in the [[Instance]]
internal slot of the [[Static]]
internal slot type.
By default have type never
.
static
operatorWhen a type used as a value type,
the [[Static]]
internal slot will be discarded:
````typescript
declare const serializable: Serializable;
type T = typeof serializable;
// { serialize(): string; }
````
But the type itself remains keep the [[Static]]
internal slot:
typescript
type T = Serializable;
// {
// [[Static]]: {
// [[Instance]]: Serializable;
// deserialize(s: string): Serializable;
// };
// serialize(): string;
// }
Both values of static-aware types are assignable to the structurally identical
(except of the [[Static]]
, of course) ones and vice versa.
static
operator| Associativity | Precedence |
| :-----------: | :--------------------------------: |
| Right | IDK, but equals to the new
's one |
The static
type operator returns the type of [[Static]]
internal slot of the type.
It somewhat similar to the typeof
type operator,
but it's argument must be a type rather than a value.
typescript
type T = static Serializable;
// {
// [[Instance]]: Serializable;
// deserialize(s: string): Serializable;
// }
The typeof
type operator also discards the [[Instance]]
internal slot:
````typescript
declare const SerializableImpl: static Serializable;
type T = typeof SerializableImpl;
// { deserialize(s: string): Serializable; }
````
Both values of instance-aware types are assignable to the structurally identical
(except the [[Instance]]
internal slot, of course) ones and vice versa.
new
operator| Associativity | Precedence |
| :-----------: | :-----------------------------------: |
| Right | IDK, but equals to the static
's one |
The new
operators returns the type of [[Instance]]
internal slot of the type.
It effectively reverses the static
operator:
typescript
type T = new static Serializable;
// {
// [[Static]]: {
// [[Instance]]: Serializable;
// deserialize(s: string): Serializable;
// };
// serialize(): string;
// }
extends
/implements
semanticsA class implementing an interface with non-default [[Static]]
internal slot
MUST have a compatible [[Static]]
internal slot.
The compatibility checks should be the same as (or similar with)
regular (instance) compatibility checks.
````typescript
class SerializableImpl implements Serializable {
static deserialize(s: string): SerializableImpl {
// Deserializing logic goes here.
}
// ...other static members
// constructor
// ...other members
serialize(): string {
//
}
}
````
infer
operator?I've spent a few hours reading through this issue and other issues for a workaround for my problem, which would be solved by the static modifier or interfaces for classes. I can't find a single workaround which solves my problem.
The issue I have is that I want to make a modification to a code generated class. For example what I would like to do is:
import {Message} from "../protobuf";
declare module "../protobuf" {
interface Message {
static factory: (params: MessageParams) => Message
}
}
Message.factory = function(params: MessageParams) {
const message = new Message();
//... set up properties
return message;
}
export default Message;
I can't find a single workaround that allows me to do the equivalent of this, with the current version of TS. Am I missing a way to solve this problem currently?
This feels relevant to post here as a use case for which there is apparently no workaround and certainly no straightforward workaround.
If you want to check the instance and static side of a class against interfaces, you can do it like this:
interface C1Instance {
// Instance properties ...
// Prototype properties ...
}
interface C2Instance extends C1Instance {
// Instance properties ...
// Prototype properties ...
}
interface C1Constructor {
new (): C1Instance;
// Static properties ...
}
interface C2Constructor {
new (): C2Instance;
// Static properties ...
}
type C1 = C1Instance;
let C1: C1Constructor = class {};
type C2 = C2Instance;
let C2: C2Constructor = class extends C1 {};
let c1: C1 = new C1();
let c2: C2 = new C2();
All too many of us are wasting far too many hours of our time and others on this. Why isn't it a thing?!¿i!
Why are the answers all huge workarounds for something that should be readable, digestible, and something that's a 1-liner that's straightforward. There's no way I want someone to have to figure out what my code is trying to do with some hacky workaround. Sorry to further clutter this topic with something that's not very valuable, but it's a huge pain and waste of time currently, so I do find it valuable for me, the others right now, and those who come along later looking for an answer.
The thing with any language, both natural and artificial, that it should naturally evolve to be efficient and appealing to be used. Eventually people decided to use reductions in language, ("okay"=>"ok","going to" =>"gonna"), invented new ridiculous words, like "selfie" and "google", redefined the spelling with l33tspeak and stuff, and even banned some words, and, despite if you want to use them or not, everybody still kinda understands what they mean, and some of us do use them to achieve some particular whatever tasks. And for none of those there could be any good reason, but the efficiency of certain people in certain tasks, it's all question of numbers of people, who actually make use of them. The volume of this conversation clearly shows, that a whole lot of people could make a use of this static abstract
for whatever goddamn considerations they have. I came here for the same reason, 'cause I wanted to implement Serializable
so I tried all of the intuitive (for me) ways to do it, and none of them work. Trust me, the last thing I would look for is the explanation why I do not need this feature and should go for something else. Year and a half, jesus christ! I bet there's a PR somewhere already, with tests and stuff. Please make this happen, and if there's a certain way of usage which is discouraged, we have a tslint for that.
It is possible to access static members of child classes from the base class via this.constructor.staticMember
, so abstract static members make sense to me.
class A {
f() {
console.log(this.constructor.x)
}
}
class B extends A {
static x = "b"
}
const b = new B
b.f() // logs "b"
Class A should be able to specify that it requires static x
member, because it uses it in f
method.
Any news ?
The features are really needed 😄
1) static
functions in interfaces
2) abstract static
functions in abstract classes
although I hate the idea of having static interfaces but for all practical purposes the following should be enough today:
type MyClass = (new (text: string) => MyInterface) & { myStaticMethod(): string; }
that can be used as:
const MyClass: MyClass = class implements MyInterface {
constructor(text: string) {}
static myStaticMethod(): string { return ''; }
}
UPDATE:
more details on the idea and a live example:
// dynamic part
interface MyInterface {
data: number[];
}
// static part
interface MyStaticInterface {
myStaticMethod(): string;
}
// type of a class that implements both static and dynamic interfaces
type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface;
// way to make sure that given class implements both
// static and dynamic interface
const MyClass: MyClass = class MyClass implements MyInterface {
constructor(public data: number[]) {}
static myStaticMethod(): string { return ''; }
}
// works just like a real class
const myInstance = new MyClass([]); // <-- works!
MyClass.myStaticMethod(); // <-- works!
// example of catching errors: bad static part
/*
type 'typeof MyBadClass1' is not assignable to type 'MyClass'.
Type 'typeof MyBadClass1' is not assignable to type 'MyStaticInterface'.
Property 'myStaticMethod' is missing in type 'typeof MyBadClass1'.
*/
const MyBadClass1: MyClass = class implements MyInterface {
constructor(public data: number[]) {}
static myNewStaticMethod(): string { return ''; }
}
// example of catching errors: bad dynamic part
/*
Type 'typeof MyBadClass2' is not assignable to type 'MyClass'.
Type 'typeof MyBadClass2' is not assignable to type 'new (data: number[]) => MyInterface'.
Type 'MyBadClass2' is not assignable to type 'MyInterface'.
Property 'data' is missing in type 'MyBadClass2'.
*/
const MyBadClass2: MyClass = class implements MyInterface {
constructor(public values: number[]) {}
static myStaticMethod(): string { return ''; }
}
@aleksey-bykov this might not be Typescript's fault, but I couldn't get this working Angular component decorators and their AoT compiler.
@aleksey-bykov that's clever but still doesn't work for abstract static. If you have any subclasses of MyClass
, they are not enforced with type checking. It's also worse if you have generics involved.
// no errors
class Thing extends MyClass {
}
I really hope that the TypeScript team reconsiders their stance on this, because building end user libraries that require static attributes doesn't have any reasonable implementation. We should be able to have a contract that requires that interface implementers/abstract class extenders have statics.
@bbugh i question the very existence of the problem being discussed here, why would you need all these troubles with static abstract inherited methods if the same can be done via instances of regular classes?
class MyAbstractStaticClass {
abstract static myStaticMethod(): void; // <-- wish we could
}
class MyStaticClass extends MyAbstractStaticClass {
static myStaticMethod(): void {
console.log('hi');
}
}
MyStaticClass.myStaticMethod(); // <-- would be great
vs
class MyAbstractNonStaticClass {
abstract myAbstractNonStaticMethod(): void;
}
class MyNonStaticClass extends MyAbstractNonStaticClass {
myNonStaticMethod(): void {
console.log('hi again');
}
}
new MyNonStaticClass().myNonStaticMethod(); // <-- works today
@aleksey-bykov There are plenty of reasons. For example (from @patryk-zielinski93):
abstract class Serializable {
abstract serialize (): Object;
abstract static deserialize (Object): Serializable;
}
I want to force implementation of static deserialize method in Serializable's subclasses.
Is there any workaround to implement such behaviour?
EDIT: I know that you could also use deserialize
as a constructor, but classes can only have 1 constructor, which makes factory methods necessary. People want a way to require factory methods in interfaces, which makes complete sense.
simply take desirealization logic to a separate class, because there is no benefit of having a static deserialize method attached to the very class being deserialized
class Deserializer {
deserializeThis(...): Xxx {}
deserializeThat(...): Yyy {}
}
what is the problem?
@aleksey-bykov separate class doesn't look as pretty
@aleksey-bykov, the problem is that there's more than 1 class requiring serialization, so your approach is forcing towards creating a dictionary of serializables, which is an uberclass antipattern, and every modification to any of them would require an edit in this uberclass, which makes code support a nuisance. While having a serializable interface could force an implementation to any given object type, and also support polymorphic inheritance, which are the main reasons people want it. Please do not speculate whether there's a benefit or not, anyone should be able to have options and to choose what is beneficial for his own project.
@octaharon having a class solely dedicated to deserialization is quite the opposite to what you said, it has single responsibility because the only time you change it is when you care about deserialization
contrary adding deserialization to the class itself like you proposed gives it an additional responsibility and additional reason for being changed
lastly i have no problem static methods , but i am truly curious to see an example of a practical use case for abstract static methods and static interfaces
@octaharon having a class solely dedicated to deserialization is quite the opposite to what you said, it has single responsibility because the only time you change it is when you care about deserialization
except for you have to change the code in two places instead of one, as your (de)serialization depends on your type structure. That doesn't count towards "single responsibility"
contrary adding deserialization to the class itself like you proposed gives it an additional responsibility and additional reason for being changed
I don't get what you're saying. A class that is responsible for its own (de)serialization is exactly what I want, and please, don't tell me if it's right or wrong.
single responsibility doesn't say anything about how many files are involved, it only says there has to be a single reason
what i am saying is that when you add a new class you mean it for something other than just being deserialized, so it already has a reason to exist and responsibility assigned to it; then you add another responsibility of being able to deserialize itself, this gives us 2 responsibilities, this violates SOLID, if you keep adding more stuff into it like render, print, copy, transfer, encrypt, etc you gonna get a god class
and pardon me telling you banalities, but questions (and answers) about benefits and practical use cases is what drives this feature proposal to being implemented
SOLID wasn't sent by God Allmighty on a tablet, and even if it was, there's a degree of freedom to its interpretation. You might be as idealistic as you wish about it, but do me a favor: don't spread your beliefs over the community. Everyone has all the rights to use any tool in any way he wants and break all possible rules he knows (you can't blame a knife for a murder). What defines the quality of a tool is a balance between a demand for certain features and an offer for them. And this branch shows the volume of the demand. If you don't need this feature - don't use it. I do. And a bunch of people here do need it too, and _we_ have a practical use case, while you're telling that we should disregard it, in fact. It's only about what's more important for the maintainers - the holy principles (in whichever way they understand it), or the community.
man, any good proposal features use cases, this one doesn't
so i only expressed some curiosity and asked a question, since the proposal doesn't say it, why you people might need it
it boiled down to typical: just cause, don't you dare
personally i don't care about solid or oop, i just happened to overgrow it long time ago, you brought it up by throwing the "uberclass antipattern" argument and then backed up to "a degree of freedom to its interpretation"
the only practical reason mentioned in this whole discussion is this: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-308362119
A very common use case this will help with is React components, which are classes with static properties such as displayName, propTypes, and defaultProps.
and a few posts alike https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-345496014
but it's covered by (new (...) => MyClass) & MyStaticInterface
that's the exact use case and a reason I'm here. Do you see the number of votes? Why do you think it's up to you personally to decide what's practical
and what's not? Practical
is what can be put to practice
, and (at the moment of writing) 83 people would find this feature damn practical. Please respect others and read the full thread before you start pulling the phrases out of a context and exhibiting various buzzwords. Whatever you had overcome, that's definitely not your ego.
it's common sense that practical things are those that solve problems, non practical things are those to tingle your sense of beauty, i do respect others but with all that respect the question (mostly rhetorical now) still holds: what problem does this proposal intend to solve given (new (...) => MyClass) & MyStaticInterface
for https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-308362119 and https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-289084844 just cause
please do not answer
For the same reason I sometimes use type
declarations to reduce large annotations, I think a keyword like abstract static
would be alot more readable than a relatively more difficult to digest construct like has been mentioned as an existing example.
Plus, we still haven't addressed abstract classes?
The solution to not use abstract classes is not the solution, in my opinion. That is a work-around! A work-around around what?
I think this feature request exists because many people, including the requester, have found that an expected feature such as abstract static
or static
in interfaces was not present.
Based on the solution offered, does the static
keyword even have need to exist if there is a work-around to avoid its' use? I think that would be equally ridiculous to suggest.
The problem here is that static
makes alot more sense.
Based on the generated interest, can we have some less dismissive discussion?
Has there been any update on this proposal? Any arguments worth considering that demonstrate why we shouldn't have static abstract
and the like?
Can we have more suggestions that show why it would be useful?
Perhaps we need to reel things in and summarize what's been discussed and so we can figure out a solution.
There are two proposals as I understand:
interface ISerializable<T> {
static fromJson(json: string): T;
}
abstract class MyClass<T> implements ISerializable<T> {
abstract static fromJson(json: string): T;
}
class MyOtherClass extends MyClass<any> {
static fromJson(json: string) {
// unique implementation
}
}
As for proposal one, there is technically a work-around! Which is not great, but that is something at least. But it IS a work-around.
You can split your interfaces into two and re-write your class like
interface StaticInterface {
new(...args) => MyClass;
fromJson(json): MyClass;
}
interface InstanceInterface {
toJson(): string;
}
const MyClass: StaticInterface = class implements InstanceInterface {
...
}
In my opinion, this is alot of extra work and slightly less readable, and has the downside of re-writing your classes in a funny way which is simply strange and deviates from the syntax we are using.
But then, what about proposal 2? There is nothing that can be done about that, is there? I think that deserves addressing as well!
What is the practical use for one of these types—how would one of these be used?
interface JsonSerializable {
toJSON(): string;
static fromJSON(serializedValue: string): JsonSerializable;
}
It's already possible to say a value must be an object like { fromJSON(serializedValue: string): JsonSerializable; }
, so is this just wanted in order to enforce a pattern? I don't see the benefit to that from a type checking perspective. As a side note: in this case it would be enforcing a pattern that's hard to work with—it would be better to move the serialization process into separate serializer classes or functions for many reasons I won't get into here.
Additionally, why is something like this being done?
class FirstChildClass extends AbstractParentClass {
public static getSomeClassDependentValue(): string {
return 'Some class-dependent value of class FirstChildClass';
}
}
What about using either template method or strategy pattern instead? That would work and be more flexible, right?
At the moment, I'm against this feature because to me it seems to add unnecessary complexity in order to describe a class designs that are hard to work with. Maybe there's some benefit I'm missing though?
there is one valid usecase for static React methods, thats about it
@aleksey-bykov ah, ok. For those cases it might be better if adding a type on the constructor
property would cause type checking in that rare scenario. For example:
interface Component {
constructor: ComponentConstructor;
}
interface ComponentConstructor {
displayName?: string;
}
class MyComponent implements Component {
static displayName = 5; // error
}
That seems way more worthwhile to me. It doesn't add any additional complexity to the language and only adds more work for the type checker when checking if a class implements an interface properly.
By the way, I was thinking a valid use case would be when traveling from an instance to the constructor and finally to a static method or property with type checking, but that's already possible by typing the constructor
property on a type and will be solved for class instances in #3841.
i had a similar idea: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-437071092
Is the serialize/deserialize pattern described upthread not "valid"?
@dsherret doesn't "see the benefit from a type-checking perspective". The whole point of doing static analysis in the first place is to catch errors as early as possible. I type things so that if a call signature needs to change, then everybody who calls it -- or, critically, everybody responsible for implementing methods that use the signature -- update to the new signature.
Suppose I have a library that provides a set of sibling classes with a static foo(x: number, y: boolean, z: string)
method, and the expectation is that users will write a factory class that takes several of these classes and calls the method to construct instances. (Maybe it's deserialize
or clone
or unpack
or loadFromServer
, doesn't matter.) The user also makes subclasses of the same (possibly abstract) parent class from the library.
Now, I need to change that contract so that the last parameter is an options object. Passing a string value for the 3rd argument is an unrecoverable error that should be flagged at compile-time. Any factory class should be updated to pass an object instead when calling foo
, and subclasses that implement foo
should change their call signature.
I want to guarantee that users who update to the new library version will catch the breaking changes at compile-time. The library has to export one of the static interface workarounds above (like type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface;
) and hope that the consumer applied it in all the right places. If they forgot to decorate one of their implementations or if they didn't use a library exported type to describe the call signature of foo
in their factory class, the compiler can't tell anything changed and they get runtime errors. Contrast that with a sensible implementation of abstract static
methods in the parent class -- no special annotations required, no burden on the consuming code, it just works out of the box.
@thw0rted won't most libraries require some kind of registration of these classes and type checking can occur at that point? For example:
// in the library code...
class SomeLibraryContext {
register(classCtor: Function & { deserialize(serializedString: string): Component; }) {
// etc...
}
}
// then in the user's code
class MyComponent extends Comonent {
static deserialize(serializedString: string) {
return JSON.parse(serializedString) as Component;
}
}
const context = new SomeLibraryContext();
// type checking occurs here today. This ensures `MyComponent` has a static deserialize method
context.register(MyComponent);
Or are instances of these classes being used with the library and the library goes from the instance to the constructor in order to get the static methods? If so, it's possible to change the design of the library to not require static methods.
As a sidenote, as an implementer of an interface, I would be very annoyed if I was forced to write static methods because it's very difficult to inject dependencies to a static method because of the lack of a constructor (no ctor dependencies are possible). It also makes it difficult to swap out the implementation of the deserialization with something else depending on the situation. For example, say I was deserializing from different sources with different serialization mechanisms... now I have a static deserialize method which by design can only be implemented one way for an implementation of a class. In order to get around that, I would need to have another global static property or method on the class to tell the static deserialize method what to use. I'd prefer a separate Serializer
interface which would allow for easily swapping out what to use and wouldn't couple the (de)serialization to the class being (de)serialized.
I see what you mean -- to use a factory pattern, eventually you have to pass the implementing class to the factory, and static checking occurs at that time. I still think there can be times when you want to provide a factory-compliant class but not actually use it at the moment, in which case an abstract static
constraint would catch problems earlier and make meaning clearer.
I have another example that I think does not fall afoul of your "sidenote" concern. I have a number of sibling classes in a frontend project, where I give the user a choice of which "provider" to use for a certain feature. Of course, I could make dictionary somewhere of name => Provider
and use the keys to determine what to show in the pick-list, but the way I have it implemented now is by requiring a static name
field on each Provider implementation.
At some point, I changed that to requiring both a shortName
and longName
(for display in different contexts with different amounts of available screen space). It would have been much more straightforward to change abstract static name: string;
to abstract static shortName: string;
etc, instead of changing the pick-list component to have a providerList: Type<Provider> & { shortName: string } & { longName: string }
. It conveys intent, in the right place (that is, in the abstract parent, not in the consuming component), and is easy to read and easy to change. I think we can say there's a workaround, but I still believe it's objectively worse than the proposed changes.
I recently stumbled upon this issue, when I needed to use static method in an interface. I apologize, if this has already been addressed before, as I do not have time to read through this amount of comments.
My current problem: I have a private .js library, that I want to use in my TypeScript project. So I went ahead and started writing a .d.ts file for that library, but as the library uses static methods, I could not really finish that. What is the suggested approach in this case?
Thanks for answers.
@greeny https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-449610196
@greeny here is a working solution: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-437071092
specifically this part of it:
type MyClass = (new (text: string) => MyInterface) & { myStaticMethod(): string; }
Another use-case : Generated code / partial class shims
import
statements are ignored in the final merging so the 'shim' include is ignored in the final generated code.interface SwaggerException
{
static isSwaggerException(obj: any): obj is SwaggerException;
}
But I can't do this, and have to actually write a class - which is fine but still feels wrong. I just want - as many others have said - to say 'this is the contract'
Just wanted to add this here since I don't see any other mentions of code-generation - but many 'why would you need this' comments which just get irritating. I would expect a decent percentage of people 'needing' this 'static' feature in an interface are doing it just so they can refer to external library items.
Also I'd really like to see cleaner d.ts
files - there must be some overlap with this feature and those files. It's hard to understand something like JQueryStatic because it seems like it's just a hack. Also the reality is d.ts
files are often out of date and not maintained and you need to declare shims yourself.
(sorry for mentioning jQuery)
For the serialization case i did something like that.
export abstract class World {
protected constructor(json?: object) {
if (json) {
this.parseJson(json);
}
}
/**
* Apply data from a plain object to world. For example from a network request.
* @param json Parsed json object
*/
abstract parseJson(json: object): void;
/**
* Parse instance to plane object. For example to send it through network.
*/
abstract toJson(): object;
}
But it would be still way easier to use something like that:
export abstract class World {
/**
* Create a world from plain object. For example from a network request.
* @param json Parsed json object
*/
abstract static fromJson(json: object): World;
/**
* Parse instance to plane object. For example to send it through network.
*/
abstract toJson(): object;
}
I red a lot of this thread and i still don't understand why it is good and correct to say no to this pattern. If you share that opinion, you also say java and other popular langages make it wrong. Or am i wrong?
This issue is open two years and has 79 comments. It is labeled as Awaiting More Feedback
. Could you say what else feedback you need to take a decision?
it's really annoying that I can't describe static method neither in interface nor in abstract class (declaration only). It's so ugly to write workarounds because of this feature is yet to be implemented =(
For example, next.js uses a static getInitialProps
function for getting page properties before constructing the page. In case it throws, the page does not get constructed, but rather the error page.
https://github.com/zeit/next.js/blob/master/packages/next/README.md#fetching-data-and-component-lifecycle
But unfortunately, clue ts which implement this method can give it any type signature, even if it causes errors at run time, because it cannot be type checked.
I think this issue exists here such long time is because JavaScript itself not good at static thing 🤔
Static inheritance should never exists in first place. 🤔🤔
Who want to call a static method or read a static field? 🤔🤔🤔
It's there any other use case?🤔🤔🤔🤔
Any update at all on this?
If it's any help, what I've done in cases where I need to type the static interface for a class is use a decorator to enforce the static members on the class
The decorator is defined as:
export const statics = <T extends new (...args: Array<unknown>) => void>(): ((c: T) => void) => (_ctor: T): void => {};
If I have static constructor member interface defined as
interface MyStaticType {
new (urn: string): MyAbstractClass;
isMember: boolean;
}
and invoked on the class that should statically declare the members on T as:
@statics<MyStaticType>()
class MyClassWithStaticMembers extends MyAbstractClass {
static isMember: boolean = true;
// ...
}
The most frequent example is good:
interface JsonSerializable {
toJSON(): string;
static fromJSON(serializedValue: string): JsonSerializable;
}
But as said in #13462 here:
Interfaces should define the functionality an object provides. This functionality should be overridable and interchangeable (that's why interface methods are virtual). Statics are a parallel concept to dynamic behaviour/virtual methods.
I agree on the point that interfaces, in TypeScript, describe just an object instance itself, and how to use it. The problem is that an object instance is not a class definition, and a static
symbol may only exists on a class definition.
So I may propose the following, with all its flaws:
An interface could be either describing an object, or a class. Let's say that a class interface is noted with the keywords class_interface
.
class_interface ISerDes {
serialize(): string;
static deserialize(str: string): ISerDes
}
Classes (and class interfaces) could use the statically implements
keywords to declare their static symbols using an object interface (class interfaces could not be statically
implemented).
Classes (and class interfaces) would still use the implements
keyword with an object interface or a class interface.
A class interface could then the mix between a statically implemented object interface and an instance implemented interface. Thus, we could get the following:
interface ISerializable{
serialize(): string;
}
interface IDeserializable{
deserialize(str: string): ISerializable
}
class_interface ISerDes implements ISerializable statically implements IDeserializable {}
This way, interfaces could keep their meaning, and class_interface
would be a new kind of abstraction symbol dedicated for classes definitions.
One small not-critical use-case more:
In Angular for AOT compiling, you can not call functions in decorators (like in @NgModule
module decorator)
angular issue
For service worker module, you need thing like this:
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production})
Our environment using subclasses, extending abstract class with default values and abstract properties to implement. Similar implementation example
So AOT does not work because construct a class is a function, and throws error like: Function calls are not supported in decorators but ..
To make it work and keep autocompletion/compiler support, its possible to define same properties on static level. But for 'to implement' properties, we need interface with static members or abstract static members in abstract class. Both not possible yet.
with interface could work this way:
// default.env.ts
interface ImplementThis {
static propToImplement: boolean;
}
class DefaultEnv {
public static production: boolean = false;
}
// my.env.ts
class Env extends DefaultEnv implements ImplementThis {
public static propToImplement: true;
}
export const environment = Env;
with abstract statics could work this way:
// default.env.ts
export abstract class AbstractDefaultEnv {
public static production: boolean = false;
public abstract static propToImplement: boolean;
}
// my.env.ts
class Env extends AbstractDefaultEnv {
public static propToImplement: true;
}
export const environment = Env;
there are workarounds, but all they are weak :/
@DanielRosenwasser @RyanCavanaugh apologies for the mentions, but it seems that this feature suggestion—which has a lot of support from the community, and I feel would be fairly easy to implement—has gotten buried deep in the Issues category. Do either of you have any comments about this feature, and would a PR be welcome?
Isn't this issue a duplicate of #1263? 😛
I feel as though static methods in interfaces is not as intuitive as having static abstract methods on abstract classes.
I think it is a little sketchy to add static methods to interfaces because an interface should define an object, not a class. On the other hand, an abstract class should certainly be allowed to have static abstract methods, since abstract classes are used for defining subclasses. As far as implementation of this goes, it would simply just need to be type-checked when extending the abstract class (e.g. class extends MyAbstractClass
), not when using it as a type (e.g. let myInstance: MyAbstractClass
).
Example:
abstract class MyAbstractClass {
static abstract bar(): number;
}
class Foo extends MyAbstractClass {
static bar() {
return 42;
}
}
right now out of necessity I use this
abstract class MultiWalletInterface {
static getInstance() {} // can't write a return type MultiWalletInterface
static deleteInstance() {}
/**
* Returns new random 12 words mnemonic seed phrase
*/
static generateMnemonic(): string {
return generateMnemonic();
}
}
this is inconvenient!
I came with a problem where I am adding properties to the "Object", here is a sandbox example
interface Object {
getInstanceId: (object: any) => number;
}
Object.getInstanceId = () => 42;
const someObject = {};
Object.getInstanceId(someObject); // correct
someObject.getInstanceId({}); // should raise an error but does not
Any object instance are now considered to have the property getInstanceId
while only the Object
should. With a static property, the problem would have been solved.
You want to augment ObjectConstructor, not Object. You're declaring an instance method, when you actually want to attach a method to the constructor itself. I think this is already possible through declaration merging:
````ts
declare global {
interface ObjectConstructor {
hello(): string;
}
}
Object.hello();
````
@thw0rted Excellent! thank you I wasn't aware of ObjectConstructor
The larger point is that you're augmenting the constructor-type rather than the instance-type. I just looked up the Object
declaration in lib.es5.d.ts
and found that it's of type ObjectConstructor
.
It's hard for me to believe this issue is still around. This is a legitimately useful feature, with several actual use cases.
The whole point of TypeScript is to be able to ensure type safety in our codebase, so, why is this feature still "pending for feedback" after two years worth of feedback?
I may be way off on this, but would something like Python's metaclasses be able to solve this problem in a native, sanctioned way (i.e. not a hack or workaround) without violating the paradigm of TypeScript (i.e. keeping separate TypeScript types for the instance and the class)?
Something like this:
interface DeserializingClass<T> {
fromJson(serializedValue: string): T;
}
interface Serializable {
toJson(): string;
}
class Foo implements Serializable metaclass DeserializingClass<Foo> {
static fromJson(serializedValue: string): Foo {
// ...
}
toJson(): string {
// ...
}
}
// And an example of how this might be used:
function saveObject(Serializable obj): void {
const serialized: string = obj.toJson();
writeToDatabase(serialized);
}
function retrieveObject<T metaclass DeserializingClass<T>>(): T {
const serialized: string = getFromDatabase();
return T.fromJson(serialized);
}
const foo: Foo = new Foo();
saveObject(foo);
const bar: Foo = retrieveObject<Foo>();
Honestly the trickiest part of this approach seems like it would be coming up with a meaningful, TypeScript-y keyword for metaclass
... staticimplements
, classimplements
, withstatic
, implementsstatic
... not sure.
This is a bit like @GerkinDev's proposal but without the separate kind of interface. Here, there is a single concept of interface, and they can be used to describe the shape of an instance or of a class. Keywords in the implementing class definition would tell the compiler which side each interface should be checked against.
Let's pick up the discussion at #34516 and #33892 depending on which feature you're going for
Most helpful comment
I want to force implementation of static deserialize method in Serializable's subclasses.
Is there any workaround to implement such behaviour?