Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using class decorators, can I get the type of the class-type instance?

Tags:

typescript

Consider this failing example:

function DecorateClass<T>(instantiate: (...params:any[]) => T){
    return (classTarget:T) => { /*...*/ }
}

@DecorateClass((json:any) => {
    //Purely example logic here, the point is that it have to return
    //an instance of the class that the decorator runs on.
    var instance = new Animal();
    instance.Name = json.name;
    instance.Sound = json.sound;
    return instance;
})
class Animal {
    public Name:string;
    public Sound:string;
}

Here I want to constrain the anonymous function in the decorator to always return an instance of the class in question, but the above does not work since T is actually typeof Animal and not Animal.

In a generic function, is there anyway I can get type Animal from the type typeof Animal without being annoyingly verbose like explicitly defining all types like function DecorateClass<TTypeOfClass, TClass>(...)?

Unfortunately, using typeof in the generic syntax is not supported, which was my best bet in trying to get the compiler to understand what I want:

function DecorateClass<T>(instantiate: (json:any) => T){
    return (classTarget:typeof T) => { /*...*/  } // Cannot resolve symbol T
}
like image 982
Alex Avatar asked Feb 12 '16 22:02

Alex


People also ask

Can Python decorators be used on classes?

In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class. We can add functionality to the function by defining methods in the decorating class.

Can you define a decorator in a class?

A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called. The multiply_together method gets passed as the argument to the integer_check function and returns inner.

Is decorator a special class?

A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression , where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.


1 Answers

Hold the line just for a second...

Recently I've needed a type definition for a function that takes in a class as an argument, and returns an instance of that class. When I came up with a solution, this question soon came to my mind.

Basically, using a newable type it is possible to conjure a relation between a class and its instance, which accurately and perfectly answers your question:

function DecorateClass<T>(instantiate: (...args: any[]) => T) {
    return (classTarget: { new(...args: any[]): T }) => { /*...*/ }
}

Explanation

In TypeScript, any given newable type can be defined with the following signature:

new(...args: any[]): any

This is analogous to a newable type (the constructor function) that may or may not take arguments and returns any (the instance). However, nothing says it must be any that is returned -- it can be a generic type as well.

And since we have exactly what is returned from the constructor function (by type-inferring the class the decorator is applied to) inside a generic type parameter we can use that to define the return type of the passed in callback function.

I've tested the decorator, and it seems to be working precisely as expected:

@DecorateClass((json: any) => {
    return new Animal(); // OK
})
@DecorateClass((json: any) => {
    return Animal; // Error
})
@DecorateClass((json: any) => {
    return "animal"; // Error
})
class Animal {
    public Name: string;
    public Sound: string;
}

This effectively invalidates my previous answer.


Edit: Inheritance

When inheritance is involved (eg.: a derived type is to be returned from instantiate), assignability seems to be flipped: you can return a base type, but not a derived type.

This is because the returned type from instantiate takes precedence over the "returned" type of classTarget during generic type-inference. The following question examines this exact problem:

  • Generic type parameter inference priority in TypeScript
like image 63
John Weisz Avatar answered Jan 02 '23 08:01

John Weisz