Typescript: Request: Class Decorator Mutation

Created on 20 Sep 2015  ·  231Comments  ·  Source: microsoft/TypeScript

If we can get this to type check properly, we would have perfect support for boilerplate-free mixins:

declare function Blah<T>(target: T): T & {foo: number}

@Blah
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

new Foo().foo; // Property 'foo' does not exist on type 'Foo'
Needs Proposal Suggestion

Most helpful comment

Same would be useful for methods:

class Foo {
  @async
  bar(x: number) {
    return x || Promise.resolve(...);
  }
}

The async decorator is supposed to change the return type to Promise<any>.

All 231 comments

Same would be useful for methods:

class Foo {
  @async
  bar(x: number) {
    return x || Promise.resolve(...);
  }
}

The async decorator is supposed to change the return type to Promise<any>.

@Gaelan, this is exactly what we are needing here! It would make mixins just natural to work with.

class asPersistent {
  id: number;
  version: number;
  sync(): Promise<DriverResponse> { ... }
  ...
}

function PersistThrough<T>(driver: { new(): Driver }): (t: T) => T & asPersistent {
  return (target: T): T & asPersistent {
    Persistent.call(target.prototype, driver);
    return target;
  }
}

@PersistThrough(MyDBDriver)
Article extends TextNode {
  title: string;
}

var article = new Article();
article.title = 'blah';

article.sync() // Property 'sync' does not exist on type 'Article'

+1 for this. Though I know this is hard to implement, and probably harder to reach an agreement on decorator mutation semantics.

+1

If the primary benefit of this is introducing additional members to the type signature, you can already do that with interface merging:

interface Foo { foo(): number }
class Foo {
    bar() {
        return this.foo();
    }
}

Foo.prototype.foo = function() { return 10; }

new Foo().foo();

If the decorator is an actual function that the compiler needs to invoke in order to imperatively mutate the class, this doesn't seem like an idiomatic thing to do in a type safe language, IMHO.

@masaeedu Do you know any workaround to add a static member to the decorated class?

@davojan Sure. Here you go:

class A { }
namespace A {
    export let foo = function() { console.log("foo"); }
}
A.foo();

It would also be useful to be able to introduce _multiple_ properties to a class when decorating a method (for example, a helper that generates an associated setter for a getter, or something along those lines)

The react-redux typings for connect takes a component and returns a modified component whose props don't include the connected props received through redux, but it seems TS doesn't recognize their connect definition as a decorator due to this issue. Does anyone have a workaround?

I think the ClassDecorator type definition needs changing.

Currently it's declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;. Maybe it could be changed to

declare type MutatingClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type WrappingClassDecorator = <TFunction extends Function, TDecoratorFunction extends Function>(target: TFunction) => TDecoratorFunction;
declare type ClassDecorator = MutatingClassDecorator | WrappingClassDecorator;

Obviously the naming sucks and I have no idea if this sort of thing will work (I am just trying to convert a Babel app over to typescript and am hitting this).

@joyt Could you provide a playground reconstruction of the problem? I don't use react-redux, but as I've mentioned before, I think any extensions you desire to the shape of a type can be declared using interface merging.

@masaeedu here is a basic breakdown of the moving parts..

Basically the decorator provides a bunch of the props to the React component, so the generic type of the decorator is a subset of the decorated component, not a superset.

Not sure if this is helpful, but tried to put together a non-runnable sample to show you the types in play.

// React types
class Component<TProps> {
    props: TProps
}
class ComponentClass<TProps> {
}
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps>): ComponentClass<TOwnProps>;
}

// Redux types
interface MapStateToProps<TStateProps, TOwnProps> {
    (state: any, ownProps?: TOwnProps): TStateProps;
}

// Fake react create class
function createClass(component: any, props: any): any {
}

// Connect wraps the decorated component, providing a bunch of the properies
// So we want to return a ComponentDecorator which exposes LESS than
// the original component
function connect<TStateProps, TOwnProps>(
    mapStateToProps: MapStateToProps<TStateProps, TOwnProps>
): ComponentDecorator<TStateProps, TOwnProps> {
    return (ComponentClass) => {
        let mappedState = mapStateToProps({
            bar: 'bar value'
        })
        class Wrapped {
            render() {
                return createClass(ComponentClass, mappedState)
            }
        }

        return Wrapped
    }
}


// App Types
interface AllProps {
    foo: string
    bar: string
}

interface OwnProps {
    bar: string
}

// This does not work...
// @connect<AllProps, OwnProps>(state => state.foo)
// export default class MyComponent extends Component<AllProps> {
// }

// This does
class MyComponent extends Component<AllProps> {
}
export default connect<AllProps, OwnProps>(state => state.foo)(MyComponent)
//The type exported should be ComponentClass<OwnProps>,
// currently the decorator means we have to export ComponentClass<AllProps>

If you want a full working example I suggest pulling down https://github.com/jaysoo/todomvc-redux-react-typescript or another sample react/redux/typescript project.

According to https://github.com/wycats/javascript-decorators#class-declaration, my understanding is that the proposed declare type WrappingClassDecorator = <TFunction extends Function, TDecoratorFunction extends Function>(target: TFunction) => TDecoratorFunction; is invalid.

The spec says:

@F("color")
@G
class Foo {
}

is translate to:

var Foo = (function () {
  class Foo {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

So if I understand it correctly, the following should be true:

declare function F<T>(target: T): void;

@F
class Foo {}

let a: Foo = new Foo(); // valid
class X {}
declare function F<T>(target: T): X;

@F
class Foo {}

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // INVALID
declare function F<T>(target: T): void;
declare function G<T>(target: T): void;

@F
@G
class Foo {}

let a: Foo = new Foo(); // valid
class X {}
declare function F<T>(target: T): void;
declare function G<T>(target: T): X;

@F
@G
class Foo {}

@G
class Bar {}

@F
class Baz {}

let a: Foo = new Foo(); // valid
let b: X = new Foo(); // INVALID
let c: X = new Bar(); // valid
let d: Bar = new Bar(); // INVALID
let e: Baz = new Baz(); // valid
class X {}
declare function F<T>(target: T): X;
declare function G<T>(target: T): void;

@F
@G
class Foo {}

@G
class Bar {}

@F
class Baz {}

let a: X = new Foo(); // valid
let b: Bar = new Bar(); // valid
let c: X = new Baz(); // valid
let d: Baz = new Baz(); // INVALID

@blai

For your example:

class X {}
declare function F<T>(target: T): X;

@F
class Foo {}

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // INVALID

I'm assuming you mean that F returns a class that conforms to X (and is not an instance of X)? E.g:

declare function F<T>(target: T): typeof X;

For that case, the assertions should be:

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // valid

The Foo that is in scope of those let statements has been mutated by the decorator. The original Foo is no longer reachable. It's effectively equivalent to:

let Foo = F(class Foo {});

@nevir Yep, you are right. Thanks for clarification.

On a side note, it seems like turning off the check to invalidate mutated class types is relatively easy:

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 06591a7..2320aff 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -11584,10 +11584,6 @@ namespace ts {
           */
         function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) {
             switch (node.parent.kind) {
-                case SyntaxKind.ClassDeclaration:
-                case SyntaxKind.ClassExpression:
-                    return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression;
-
                 case SyntaxKind.Parameter:
                     return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression;
         }

         /** Check a decorator */
        function checkDecorator(node: Decorator): void {
             const signature = getResolvedSignature(node);
             const returnType = getReturnTypeOfSignature(signature);
             if (returnType.flags & TypeFlags.Any) {
@@ -14295,9 +14291,7 @@ namespace ts {
             let errorInfo: DiagnosticMessageChain;
             switch (node.parent.kind) {
                 case SyntaxKind.ClassDeclaration:
-                    const classSymbol = getSymbolOfNode(node.parent);
-                    const classConstructorType = getTypeOfSymbol(classSymbol);
-                    expectedReturnType = getUnionType([classConstructorType, voidType]);
+                    expectedReturnType = returnType;
                     break;

                 case SyntaxKind.Parameter:
         }

But I am not knowledgable enough to make the compiler output the correct type definitions of the mutated class. I have the following test:

tests/cases/conformance/decorators/class/decoratorOnClass10.ts

// @target:es5
// @experimentaldecorators: true
class X {}
class Y {}

declare function dec1<T>(target: T): T | typeof X;
declare function dec2<T>(target: T): typeof Y;

@dec1
@dec2
export default class C {
}

var c1: X | Y = new C();
var c2: X = new C();
var c3: Y = new C();

It generates tests/baselines/local/decoratorOnClass10.types

=== tests/cases/conformance/decorators/class/decoratorOnClass10.ts ===
class X {}
>X : X

class Y {}
>Y : Y

declare function dec1<T>(target: T): T | typeof X;
>dec1 : <T>(target: T) => T | typeof X
>T : T
>target : T
>T : T
>T : T
>X : typeof X

declare function dec2<T>(target: T): typeof Y;
>dec2 : <T>(target: T) => typeof Y
>T : T
>target : T
>T : T
>Y : typeof Y

@dec1
>dec1 : <T>(target: T) => T | typeof X

@dec2
>dec2 : <T>(target: T) => typeof Y

export default class C {
>C : C
}

var c1: X | Y = new C();
>c1 : X | Y
>X : X
>Y : Y
>new C() : C
>C : typeof C

var c2: X = new C();
>c2 : X
>X : X
>new C() : C
>C : typeof C

var c3: Y = new C();
>c3 : Y
>Y : Y
>new C() : C
>C : typeof C

I was expecting
>C: typeof C to be >C: typeof X | typeof Y

For those interested in react-redux's connect as a case study for this feature, I've filed https://github.com/DefinitelyTyped/DefinitelyTyped/issues/9951 to track the issue in one place.

I've read all comments on this issue and got an idea that decorator's signature doesn't actually shows what it can do with wrapped class.

Consider this one:

function decorator(target) {
    target.prototype.someNewMethod = function() { ... };
    return new Wrapper(target);
}

It should be typed in that way:
declare function decorator<T>(target: T): Wrapper<T>;

But this signature doesn't tell us that decorator has added new things to the target's prototype.

On the other hand, this one doesn't tell us that decorator has actually returned a wrapper:
declare function decorator<T>(target: T): T & { someMethod: () => void };

Any news on this? This would be super powerful for metaprogramming!

What about a simpler approach to this problem? For a decorated class, we bind the class name to the decorator return value, as a syntactic sugar.

declare function Blah<T>(target: T): T & {foo: number}

@Blah
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

// is desugared to
const Foo = Blah(class Foo {
  // this.foo is not available here
})

new Foo.foo // foo is available here.

Implementation-wise, this will introduce one synthetic symbol for decorated class. And the original class name is only bound to class body scope.

@HerringtonDarkholme I think that would be a nicely pragmatic approach that would provide most of the expressiveness desired. Great Idea!

I definitely want to see this someday

I often write a class for Angular 2 or for Aurelia, that looks like this:

import {Http} from 'aurelia-fetch-client';
import {User} from 'models';

// accesses backend routes for 'api/user'
@autoinject export default class UserService {
  constructor(readonly http : Http) { }

  readonly resourceUrl = 'api/users';

  async get(id: number) {
    const response = await this.http.fetch(this.resourceUrl);
    const user = await response.json() as User;
    return user;
  }

  async post(id: number, model: { [K in keyof User]?: User[K] }) {
    const response = await this.http.post(`${this.resourceUrl}/`${id}`, model);
    return await response.json();
  }
}

What I want to write is something like
decorators/api-client.ts

import {Http} from 'aurelia-fetch-client';

export type Target = { name; new (...args): { http: Http }};

export default function apiClient<T extends { id: string }>(resourceUrl: string) {
  return (target: Target)  => {
    type AugmentedTarget = Target & { get(id: number): Promise<T>, post(id, model: Partial<T>) };
    const t = target as AugmentedTarget;
    t.prototype.get = async function (id: number) {
      const response = await this.http.fetch(resourceUrl);
      return await response.json() as T;
    }
  }
}

and then I could generically apply it like

import {Http} from 'aurelia-fetch-client';
import apiClient from ./decorators/api-client
import {User} from 'models';

@apiClient<User>('api/users') export default class UserService {
  constructor(readonly http : Http) { }
}

with no loss of typesafety. This would be a boon for writing clean, expressive code.

Reviving this issue.

Now that #13743 is out and mixin support is in the language this is a super useful feature.

@HerringtonDarkholme is less suitable for this case though, having to declare the return type of the decorator looses some dynamic features...

@ahejlsberg, @mhegazy Do you think this is doable?

I have another usage scenario I'm not sure is yet covered by this conversation but probably falls under the same umbrella.

I would like to implement a method decorator that changes the type of the method entirely (not the return type or parameters but the entire function). e.g.

type AsyncTask<Method extends Function> = {
    isRunning(): boolean;
} & Method;

// Decorator definition...
function asyncTask(target, methodName, descriptor) {
    ...
}

class Order {
    @asyncTask
    async save(): Promise<void> {
        // Performs an async task and returns a promise
        ...
    }
}

const order = new Order();

order.save();
order.save.isRunning(); // Returns true

Totally possible in JavaScript, that's not the problem obviously, but in TypeScript I need the asyncTask decorator to change the type of the decorated method from () => Promise<void> to AsyncTask<() => Promise<void>>.

Pretty sure this isn't possible now and probably falls under the umbrella of this issue?

@codeandcats your example is the exact same use case I am here for!

Hi @ohjames, forgive me, I'm having trouble groking your example, any chance you could rewrite into something that works as is in the playground?

Any progress on this? I had this in my head all day, unaware of this issue, went to go implement it only to find out that the compiler doesn't pick up on it. I have a project that could use a better logging solution so I wrote a quick singleton to later expand into a full-fledged logger that I was going to attach to classes via a decorator like

@loggable
class Foo { }

and I wrote the necessary code for it

type Loggable<T> = T & { logger: Logger };

function loggable<T extends Function>(target: T): Loggable<T>
{
    Object.defineProperty(target.prototype, 'logger',
        { value: Logger.instance() });
    return <Loggable<T>> target;
}

and the logger property is definitely present at runtime but regrettably not picked up by the compiler.

I would love to see some resolution to this issue, especially since a runtime construct like this should absolutely be able to be properly represented at compile-time.

I ended up settling for a property decorator just to get me by for now:

function logger<T>(target: T, key: string): void
{
    Object.defineProperty(target, 'logger',
        { value: Logger.instance() });
}

and attaching it to classes like

class Foo {
    @logger private logger: Logger;
    ...

but this is far more boilerplate per class utilizing the logger than a simple @loggable class decorator. I suppose I could feasibly typecast like (this as Loggable<this>).logger but this is also pretty far from ideal, especially after doing it a handful of times. It'd get tiresome very quickly.

I had to can TS for an entire app mainly cause I was unable to get https://github.com/jeffijoe/mobx-task working with decorators. I hope this will be addressed soon. 😄

It's very irritating in the Angular 2 ecosystem where decorators and TypeScript are treated as first class citizens. Yet the minute you try to add a property with a decorator the TypeScript compiler says no. I would have thought the Angular 2 team would show some interest in this issue.

@zajrik you can accomplish what you want with class mixins that have been supported with proper typing since TS 2.2:

Define your Loggable mixin like so:

type Constructor<T> = new(...args: any[]) => T;

interface Logger {}

// You don't strictly need this interface, type inference will determine the shape of Loggable,
// you only need it if you want to refer to Loggable in a type position.
interface Loggable {
  logger: Logger;
}

function Loggable<T extends Constructor<object>>(superclass: T) {
  return class extends superclass {
    logger: Logger;
  };
}

and then you can use it in a few ways. Either in the extends clause of a class declaration:

class Foo {
  superProperty: string;
}

class LoggableFoo extends Loggable(Foo) {
  subProperty: number;
}

TS knows that instances of LoggableFoo have superProperty, logger, and subProperty:

const o = new LoggableFoo();
o.superProperty; // string
o.logger; // Logger
o.subProperty; // number

You can also use a mixin as an expression that returns the concrete class you want to use:

const LoggableFoo = Loggable(Foo);

You _can_ also use a class mixin as a decorator, but it has some slightly different semantics, mainly that is subclasses your class, rather than allowing your class to subclass it.

Class mixins have several advantages over decorators, IMO:

  1. They create a new superclass, so that the class you apply them to has a change to override them
  2. They type check now, without any additional features from TypeScript
  3. They work well with type inference - you don't have to type the return value of the mixin function
  4. They work well with static analysis, especially jump-to-definition - Jumping to the implementation of logger takes you to the mixin _implementation_, not the interface.

@justinfagnani I hadn't even considered mixins for this so thank you. I'll go ahead and write up a Loggable mixin tonight to make my Logger attachment syntax a bit nicer. The extends Mixin(SuperClass) route is my preferred as it's how I've used mixins so far since the release of TS 2.2.

I do prefer the idea of decorator syntax to mixins, however, so I do still hope some resolution can be had for this specific issue. Being able to create boilerplate-free mixins using decorators would be a huge boon to cleaner code, in my opinion.

@zajrik glad the suggestion helped, I hope

I still don't quite understand how mixins have more boilerplate than decorators. They're nearly identical in syntactic weight:

Class Mixin:

class LoggableFoo extends Loggable(Foo) {}

vs Decorator:

@Loggable
class LoggableFoo extends Foo {}

In my opinion, the mixin is way more clear about its intention: it's generating a superclass, and superclasses define members of a class, so the mixin is probably defining members as well.

Decorators will be used for so many things that you can't assume that is or isn't defining members. It could simply be registering the class for something, or associating some metadata with it.

To be fair I think what @zajrik wants is:

@loggable
class Foo { }

Which is undeniably, if ever so slightly, less boilerplate.

That said, I love the mixin solution. I keep forgetting that mixins are a thing.

If all you care about is adding properties to the current class then mixins are basically equivalent to decorators with one significant annoyance... if your class doesn't already have a superclass you need to create an empty superclass to use them. Also the syntax seems heavier in general. Also it isn't clear if parametric mixins are supported (is extends Mixin(Class, { ... }) allowed).

@justinfagnani in your list of reasons, points 2-4 are actually deficiencies in TypeScript not advantages of mixins. They don't apply in a JS world.

I think we should all be clear that a mixin based solution to OPs problem would involve adding two classes to the prototype chain, one of which is useless. This reflects the semantic differences of mixins Vs decorators though, mixins give you a chance to intercept the parent class chain. However 95% of the time this isn't what people want to do, they want to decorate this class. Whilst mixins have their limited uses i think promoting them as an alternative to decorators and higher order classes is semantically inappropriate.

Mixins are basically equivalent to decorators with one significant annoyance... if your class doesn't already have a superclass you need to create an empty superclass to use them

This isn't necessarily true:

function Mixin(superclass = Object) { ... }

class Foo extends Mixin() {}

Also the syntax seems heavier in general.

I just don't see how this is so, so we'll have to disagree.

Also it isn't clear if parametric mixins are supported (is extends Mixin(Class, { ... }) allowed).

They very much are. Mixins are just functions.

in your list of reasons, points 2-4 are actually deficiencies in TypeScript not advantages of mixins. They don't apply in a JS world.

This is a TypeScript issue, so they apply here. In the JS world decorators don't actually exist yet.

I think we should all be clear that a mixin based solution to OPs problem would involve adding two classes to the prototype chain, one of which is useless.

I'm not clear where you get two. It's one, just like the decorator might do, unless it's patching. And which prototype is useless? The mixin application presumably adds a property/method, that's not useless.

This reflects the semantic differences of mixins Vs decorators though, mixins give you a chance to intercept the parent class chain. However 95% of the time this isn't what people want to do, they want to decorate this class.

I'm no so sure this is true. Usually when defining a class you expect it to be at the bottom of the inheritance hierarchy, with the ability to override superclass methods. Decorators either have to patch the class, which has numerous problems, including not working with super(), or extend it, in which case the decorated class does not have an ability to override the extension. This can be useful in some cases, like a decorators that overrides every defined method of the class for performance/debugging tracing, but it's far from the usual inheritance model.

Whilst mixins have their limited uses i think promoting them as an alternative to decorators and higher order classes is semantically inappropriate.

When a developer wants to add members to the prototype chain, mixins are exactly semantically appropriate. In every case that I've seen someone want to use decorators for mixins, using class mixins would accomplish the same task, with the semantics that they're actually expecting out of decorators, more flexibility due to working property with super calls, and of course they work now.

Mixins are hardly inappropriate when they directly address the use case.

When a developer wants to add members to the prototype chain

That's exactly my point, the OP doesn't want to add anything to the prototype chain. He just wants to mutate a single class, and mostly when people use decorators they don't even have a parent class other than Object. And for some reason Mixin(Object) doesn't work in TypeScript so you have to add a dummy empty class. So now you have a prototype chain of 2 (not including Object) when you don't need it. Plus there is a non-trivial cost to adding new classes to the prototype chain.

As for the syntax compare Mixin1(Mixin2(Mixin3(Object, { ... }), {... }), {...}). The parameters for each mixin are as far from the mixin-class as they could be. Decorator syntax is clearly more readable.

While decorator syntax per-se doesn't type check, you can just use regular function invocation to get what you want:

class Logger { static instance() { return new Logger(); } }
type Loggable<T> = T & { logger: Logger };
function loggable<T, U>(target: { new (): T } & U): { new (): Loggable<T> } & U
{
    // ...
}

const Foo = loggable(class {
    x: string
});

let foo = new Foo();
foo.logger; // Logger
foo.x; // string

It's just a little annoying that you have to declare your class as const Foo = loggable(class {, but aside from that it all works.

@ohjames (cc @justinfagnani) you have to be careful when extending builtins such as Object (since they bash over your subclass's prototype in instances): https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work

@nevir yep, I already tried @justinfagnani's suggestion of using a mixin with a default Object parameter in the past with TypeScript 2.2 and tsc rejects the code.

@ohjames it still works, you just have to be careful about the prototype in the default case (see that FAQ entry).

Though, it's generally easier to rely on tslib.__extend's behavior when passed null

Any plans to focus this issue in the next iteration step? The benefits of this feature is extreme high accross so much libraries.

I just ran into this issue - it forces me to write a lot of unneeded code. Resolving this issue would be a huge help to any decorator-based framework/library.

@TomMarius As I've mentioned earlier, classes wrapped in decorator functions already type check properly, you just can't use the @ syntax sugar. Instead of doing:

@loggable
class Foo { }

you just need to do:

const Foo = loggable(class { });

You can even compose a bunch of decorator functions together before wrapping a class in them. While making the syntax sugar work properly is valuable, it doesn't seem like this should be such a huge pain point as things are.

@masaeedu Really the issue is not external but internal type support. Being able to use the properties the decorator adds within the class itself without compilation errors is the desired result, at least for me. The example you've provided would only give Foo the loggable type but would not afford the type to the class definition itself.

@zajrik A decorator returns a new class from an original class, even when you use the built in @ syntax. Obviously JS doesn't enforce purity, so you're free to mutate the original class you are passed, but this is incongruent with idiomatic use of the decorator concept. If you're tightly coupling functionality that you're adding via decorators to the class internals, they may as well be internal properties.

Can you give me an example of a use case for a class internally consuming API that is added at some later point via decorators?

The Logger example above is a good example of a common _want_ for being able to manipulate the internals of the decorated class. (And is familiar to people coming from other languages with class decoration; such as Python)

That said, @justinfagnani's class mixin suggestion seems like a good alternative for that case

If you want to be able to define the internals of a class, the structured way to do this isn't to patch the class, or define a new subclass, both which TypeScript will have a hard time reasoning about in the context of the class itself, but to either just define things in the class itself, or create a new superclass that has the needed properties, which TypeScript can reason about.

Decorators really shouldn't change the shape of a class in a way that's visible to the class or most consumers. @masaeedu is right on here.

While what you're saying is true, TypeScript isn't there to enforce clean coding practices, but to correctly type JavaScript code, and it fails in this case.

@masaeedu What @zajrik said. I have a decorator that declares an "online service" that adds bunch of properties to a class that are then used in the class. Subclassing or implementing an interface isn't an option because of missing metadata and constraint enforcement (if you strive for no code duplication).

@TomMarius My point is that it is type checking correctly. When you apply a decorator function to a class, the class is not changed in any way. A new class is produced via some transformation of the original class, and only this new class is guaranteed to support the API introduced by the decorator function.

I don't know what "missing metadata and constraint enforcement" means (perhaps a concrete example would help), but if your class explicitly relies on the API introduced by the decorator, it should just subclass it directly via the mixin pattern @justinfagnani showed, or inject it through the constructor or something. The utility of decorators is that they allow classes that are closed to modification to be extended for the benefit of code that consumes those classes. If you're at liberty to define the class yourself, just use extends.

@masaeedu If you're developing some kind of, let's say, a RPC library, and you want to force the user to write asynchronous methods only, inheritance-based approach forces you to duplicate code (or I haven't found the correct way, maybe - I'd be glad if you told me if you know how).

Inheritance-based approach
Definition: export abstract class Service<T extends { [P in keyof T]: () => Promise<IResult>}> { protected someMethod(): Promise<void> { return Promise.reject(""); } }
Usage: export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } }

Decorator-based approach
Definition: export function service<T>(target: { new (): T & { [P in keyof T]: () => Promise<IResult> } }) { target.someMethod = function () { return Promise.reject(""); }; return target; }
Usage: @service export default class { async foo() { return this.someMethod(); } }

You can clearly see the code duplication in the inheritance-based approach example. It has happened many times to me and my users that they've forgot to change the type parameter when they copy-pasted the class or started to use "any" as the type parameter and the library stopped working for them; the decorator-based approach is much more developer friendly.

After that there's yet another problem with the inheritance-based approach: reflection metadata are missing now, so you have to duplicate code even more because you have to introduce the service decorator anyways. The usage is now: @service export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } }, and that's just plain unfriendly, not just a little inconvience.

You're true that semantically the modification is done after the class is defined, however, there's no way to instantiate the undecorated class, so there's no reason not to properly support mutation of the class other than that it sometimes allows unclean code (but sometimes for the better good). Remember that JavaScript is still based on prototypes, the class syntax is just a sugar to cover it. The prototype is mutable and might be muted by the decorator, and it should be correctly typed.

When you apply a decorator function to a class, the class is not changed in any way.

Not true, when you apply a decorator function to a class, the class may be changed, in any way. Whether you like it or not.

@TomMarius You're trying to exploit inference to enforce some contract, which is totally irrelevant to the argument being had here. You should just do:

function service<T>(target: { new (): T & {[P in keyof T]: () => Promise<any> } }) { return target };

// Does not type check
const MyWrongService = service(class {
    foo() { return ""; }
})

// Type checks
const MyRightService = service(class {
    async foo() { return ""; }
})

There is absolutely no requirement for the internals of the class to be aware of the decorating function.

@masaeedu That wasn't my point. The service decorator/Service class introduces some new properties, and these are always available to the class to be used, but the type system doesn't correctly reflect that. You said I should use inheritance for that, so I've shown you why I can't/don't want to.

I've edited the example to be more clear.

@masaeedu By the way, the statement "A decorator returns a new class from an original class" is incorrect - every decorator we both have shown here returns a mutated or directly the original class, never a new one.

@TomMarius Your comment mentioned a problem with "forcing the user to write asynchronous methods only", which is the problem I tried to address in my comment. Enforcing that the user has followed the contract you expect should be done wherever the user's code is passed back to the library, and doesn't have anything to do with the discussion about whether decorators should change the type-shape presented to the class internals. The orthogonal problem of providing an API to the user's code can be solved with standard inheritance or composition approaches.

@ohjames The class is not changed merely by applying a decorator. JS doesn't enforce purity, so obviously any statement anywhere in your code can modify anything else, but this is irrelevant to this feature discussion. Even once the feature is implemented, TypeScript won't be helping you track arbitrary structural changes within function bodies.

@masaeedu You're picking bits, but I'm talking about the big picture. Please review all my comments in this thread - the point is not in the individual issues but in each issue happening at the same time. I think I explained the problem with inheritance-based approach well - lots and lots of code duplication.

For clarity, this isn't about "clean code" to me. The problem is one of practicality; you don't need massive changes to the type system if you treat @foo the same as function application. If you open up the can of worms of trying to introduce type information to a function's argument from its return type, while at the same time interacting with type inference and all the other magical beasts found in various corners of the TypeScript type system, I feel this will become a become a big obstacle for new features in much the same way as overloading is now.

@TomMarius Your first comment in this thread is about clean code, which is not relevant. The next comment is about this online service concept that you provided the example code for. The primary complaint, all the way from the first paragraph to the fourth, is about how it is error prone to use MyService extends Service<MyService>. I tried to show an example of how you can deal with this.

I've looked at it again, and I really can't see anything in that example that illustrates why the members of the decorated class would need to be aware of the decorator. What is it about these new properties that you provide to the user that cannot be accomplished with standard inheritance? I haven't worked with reflection, so I kind of skimmed over that, my apologies.

@masaeedu I can accomplish it with inheritance, but inheritance forces me/my users to massively duplicate code, so I'd like to have another way - and I do, but the type system isn't able to correctly reflect the reality.

The point is that correct type of @service class X { } where service is declared as <T>(target: T) => T & IService isn't X, but X & IService; and the problem is that in reality it's true even inside the class - even though it semantically isn't true.

Another huge pain point this issue causes is that when you have a series of decorators each having some constraints, the type system thinks that the target is always the original class, not the decorated one, and thus the constraint is useless.

I can accomplish it with inheritance, but inheritance forces me/my users to massively duplicate code, so I'd like to have another way.

This is the part I'm not understanding. Your users need to implement IService, and you want to ensure TheirService implements IService also obeys some other contract { [P in keyof blablablah] }, and perhaps you also want them to have a { potato: Potato } on their service. All of this is easy to accomplish without the class members being aware of @service:

import { serviceDecorator, BaseService } from 'library';

// Right now
const MyService = serviceDecorator(class extends BaseService {
    async foo(): { return ""; }
})

const MyBrokenService1 = serviceDecorator(class extends BaseService {
    foo(): { return; } // Whoops, forgot async! Not assignable
});

const MyBrokenService2 = serviceDecorator(class { // Whoops, forgot to extend BaseService! Not assignable
    async foo(): { return; } 
});

// Once #4881 lands
@serviceDecorator
class MyService extends BaseService {
    async foo(): { return ""; }
}

In neither case is there some giant win in succinctness that can only be achieved by presenting the return type of serviceDecorator as the type of this to foo. More importantly, how do you propose to come up with an even sound-ish typing strategy for this? The return type of serviceDecorator is inferred based on the type of the class you're decorating, which in turn is now typed as the return type of the decorator ...

Hi @masaeedu, the succinctness becomes especially valuable when you have more than one.

@Component({ /** component args **/})
@Authorized({/** roles **/)
@HasUndoContext
class MyComponent  {
  // do stuff with undo context, component methods etc
}

This workaround however is only an alternative to class decorators. For method decorators there is currently no solutions and blocks so much nice implementations. Decorators are in proposal in stage 2 - https://github.com/tc39/proposal-decorators . However there were lot of cases the implementation was made lot earlier. I think especially decorators are one of those important bricks which were really important since they were used already in so much frameworks and a very simple version is already implemented in babel/ts. If this issue could be implemented it wouldn't lose it's experimental state until the official release. But that's "experimental" for.

@pietschy Yes, making the @ syntax sugar work properly with type checking would be nice. At the moment you can use function composition to get reasonably similar succinctitude:

const decorator = compose(
    Component({ /** component args **/ }),
    Authorized({ /** roles **/ })
    HasUndoContext
);

const MyComponent = decorator(class {
});

The preceding discussion is about whether it is a good idea to do some kind of inverse typing where the return type of the decorator is presented as the type of this to the class members.

Hi @masaeedu, yep I understood the context of the discussion, hence the // do stuff with undo context, component methods etc. Cheers

really need make Mixins easier.

typescript (javascript) do not support multiple inheritance, so we must use Mixins or Traits.
And now It's wasting me so many time, especially when I reconstruct something.
And I must copy & paste an interface with its "empty implementation" everywhere. (#371)

--
I do not think the return type of decorator should presented on the class.
because: .... emmm. not know how to describe it, sorry for my poor english skill ... ( 🤔 can a photo exists without a photo frame ? ) this job is for an interface.

Gonna add my +1 to this one! I'd love to see it coming soon.

@pietschy If the class is dependent on members added by the decorators, then it should be extending the result of the decorators, not the other way around. You should do:

const decorator = compose(
    Component({ /** component args **/ }),
    Authorized({ /** roles **/ })
    HasUndoContext
);

class MyComponent extends decorator(class { }) {
    // do stuff with undo context, component methods etc
};

The alternative is for the type system to work in some kind of loop, where the decorator function's argument is contextually typed by its return type, which is inferred from its argument, which is contextually typed by its return type, etc. We still need a specific proposal for how this should work, it isn't just waiting on implementation.

Hi @masaeedu, I'm confused as to why I'd have to compose my decorators and apply that to a base class. As far as I understand it the prototype has already been modified by the time any user land code executes. E.g.

function pressable<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        pressMe() {
            console.log('how depressing');
        }
    }
}

@pressable
class UserLandClass {
    constructor() {    
        this['pressMe'](); // this method exists, please let me use code completion.
    }
}

console.log(new UserLandClass());

So if methods defined by decorators already exist and can legitimately called then it would be nice if typescript reflected this.

The desire is for typescript to reflect this without imposing workarounds. If there are other
things that decorators can do that can't be modelled then it would at least be nice if this
scenario was supported in some shape or form.

This thread is full of opinions on what decorators should do, how they should be used, how they shouldn't be used, etc ad nauseam.

This is what decorators actually do:

function addMethod(Class) : any {
    return class extends Class {
        hello(){}
    };
}

@addMethod
class Foo{
    originalMethod(){}
}

which becomes, if you're targeting esnext

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

function addMethod(Class) {
    return class extends Class {
        hello() {
        }
    };
}
let Foo = class Foo {
    originalMethod() { }
};
Foo = __decorate([
    addMethod
], Foo);

with __decorate applying each decorator from bottom to top, with each one having the option to return a whole new class.

I understand getting the type system to support and acknowledge this may very well be tricky, and I understand it may very well take time to support this, but can we not all agree that the current behavior, from the original code sample above that started this thread, is simply incorrect?

No matter what any developer's opinion is on decorators or mixins or functional composition etc etc etc, this appears to be a pretty clear bug.


Can I politely inquire as to whether this happens to be in the pipeline for a future release?

TypeScript is simply amazing and I love it; this however seems like one of the few (only?) pieces that's simply broken, and I'm just looking forward to seeing it fixed :)

@arackaf It is well understood what decorators actually do, and assuming you don't care about the types TypeScript's emit already supports decorators. The entire debate in this issue is about how decorated classes should be presented in the type system.

I agree that new Foo().foo being a type error is a bug, and an easily fixed one at that. I do not agree that return this.foo; being a type error is a bug. If anything, you are asking for a feature in the type system that so far no one in this issue has yet specified. If you have some mechanism in mind by which the type of this should be transformed by a decorator applied to the containing class, you need to suggest this mechanism explicitly.

Such a mechanism is not as trivial as you might expect, because in nearly all cases the return type of the decorator is inferred from the very type you are proposing to transform using the return type of the decorator. If decorator addMethod takes a class of type new () => T and produces new() => T & { hello(): void }, it doesn't make sense to suggest that T should then be T & { hello(): void }. Within the decorator body, is it valid for me to refer to super.hello?

This is especially pertinent because I'm not restricted to doing return class extends ClassIWasPassed { ... } in the body of the decorator, I can do whatever I please; subtraction types, mapped types, unions, they're all fair game. Any resultant type must play well with this inference loop you're suggesting. As an illustration of the problem, please tell me what should happen in this example:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello()) // Do I get a type error here? After all, the signature of Class is { hello: () => void }
        } 
    };
}

// Or should I get the error here? Foo does not conform to { hello(): string }
@logger
class Foo {
    hello() { return "foo"; }
}

You can't just handwave the problem away as being "tricky", someone needs to actually propose how this should work.

Not an error - you're passing Logger a class which conforms to Greeter; however Foo is then mutated by the decorator to be something completely different, which no longer extends Greeter. So it should be valid for the logger decorator to treat Foo as Greeter, but invalid for anyone else to, since Foo is reassigned to something completely different by the decorator.

I'm sure implementing this would be visciously difficult. Is some reasonable subset possible, for the common cases like the one listed at the top of this thread, with workarounds like interface merging being fallbacks for tricky edge cases like this?

@arackaf

however Foo is then mutated by the decorator to be something completely different

There is no precise definition for what Foo is in the first place. Remember that our entire point of contention is whether Foo's members should be able to access (and hence return as part of the public API), decorator introduced members from this. What Foo is is defined by what the decorator returns, and what the decorator returns is defined by what Foo is. They're inseparably spliced.

I'm sure implementing this would be viciously difficult

It is still premature to talk about implementation complexity, when we don't even have a solid proposal for how the feature should work. Apologies for harping on this, but we really need someone to propose a concrete mechanism for "what happens to this when I apply such a sequence of decorators to such a class". We can then plug in various TypeScript concepts in different parts and see whether what comes out makes sense. Only then does it make sense to discuss implementation complexity.

Is the current decorator output that I pasted above not spec compliant? I assumed it was from what I've seen.

Assuming it is, Foo has a fairly precise meaning every step of the way. I would expect TS to allow me to use this (Inside of Foo methods and on instances of Foo) based on what the last decorator returns, with TS forcing me to add type annotations along the way as needed if the decorator functions aren't sufficiently analyzable.

@arackaf There are no "steps of the way"; we're only concerned with the final type of this for a given immutable code snippet. You need to describe, at least at a high level, what you expect the type of this should be for members of a class X decorated with decorator functions f1...fn, in terms of the type signatures of X and f1...fn. You can have as many steps in the process as you like. So far, no one has been doing this. I've been guessing that people mean the return type of the decorator should be presented as the type of this, but for all I know I could be totally off the mark.

If your proposal is to mechanically make the types reflect what is happening to the values in the transpiled output, you end up with what I'm proposing instead of what you're proposing: i.e. new Foo().hello() is fine, but this.hello() is not. At no point in that example does the original class you are decorating gain a hello method. Only the result of __decorate([addMethod], Foo) (which is then assigned to Foo) has a hello method.

I've been guessing that people mean the return type of the decorator should be presented as the type of this

Oh, sorry, yes, that's right. Exactly that. Full stop. Because that's what the decorators DO. If the last decorator in line returns some completely new class, then that IS what Foo is.

In other words:

@c
@b
@a
class Foo { 
}

Foo is whatever in the world c says it is. If c is a function that returns any then, I don't know - maybe just fallback to the original Foo? That seems like a reasonable, backward compatible approach.

but if c returns some new type X, then I absolutely would expect Foo to respect that.

Am I missing something?


clarifying further, if

class X { 
    hello() {}
    world() {}
}
function c(cl : any) : X {  // or should it be typeof X ?????
    //...
}

@c
@b
@a
class Foo { 
    sorry() {}
}

new Foo().hello(); //perfectly valid
new Foo().sorry(); //ERROR 

Am I missing something?

@arackaf Yes, what this naive approach is missing is that a decorator is free to return arbitrary types, with no restriction that the result be compatible with the type of Foo.

There's any number of absurdities you can produce with this. Let's say I suspend my objection about the circularity of typing this as the result of c, which is determined by the type of this, which is determined by the result of c, etc. This still doesn't work. Here's another nonsensical example:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}


@logger
class Foo {
    foo() { return "bar" }
    // Whoops, `this` is `{ hello(): void }`, it has no `foo` method
    hello() { return this.foo(); }
}

For this case, you're either forced to produce an error for totally benign code, or adjust the proposition that this be exactly identical to the return type of the decorator.

I'm not sure I see the problem. @logger has chosen to return a brand new class, without any foo method, and with a totally new hello() method, which happens to reference back to the original, now-unreachable Foo.

new Foo().foo()

is no longer valid; it will produce a runtime error. I'm just saying it should also produce a compile-time error.

That said, if statically analyzing all of that is too hard, it would be totally reasonable imo to force us to add an explicit type annotations to logger to signify exactly what is being returned. And if no such type annotation is present, then I'd say just revert to assuming Foo is returned back. That should keep it backward compatible.

@arackaf There is no problem with the code in terms of typing or runtime evaluation. I can call new Foo().hello(), which will internally call the decorated class's hello, which will call the decorated class's bar. It is not an error for me to invoke bar inside the original class.

You can try it out for yourself by running this full example in the playground:

// Code from previous snippet...

const LoggerFoo = logger(Foo)
new LoggerFoo().hello()

Sure - but I said it was an error to invoke

new Foo().foo()

There is no problem with the code in terms of typing or runtime evaluation. I can call new Foo().hello(), which will internally call the decorated class's hello, which will call the decorated class's bar

But it should be an error to say

let s : string = new Foo().hello();

since Foo's hello method now returns void, per the class that Logger returns.

Sure - but I said it was an error to invoke new Foo().foo()

@arackaf But that doesn't matter. I didn't invoke new Foo().foo(). I invoked this.foo(), and I got a type error, even though my code works fine at runtime.

But it should be an error to say let s : string = new Foo().hello()

Again, this is irrelevant. I am not saying the final type of Foo.prototype.hello should be () => string (I agree it should be () => void). I am complaining about the valid invocation this.bar() erroring out, because you've surgically transplanted a type where it is nonsensical to transplant it.

There are two Foo's here. When you say

class Foo { 
}

Foo is an immutable binding WITHIN the class, and a mutable binding outside the class. So this works perfectly as you can verify in a jsbin

class Foo { 
  static sMethod(){
    alert('works');
  }
  hello(){ 
    Foo.sMethod();
  }
}

let F = Foo;

Foo = null;

new F().hello();

Your example above does similar things; it captures a reference to the original class before that outer binding is mutated. I'm still not sure what you're driving at.

this.foo(); is perfectly valid, and I wouldn't expect a type error (I also wouldn't blame the TS people if I needed an any reference, since I'm sure tracing this through would be hard)

this.foo(); is perfectly valid, and I wouldn't expect a type error

Good, so we agree, but this means you now have to qualify or dismiss the proposal that this be the instance type of whatever the decorator returns. If you think it shouldn't be a type error, what should this be instead of { hello(): void } in my example?

this depends on what was instantiated.

@c
class Foo{
}

new Foo(). // <---- this is based on whatever c returned 

function c(Cl){
    new Cl().  // <----- this is an object whose prototype is the original Foo's prototype
                   // but for TS's purpose, for type errors, it'd depend on how Cl is typed
}

Could we please stick to one concrete example? That would make things much easier for me to understand. In the following snippet:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}

@logger
class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); } /// <------
}

What is the type of this? If it is { hello(): void }, then I will get a type error, because foo is not a member of { hello(): void }. If it is not { hello(): void }, then this is not simply the instance type of the decorator's return type, and you need to explain whatever alternate logic you're using to arrive at the type of this.

EDIT: Forgot to add the decorator on Foo. Fixed.

this, where you have the arrows, is of course an instance of the original Foo. There's no type error.

Ah - I see your point now; but I still don't see where the problem is. this.foo() WITHIN the original Foo is not a type error - that is valid for the (now unreachable) class that used to be bound to the identifier Foo.

It's an idiosyncrasy, fun trivia, but I don't see why this should prevent TS from handling mutating class decorators.

@arackaf You're not answering the question. What, specifically, is the type of this? You can't just endlessly circularly answer "this is Foo and Foo is this". What members does this have? If it has any members besides hello(): void, what logic is being used to determine them?

When you say "this.foo() WITHIN the original Foo is not a type error", you still need to answer the question: what is the structural type of this such that it is not a type error to do this.foo()?

Also, the original class is not "unreachable". Every function defined in that code snippet is actively exercised at runtime, and it all works without a hitch. Please run the playground link I provided and look at the console. The decorator returns a new class in which the hello method delegates to the hello method of the decorated class, which in turn delegates to the foo method of the decorated class.

It's an idiosyncrasy, fun trivia, but I don't see why this should prevent TS from handling mutating class decorators.

There's no "trivia" in the type system. You're not going to get a TSC-1234 "naughty boy, you can't do that" type error because the use case is too niche. If a feature is causing perfectly normal code to break in surprising ways, the feature needs to be rethought.

You're not going to get a TSC-1234 "naughty boy, you can't do that" type error because the use case is too niche.

That's EXACTLY what I get when I try to use a method that was added to a class definition by a decorator. I currently have to work around it by either adding a definition to the class, or using interface merging, casting as any etc.

I've answered every question about what this is, and where.

The simple fact of the matter is that the meaning of this changes based on where you are.

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}

class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); } /// <------
}

const LoggableFoo = logger(Foo)
new LoggableFoo().hello() // Logs "bar"

when you say new Class() - Class is pointing to the original Foo, and from TypeScript's perspective it'd have access to hello(): string since that's what Class is typed as (extends Greeter). At runtime you'll be instantiating an instance of the original Foo.

new LoggableFoo().hello() happens to call a void method that happens to call a method that's otherwise unreachable, typed through Greeter.

If you'd done

@logger
class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); }
}

then Foo is now a class with only hello(): void and new Foo().foo() should be a Type error.

And, again, hello() { return this.foo(); } is not a TypeError - why would it be? Just because that class definition is no longer reachable doesn't somehow make that method invalid.

I don't expect TypeScript to get any of these edge cases perfect; it'd be pretty understandable to have to add type annotations here and there, like always. But none of these examples show why @logger can't fundamentally change what Foo is bound to.

If logger is a function which returns a new class, then THAT is what Foo now references.

I've answered every question about what this is, and where.
The simple fact of the matter is that the meaning of this changes based on where you are.

This is really frustrating. Fine, it changes, it is Foo, it is a static binding, etc. etc. etc. What is the type signature? What members does this have? You're talking about everything under the sun, when all I need from you is a simple type signature for what this is inside hello.

new LoggableFoo().hello() happens to call a void method that happens to call a method that's otherwise unreachable, typed through Greeter.

This is not the same as unreachable. Every reachable method is "otherwise unreachable" when you discount the paths through which it is reachable.

If you'd done:

But this is literally what I have done. That is exactly my code snippet, pasted again, prefaced with an explanation of the example I constructed for you. I even ran it through a diff checker to make sure I wasn't taking crazy pills, and the only difference is the comment you removed.

And, again, hello() { return this.foo(); } is not a TypeError - why would it be?

Because you (and others here) want the type of this to be the instantiated return type of the decorator, which is { hello(): void } (notice the absence of a foo member). If you want the members of Foo to be able to see this as the return type of the decorator, the type of this inside hello will be { hello(): void }. If it is { hello(): void }, I get a type error. If I get a type error, I am sad, because my code runs fine.

If you say it is not a type error, you are abandoning your own scheme for supplying the type of this via the return type of the decorator. The type of this is then { hello(): string; bar(): string }, regardless of what the decorator returns. You may have some alternative scheme for producing the type of this that avoids this problem, but you need to specify what it is.

You seem to not understand that, after the decorators run, Foo can reference something totally separate from what it was originally defined as.

function a(C){
    return class {
        x(){}
        y(){}
        z(){}
    }
}

@a
class Foo {
    a(){ 
        this.b();  //valid
    }
    b() { 
        this.c();  //also valid
    }
    c(){ 
        return 0;
    }
}

let f = new Foo();
f.x(); //valid
f.y(); //also valid
f.z(); //still valid

I surmise you find some strangeness in this being something different inside of Foo above, than it is in instances that are subsequently created from what Foo eventually is (after the decorators run)?

I'm not really sure what to tell you; that's just how decorators work. My only argument is that TypeScript should more closely match what's happening.

To put it another way, the type signatures inside the (original) Foo will be different than what Foo is/produces once the decorators have run.

To borrow an analogy from another language, inside the decorated Foo this would be referencing the equivalent of a C# anonymous type—a totally real type, that's otherwise valid, just not really able to be referenced directly. It would look weird getting the errors and non-errors described above, but again, that's just how it works. Decorators give us tremendous power to do bizarre things like this.

I surmise you find some strangeness in this being something different inside of Foo above, than it is in instances that are subsequently created from what Foo eventually is (after the decorators run)?

No. I don't find any strangeness in that, because it is exactly what I was proposing 200 comments ago. Did you even read any of the preceding discussion?

The snippet you have posted is totally uncontroversial. The people I was disagreeing with, and whose aid you jumped to additionally want the following:

function a(C){
    return class {
        x(){}
        y(){}
        z(){}
    }
}

@a
class Foo {
    a(){ 
        this.b();  //valid
    }
    b() { 
        this.c();  //also valid
    }
    c(){ 
        return 0;
    }
    d(){
        // HERE: All of these are also expected to be valid
        this.x();
        this.y();
        this.z();
    }
}

let f = new Foo();
f.x(); //valid
f.y(); //also valid
f.z(); //still valid

I disagree that it is possible to do this soundly, and have desperately been trying to figure out how such a suggestion would work. Despite my best efforts, I can't extract a comprehensive list of the members this is expected to have, or how such this list would be constructed under the proposal.

To put it another way, the type signatures inside the (original) Foo will be different than what Foo is/produces once the decorators have run.

So why are you even arguing with me? I said: "I agree that new Foo().foo being a type error is a bug, and an easily fixed one at that. I do not agree that return this.foo; being a type error is a bug". Analogously, in your example, I agree that new Foo().x() being a type error is a bug, but this.x() being a type error is not.

You see how there are two comments in the snippet at the top of this page?

        return this.foo; // Property 'foo' does not exist on type 'Foo'

^ That one is the one I find problematic. Either you agree that the decorator's return type shouldn't be presented on this, and only on new Foo(), in which case we're both arguing about nothing. Or you don't agree and want that feature as well, in which case the snippet in your previous comment is irrelevant.

I finally understand your point. It was extremely hard for me to get this from your Greeter code, but I'm tracking now; thank you for being so patient.

I'd say the only sensible solution would be for Foo (inside of Foo) to support the type union - derp I meant intersection - of the original Foo, and whatever the last decorator returns. You have to support the original Foo for crazy examples like your Greeter, and you definitely need to support whatever the last decorator returns since that's the whole point of using decorators (per the many comments above).

So yeah, from my most recent example, inside of Foo x, y, z, a, b, c would all work. If there were two versions of a, then support both.

@arackaf np, thanks for your patience as well. My examples haven't been the clearest because I'm just posting whatever I can cook up in playground that demonstrates how it's broken. It's hard for me to think about it systematically.

I'd say the only sensible solution would be for Foo (inside of Foo) to support the type union of the original Foo, and whatever the last decorator returns.

Ok awesome, so we're getting into the specifics of it now. Correct me if I'm getting this wrong, but when you say "union" you mean it should have the type members from both, i.e. it should be A & B. So we want this to be typeof(new OriginalClass()) & typeof(new (decorators(OriginalClass))), where decorators is the composed type signature of all the decorators . In plain English, we want this to be the intersection of the instantiated type of the "original class" and the instantiated type of the "original class" passed through all the decorators.

There's two problems with this. One is that in cases like my example, this just allows you to access non-existent members. I could add a bunch of members in the decorator, but if I tried accessing them in my class using this.newMethod(), it would just puke at runtime. newMethod is only added to the class returned from the decorator function, the original class's members have no access to it (unless I specifically happen to use the return class extends OriginalClass { newMethod() { } } pattern).

The other problem is that "original class" isn't a well defined concept. If I can access decorated members from this, I can also use them as part of the return statements, and hence they may be part of the "original class"'s public API. I'm kind of handwaving here, and I'm a bit too burnt out to think of concrete examples, but I think if we thought hard about it we could come up with nonsensical examples. Maybe you could work around this by finding a some way to segregate members that don't return things they accessed from this or at least for which the return type is not inferred as a result of returning this.something().

@masaeedu yeah, I corrected myself on the union / intersection thing prior to your reply. It's counter-intuitive for someone new to TS.

Understood on the rest. Honestly decorators won't usually return a completely different type, they'll usually just augment the type that was passed in, so the intersection thing will "just work" safely most of the time.

I'd say the runtime errors you talk about would be rare, and the result of some purposefully bad development decisions. I'm not sure you should really need to care about this but, if it truly is a problem, I'd say just using what the last decorator returned would be a decent second place (so yeah, a class could see a TypeError by trying to use a method that itself defines - not ideal, but still a worthwhile price to pay for decorators working).

But really, I think the runtime errors you're thinking of are not worth preventing, certainly at the expense of decorators working correctly. Besides, it's quite easy to produce runtime errors in TS if you're careless or silly.

interface C { a(); }
class C {
    foo() {
        this.a();  //<--- boom
    }
}

let c = new C();
c.foo();

Regarding your second objection

I can also use them as part of the return statements, and hence they may be part of the "original class"'s public API

I'm afraid I don't see any problems with that. I want anything added to the class via a decorator to absolutely be a first-class citizen. I'm curious what the potential problems there would be.

I think one other good option would be to only implement this partially.

Currently, classes are always typed as they're defined. So inside Foo, this is based on however foo is defined, regardless of decorators. It would be a huge, huge improvement to "just" extend that for some useful subset of decorator use cases (ie the most common ones)

What if you allow the class to be extended (from the perspective of this inside the class) if and only if the decorator returns something that extends the original, ie for

function d(Class) {
    return class extends Class {
        blah() { }
    };
}

@d
class Foo {
    a() { }
    b() { }
    c() { 
        this.blah(); // <---- valid
    }
}

Have it just work, and provide first-class support to blah and anything else added by the decorator. And for use cases that do crazy things like return a whole new class (like your Greeter), just continue current behavior, and ignore what the decorator is doing.


Btw, regardless of what you choose, how would I annotate that? Can this be annotated currently? I tried

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

as well as many variations on that theme, but TypeScript was having none of it :)

@arackaf A decorator is just a function. In the general case you'll be getting it from a .d.ts file somewhere, and have no idea how it is implemented. You don't know whether the implementation is returning a whole new class, adding/subtracting/replacing members on the prototype of the original class and returning it, or extending the original class. All you have available is the structural return type of the function.

If you want to somehow tie decorators to class inheritance, you need to go propose a separate language concept for JS first. The way decorators work today does not justify mutating this in the general case. As an example, I personally always prefer composition over inheritance, and would always do:

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        readonly _impl;
        constructor() {
            this._impl = new Class()
        }
        // Use _impl ...
    };
}

This isn't some crazy academic experiment, it's a bog standard approach for doing mixins, and it breaks with the example I gave you. In fact almost anything except return class extends Class breaks with the example I gave you, and in many cases things will break even with return class extends Class.

You're going to have to jump through all kinds of hoops and contortions to make this work, and the type system will fight you every step of the way, because what you are doing is nonsensical in the general case. More importantly, once you implement it, everyone will have to acrobatically maneuver around this nonsensical corner of the type system whenever they're trying to implement some other complex (but sound) concept.

Just because you have this one use case that you feel is important (and I've tried several times in this thread to demonstrate alternative ways to express what you want soundly in the existing type system), doesn't mean that the only right thing to do is to bungle onwards with your suggested approach at any cost. You can merge arbitrary interfaces to your class, including the return types of decorator functions, so it's not like it's impossible for you to get anywhere if you insist on using decorators the way you're using them.

@arackaf this:

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

Should work fine in the extends clause:

class C extends d(S) {
  foo() {
    this.blah(); // tsc is happy here
  }
}

With much easier and already defined semantics in the type system. This already works. What problem are you trying to solve by subclasses the declared class, rather than creating a superclass for it?

@masaeedu

The way decorators work today does not justify mutating this in the general case.

The primary use for (class) decorators is absolutely, positively to mutate the value of this inside the class, one way or another. Redux's @connect and MobX's @observer both take in a class, and spit out a NEW version of the original class, with added features. In those particular instances I don't think the actual structure of this changes (just the structure of this.props), but that's immaterial.

It is a pretty common (as you can see from the comments above) use case to use a decorator to mutate a class somehow). For better or worse people tend to just dislike the syntactic alternatives

let Foo = a(b(c(
    class Foo {
    }
})));

as opposed to

@a
@b
@c
class Foo {
}

Now, whether a decorator does

function d (Class){
    Class.prototype.blah = function(){
    };
}

or

function d(Class){
    return class extends Class {
        blah(){ }
    }
}

shouldn't matter. One way or the other, the use case that many are really clamoring for, is to be able to tell TypeScript, by whatever annotations necessary, no matter how inconvenient for us, that function c takes a class C in, and returns a class that has the structure C & {blah(): void}.

That's what many of us are actively using decorators for today. We just really, really want to integrate a useful subset of this behavior into the type system.

It's easily demonstrated that decorators can do bizarre things that would be impossible for the type system to track. Fine! But there must be some way of annotating that

@c
class Foo {
    hi(){ this.addedByC(); }
}

is valid. I don't know if it'll require new type annotation syntax for c, or if existing type annotation syntax could be pressed into service on c to accomplish this, but just fixing the general use case (and leaving the edges as is, with no effect happening on the original class) would be a huge boon to TS users.

@justinfagnani fyi this

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

produces

Type 'typeof (Anonymous class)' is not assignable to type 'T & (new () => { blah(): void; })'.
Type 'typeof (Anonymous class)' is not assignable to type 'T'.

in the TS playground. Not sure if I have some options set wrong.

What problem are you trying to solve by subclasses the declared class, rather than creating a superclass for it?

I'm merely trying to use decorators. I've happily been using them in JS for well over a year - I'm just eager for TS to catch up. And I'm certainly not married to subclassing. Sometimes the decorator just mutates the original class's prototype, to add properties or methods. Either way, the goal is to tell TypeScript that

@c
class Foo { 
}

will result in a class with new members, created by c.

@arackaf We're drifting out of sync again. The debate is not about this:

@a
@b
@c
class Foo {
}

vs this:

let Foo = a(b(c(
    class Foo {
    }
})));

I expect you to be able to use the former and still have proper typing of new Foo(). The debate is about this:

@a
@b
@c
class Foo {
    fooMember() { 
        this.aMember(); this.bMember(); this.cMember(); 
    }
}

vs this:

class Foo extends (@a @b @c class { }) {
    fooMember() { 
        this.aMember(); this.bMember(); this.cMember(); 
    }
}

The latter will work without breaking the type system. The former is problematic in several ways I have already illustrated.

@masaeedu are there really no restricted use cases in which the former could be made to work?

To be precise: are there no annotations TypeScript could tell us to add to a, b, and c from your example above in order for the type system to treat the former indistinguishably from the latter?

That would be a killer advancement for TypeScript.

@arackaf sorry, you do have to make the type parameter refer to the constructor type, not the instance type:

This works:

type Constructor<T = object> = new (...args: any[]) => T;

interface Blah {
  blah(): void;
}

function d<T extends Constructor>(Class: T): T & Constructor<Blah> {
  return class extends Class {
    blah() { }
  };
}

class Foo extends d(Object) {
  protected num: number;

  constructor(num: number) {
    super();
    this.num = num;
    this.blah();
  }
}

I'm merely trying to use decorators.

That isn't really a problem you're trying to solve, and it's a tautology. It's like Q: "what are trying to do with that hammer?" A: "Just use the hammer".

Is there an actual case you have in mind where generating a new superclass with the intended extension doesn't work? Because TypeScript supports that now, and as I said, it's much easier to reason about the type of the class from inside the class in that case.

I looked at Mobx's @observer annotation and it looks like it doesn't change class shape (and could easily be a method decorator rather than a class decorator since it only wraps render()).

@connect is actually a great example of the complexities of typing decorators properly, because @connect appears to not even return a subclass of the decorated class, but an entirely new class that wraps the decorated class, yet the statics are copied over, so the result throws away the instance-side interface and preserves the static side which TS doesn't even check in most cases. It looks like that @connect really shouldn't be a decorator at all and connect should just be used as a HOC, because as a decorator, it's terribly confusing.

I've happily been using them in JS for well over a year

Well... you haven't really. JS doesn't have decorators. You've likely been using a particular defunct previous proposal, that happens to be implemented slightly differently in Babel and TypeScript. In other venues I'm arguing for decorators to continue along the standardization process, but progress has slowed and I don't think they're a certainty yet. Many argue that they shouldn't be added, or only be annotations, so the semantics may even still be up in the air.

One of the complaints against decorators is exactly that they can change the shape of the class, making it hard to reason about statically, and some proposals have been talked about at least to limit decorators ability to do that, though I think that was before the most recent proposal got spec language. I personally argue that well-behaved decorators _shouldn't_ change the shape of the class, so that for the purposes of static analysis they can be ignored. Remember that standard JS doesn't have a type system to lean on to tell analysis what a function implementation is doing.

  • I'm just eager for TS to catch up.

I don't see how TypeScript is behind in any way here. At least, behind what? Decorators do work. Is there another typed JS where classes behave the way you want?

Decorators are now stage 2, and used in just about every JS framework.

As for @connect react-redux is sitting at about 2.5 million downloads per month; it's incredibly arrogant to hear that the design is somehow "wrong" because you would have implemented it differently.

Current attempts to use methods introduced by a decorator result in type errors in TypeScript, requiring workarounds like interface merging, manually adding the annotation for the added methods, or just not using decorators to mutate classes (as though anyone needed to be told we could just not use decorators).

Is there really, truly no way to manually, painstakingly inform the TS compiler that a decorator is changing a classes shape? Maybe this is crazy but couldn't TS just ship a new decorator type that we could use

let c : decorator

And IF (and only if) decorators of that type are applied to a class, TS will consider the class to be of type Input & { blah() } ?

Literally everyone knows we can just not use decorators. Most also know that a vocal group dislike decorators. This post is about how TS could possibly, somehow get its type system to understand that a decorator is mutating a class definition. That would be a major boon to a lot of people.

To be precise: are there no annotations TypeScript could tell us to add to a, b, and c from your example above in order for the type system to treat the former indistinguishably from the latter?

Yes, it's very easy to declare whatever shape for Foo you want, you just need to use interface merging. You just need to do:

interface Foo extends <whateverextramembersiwantinfoo> { } 
@a
@b
@c
class Foo { 
    /* have fun */
}

Right now you probably need to expect whatever decorator library you're using to export parametrized types corresponding to what they're returning, or you'll need to declare the members they're adding yourself. If we get #6606 you can just do interface Foo extends typeof(a(b(c(Foo)))).

Current attempts to use methods introduced by a decorator result in type errors in TypeScript, requiring workarounds like interface merging, manually adding the annotation for the added methods, or just not using decorators to mutate classes (as though anyone needed to be told we could just not use decorators).

You didn't mention the option of decorating the class you're writing when it is agnostic to the decorator introduced members, and making it extend a decorated class when it isn't. This is how I'll be using decorators at least.

Could you give me an code snippet from a library you're using right now where this is a pain point?

Sorry - I didn't follow your last comment, but your comment before that, with the interface merging, is exactly how I'm working around it.

Currently my decorator needs to also export a Type, and I use interface merging exactly as you showed, to indicate to TS that new members are being added by the decorator. It's the ability to ditch this added boilerplate that many are so eager for.

@arackaf Perhaps what you really want is a way for declaration merging to interact with contextual typing of function arguments.

Sure. I want the ability to use a decorator on a class, and conceptually, based on how I've defined the decorator, to have some interface merging happen, resulting in my decorated class having extra members added.

I have no idea how I would go about annotating my decorator to get that to happen, but I'm not really picky - any syntax a future TS version gave me to affect that would make me incredibly happy :)

Decorators are now stage 2, and used in just about every JS framework.

And they are only at stage 2 and have been moving extremely slowly. I badly want decorators to happen, and I'm trying to figure out how I can help them move along, but they are still not a certainty. I still know of people who might have influence on the process who object to them, or want them limited to annotations, though I don't _think_ they'll interfere. My point about current compiler decorator implementations not being JS stands.

As for @connect react-redux is sitting at about 2.5 million downloads per month; it's incredibly arrogant to hear that the design is somehow "wrong" because you would have implemented it differently.

Please refrain from personal attacks like calling me arrogant.

  1. I didn't personally attack you, so don't personally attack me.
  2. It doesn't help your argument, at all.
  3. A project's popularity shouldn't exclude it from technical critique.
  4. Are we talking about the same thing? redux-connect-decorator has 4 downloads a day. react-redux's connect() function looks like a HOC, like I suggested.
  5. I think that my opinion that well-behaved decorators shouldn't change the public shape of a class, and certainly should replace it with an unrelated class, is pretty reasonable, and would find a enough agreement around the JS community. It's pretty far from arrogant, even if you disagree.

Current attempts to use methods introduced by a decorator result in type errors in TypeScript, requiring workarounds like interface merging, manually adding the annotation for the added methods, or just not using decorators to mutate classes (as though anyone needed to be told we could just not use decorators).

It's not that we're just suggesting to not use decorators at all, it's that I believe @masaeedu are saying that for the purposes of modifying a prototype in a type-safe way, we have existing mechanisms: inheritance and mixin application. I was trying to ask why your d example wasn't suitable to be used as a mixin, and you didn't give an answer except to say that you just wanted to use decorators.

That's fine I guess, but seems to limit the conversation pretty critically, so I'll bow out again.

@justinfagnani I was talking about

import {connect} from 'react-redux'

connect there is just a function that takes in a class (component) and spits out a different one, as you said above.

Currently, in both Babel, and TypeScript I have the option of using connect like this

class UnConnectedComponent extends Component {
}

let Component = connect(state => state.foo)(UnConnectedComponent);

OR like this

@connect(state => state.foo)
class Component extends Component {
}

I happen to prefer the latter, but if you or anyone else prefer the former, then so be it; many roads lead to Rome. I similarly prefer

@mappable
@vallidated
class Foo {
}

over

let Foo = validated(mappable(class Foo {
}));

or

class Foo extends mixin(validated(mappable)) {
}
//or however that would look.

I didn't explicitly answer your question only because I thought the reason I was using decorators was clear, but I'll state it explicitly: I prefer the ergonomics and syntax decorators provide. This whole, entire thread is about trying to get some ability to tell TS how our decorators are mutating the class in question so we don't need boilerplate like interface merging.

As OP said way back when, we just want

declare function Blah<T>(target: T): T & {foo: number}

@Blah
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

new Foo().foo; // Property 'foo' does not exist on type 'Foo'

to just work. The fact that other alternatives exist to this specific syntax is both obvious and immaterial. The fact that many in the JS/TS community dislike this particular syntax is also obvious and immaterial.

If TS could give us any way, however restricted, of making this syntax work, it would be a huge benefit to the language and community.

@arackaf You neglected to mention how using connect necessitates accessing additional members on this. AFAICT, your component implementation is supposed to be totally agnostic to what connect does, it only uses its own props and state. So in that sense the way connect is designed agrees more with @justinfagnani's usage of decorators than yours.

Not really. Consider

@connect(state => state.stuffThatSatisfiesPropsShape)
class Foo extends Component<PropsShape, any> {
}

then later

<Foo />

that should be valid - PropsShape props are coming from the Redux store, but TypeScript doesn't know this, since it's going off of the original definition of Foo, so I wind up getting errors for missing props, and need to cast it as any.

But to be clear, the precise use case originally given by OP, and duplicated in my second comment above here, is extremely common in our code base, and from the looks of it, many others' as well. We literally use things like

@validated
@mappable
class Foo {
}

and have to add some interface merging in order to satisfy TypeScript. It would be wonderful to have a way to bake that interface merging into the decorator definition transparently.

We keep going in circles with this. We agree that the type of Foo should be the return type of connect. We are in total agreement on this.

Where we differ is whether members inside Foo need to pretend that this is some kind of recursive amalgamation of the decorator return type and the "original Foo" whatever that means. You have not demonstrated a use case for this.

Where we differ is whether members inside Foo need to pretend that this is some kind of recursive amalgamation of the decorator return type and the "original Foo" whatever that means. You have not demonstrated a use case for this.

I have. See my comment above, and for that matter OP's original code. This is an extremely common use case.

Please show me what connect adds to the prototype that it expects you to access inside the component. If the answer is "nothing", then please do not bring up connect again, as it is totally irrelevant.

@masaeedu fair enough, and I agree. I was mostly responding to @justinfagnani's assertions about how decorators shouldn't be modifying the original class, connect was designed poorly, etc etc etc.

I was merely demonstrating the syntax I and many others happen to prefer, notwithstanding the fact that other options happen to exist.

For this case, yes, I don't happen to know of popular npm libraries that take a class in, and spit back out a new class with new methods, which would be used inside the decorated class. This is something I and others do frequently, in our own code, to implement our own mixins, but I don't really have an example on npm.

But it's not really in dispute that this is a common use case, is it?

@arackaf No, it doesn't because you're not accessing any members on this that are introduced by @connect. In fact I'm fairly sure this precise snippet:

@connect(state => state.stuffThatSatisfiesPropsShape)
class Foo extends Component<PropsShape, any> {
    render(){
        this.props.stuffFromPropsShape // <----- added by decorator
    }
}

will compile without any problems in TypeScript today. The snippet you've pasted has nothing to do with the feature you are requesting.

@masaeedu - so sorry - I realized I was wrong and deleted that comment. Not fast enough to prevent you from wasting your time though :(

@arackaf I don't know how common a pattern it is, and I personally have neither used it myself nor used any libraries that use it. I used Angular 2 for a while, so it's not like I've never used decorators in my life. This is why I was asking for specific examples of using a library where inability to access decorator-introduced members in the decoratee is a pain point.

In every case where a class's members expect something of this, there should be something in the body of the class or in the class's extends clause that satisfies the requirement. This is how it has always worked, even with decorators. If you have a decorator that adds some functionality a class depends on, the class should extend whatever the decorator returns, and not the other way around. It's just common sense.

I'm not sure why this is common sense. This is code from our code base currently. We have a decorator which adds a method to a class's prototype. TypeScript errors out. At the time I just threw this declaration on there. I could have used interface merging.

But it would be nice to use nothing at all. It would be really, really nice to be able to tell TypeScript that "this decorator here adds this method to whatever class it's applied to - allow this inside the class to access it, too"

Many others in this thread appear to be using decorators similarly, so I hope you'll consider that it might not be common sense that this shouldn't work.

image

@arackaf Yeah, so you should be doing export class SearchVm extends (@mappable({...}) class extends SearchVmBase {}). It is SearchVm that depends on mappability, not the other way around.

export class SearchVm extends (@mappable({...}) class extends SearchVmBase {})

The whole point of this thread is to AVOID boilerplate like that. Decorators provide a much, much nicer DX than having to do things like that. There's a reason we chose to write code like I have, rather than that.

If you read through the comments to this thread I hope you'll be convinced that many other people are trying to use the simpler decorator syntax, and therefore reconsider what we "should" be doing, or what's "common sense," etc.

What you want has nothing to do with decorators specifically. Your problem is that TypeScript's mechanism for saying "hey TypeScript, stuff is happening to this structure that you know nothing about, let me tell you all about it", is currently too limited. Right now we only have interface merging, but you want functions to be able to apply interface merging to their arguments, which is not specifically related to decorators in any way.

Right now we only have interface merging, but you want functions to be able to apply interface merging to their arguments, which is not specifically related to decorators in any way.

Ok. Fair enough. Do you want me to open a separate issue, or do you want to rename this one, etc?

@arackaf The only "boilerplate" is the fact that I had class { }, which is a) a fixed 7 characters, no matter how many decorators you use b) a result of the fact that decorators can't (yet) be applied to arbitrary class-returning expressions, and c) because I specifically wanted to use decorators for your benefit. This alternative formulation:

export class SearchVm extends mappable({...})(SearchVmBase)
{
}

is no more verbose than what you are doing originally.

Ok. Fair enough. Do you want me to open a separate issue, or do you want to rename this one, etc?

Separate issue would be good.

is no more verbose than what you are doing originally

It's no more verbose, but I really hope you'll consider the fact that many simply dislike that syntax. For better or worse, many prefer the DX decorators offer.

Separate issue would be good.

I'll type one up tomorrow, and link to this one for context.

Thanks for helping work through this :)

If I may way in, @masaeedu I disagree with this statement:

What you want has nothing to do with decorators specifically. Your problem is that TypeScript's mechanism for saying "hey TypeScript, stuff is happening to this structure that you know nothing about, let me tell you all about it", is currently too limited. Right now we only have interface merging, but you want functions to be able to apply interface merging to their arguments, which is not specifically related to decorators in any way.

Surely TypeScript could look at the return type of the class decorator to work out what type the mutated class should be. There is no need for "applying interface merging to arguments". So as I see it, it is entirely to do with decorators.

Also, the case of TypeScript not knowing that a connected component doesn't require props anymore is one aspect that turned me off using Redux with React and TypeScript.

@codeandcats I've been going in circles with this since the thread started, and I really can't do it anymore. Please look at the preceding discussion carefully, and actually try to understand what I was disagreeing with @arackaf over.

I agree that when you do @bar class Foo { }, the type of Foo should be the return type of bar. I do not agree that this inside Foo should be affected in any way. Using connect is totally irrelevant to the point of contention here, because connect does not expect the decorated component to be aware of and use members it is adding to the prototype.

I can understand you are frustrated @masaeedu but don't assume just because I haven't been actively voicing my opinion in this back and forth you've been having over the last 48hrs that I haven't been following the discussion - so please spare me the condescension mate.

As I understand it, you think inside a decorated class it shouldn't know about any mutation made on itself by decorators, but outside it should. I don't disagree with that. The fact that @arackaf thinks inside a class should see the mutated version is besides the point of my comment.

Either way, TypeScript could use the type returned by a decorator function as the source of truth for a class. And hence it is a Decorator issue, not some new bizarre argument mutation functionality as you suggest, which honestly just sounds like an attempt at a straw-man.

@Zalastax that's using the decorator function as a function though, not as a decorator. If you have multiple decorators you need to apply you need to nest them, which isn't technically anymore verbose but isn't as syntactically pleasant - do you know what I mean?

condescension etc.

Noise.

The fact that @arackaf thinks inside a class should see the mutated version is besides the point of my comment.

It is the central point of the paragraph you are responding to, so if you are talking about something else it is irrelevant. What @arackaf wants is, roughly, interface merging on function arguments. This is better addressed by a feature independent of decorators. What you want (which apparently is identical to what I want) is to have the bug in TypeScript fixed where @foo class Foo { } doesn't result in the same type signature for Foo as const Foo = foo(class { }). Only the latter is specifically related to decorators.

As I understand it, you think inside a decorated class it shouldn't know about any mutation made on itself by decorators, but outside it should. I don't disagree with that

If you've decided to talk about something we are in agreement over, but preface it with "I disagree with this statement", then you're just wasting time.

Can I get some fries with this salt?

Oh I see, when you patronise your peers its fine but if people pull you up on it, that's noise.

I don't see why your "argument mutation" concept is needed to implement what @arackaf proposed.

Regardless of whether we agree with @arackaf or not, either way TypeScript could simply infer the actual type from the decorator result, in my humble opinion.

Apologies if you felt condescended to; the intention was to get you to
actually a giant rambling thread that anyone could be forgiven for
skimming. I stand by this, since it's still pretty clear you're not aware
of the details and are "disagreeing" with me based on a misunderstanding of
my position.

For example, your question about why interface merging on function
arguments solves Adam's problem is best answered by looking at the relevant
discussion with Adam, where he says he's already using interface merging,
but finds it annoying to have to do this at each declaration site.

I think all the technical aspects of this have been beaten to death. I
don't want the thread to be dominated by interpersonal squabbles, so this
is going to be my last post.

It's true, there are two bugs with decorators: the return type is not respected, and internal to the class, added members are not respected (this inside the class). And yes, I'm more concerned with the latter, since the former is more easily worked around.

I've opened a separate case here: https://github.com/Microsoft/TypeScript/issues/16599

Hey everyone, I missed this conversation over the weekend, and perhaps there's not a ton of reason to bring it back up now; but I want to remind everyone that in the heat of these sorts of discussions, everyone weighing in typically is well-intentioned. Keeping a respectful tone can help clarify and steer the conversation, and can foster newer contributors. Doing this isn't always easy (especially when the internet leaves tonality up to the reader), but I think it's important for future scenarios. 😃

What's the status on this?

@alex94puchades it's still a stage 2 proposal, so we've probably got a while still. TC39 does appear to have some movement on it, at least.

According to this comment, looks like it could be proposed for stage 3 as early as November.

a workaround way to change the signature of a function by decorator

add a empty wapper function

export default function wapper (cb: any) {
    return cb;
}

add definition

export function wapper(cb: IterableIterator<0>): Promise<any>;

result

@some decorator // run generator and return promise
function *abc() {}

wapper(abc()).then() // valid

/ping

If anyone is looking for a solution to this, one workaround I came up with is below.

It's not the best solution because it requires a nested object, but for me it works nicely because I actually wanted the properties of the decorator to be in an object and not just flat on the class instance.

This is something I'm using for my Angular 5 modals.

Pretend I have a @ModalParams(...) decorator that I can use on custom ConfirmModalComponent. In order to have properties of the @ModalParams(...) decorator show up within my custom component, I need to extend a base class that has a property that the decorator will assign its values to.

For instance:

export class Modal {
    params: any;

    constructor(values: Object = {}) {
        Object.assign(this, values);
    }
}

export function ModalParams (params?: any) {
    return (target: any): void  => {
        Object.assign(target.prototype, {
            params: params
        });
    };
}

@Component({...})
@ModalOptions({...})
@ModalParams({
    width:             <number> 300,
    title:             <string> 'Confirm',
    message:           <string> 'Are you sure?',
    confirmButtonText: <string> 'Yes',
    cancelButtonText:  <string> 'No',
    onConfirm:         <(modal: ConfirmModalComponent) => void> (() => {}),
    onCancel:          <(modal: ConfirmModalComponent) => void> (() => {})
})
export class ConfirmModalComponent extends Modal {
    constructor() {
        super();
    }

    confirm() {
        this.params.onConfirm(this); // This does not show a syntax error 
    }

    cancel() {
        this.params.onCancel(this); // This does not show a syntax error 
    }
}

Again, it's not super pretty, but it works nicely for my use case so I thought someone else might find it useful.

@lansana but you don't get the types do you?

@confraria Unfortunately not, but there may be a way to achieve that if you implement a generic Modal class that you extend. For instance something like this might work (not tested):

export class Modal<T> {
    params: T;
}

export function ModalParams (params?: any) {
    return (target: any): void  => {
        Object.assign(target.prototype, {
            params: params
        });
    };
}

// The object in @ModalParams() should be of type MyType
@ModalParams({...})
export class ConfirmModalComponent extends Modal<MyType> {
    constructor() {
        super();
    }
}

:/ yup but then the type is decoupled from the decorator and you certainly can't use two of them..:( besides you would get errors if as the class would not implement the methods.. :( i don't think there is a way to get the types right with this pattern at the moment

Yeah it would be great if this was possible, makes TypeScript that much better and expressive. Hopefully something will come of it soon.

@lansana Yeah, the point is that it would be nice for class decorators to be capable of changing the signature of the class on their own, without requiring the class to extend or implement anything else (since that's a duplication of effort and type information).

Side note: in your example there, be mindful that params would be static across all instances of decorated modal component classes, since it's an object reference. Although maybe that's by design. : - ) But I digress...

Edit: As I think about this, I can see the cons of enabling decorators to modify class signatures. If the class implementation clearly features certain type annotations, but a decorator is capable of swooping in and changing all of that, that would be a bit of a mean trick to play on developers. A lot of the use cases evidently concern incorporating new class logic, so unfortunately many of them would be better facilitated through extension or interface implementation - which also coordinates with the existing class signature and raises appropriate errors where collisions occur. A framework like Angular of course makes copious use of decorators to augment classes, but the design isn't to allow the class to "gain" or mix in new logic from the decorator which it can then use in its own implementation - it's to isolate class logic from framework coordination logic. That's my _humble_ opinion, anyway. : - )

It seems better to just use higher order classes rather than decorators + hacks. I know people want to use decorators for this stuff but using compose + HOCs is the way to go and it'll probably remain this way... forever ;) According to MS etc. decorators are for attaching metadata to classes only and when you check big users of decorators such as Angular you'll see they are only used in this capacity. I doubt you'll be able to convince the TypeScript maintainers otherwise.

It is sad and a bit strange that such a powerful feature, that would allow true feature composition, and that generates such engagement has been ignored by the TS team for so long now.

This is really a feature that could revolutionize how we write code; allowing everyone to release small mixins on things like Bitly or NPM and have really awesome code reuse in Typescript. In my own projects I would instantly make my @Poolable @Initable @Translating and probably a pile more.

Please all mighty TS core team. "All you need" to implement is that the interfaces returned should be honored.

// taken from my own lib out of context
export function Initable<T extends { new(...args: any[]): {} }>(constructor: T): T & Constructor<IInitable<T>> {
    return class extends constructor implements IInitable<T> {
        public init(obj: Partial<T> | any, mapping?: any) {
            setProperties(this, obj, mapping);
            return this
        }
    }
}

which would allow this code to run without complaint:

@Initable
class Person {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();

sam.init({age: 17, name: "Sam", superPower: "badassery"});

@AllNamesRTaken

Though I agree with your points, you can achieve that same thing like so:

class Animal {
    constructor(values: Object = {}) {
        Object.assign(this, values);
    }
}

And then you wouldn't even need an init function, you could just do this:

const animal = new Animal({name: 'Fred', age: 1});

@lansana
Yeah but that would polute my constructor which is not always super duper what I want.

Also I have a similar mixin to make any object Poolable and other things. Just the posibility to add functionality through composition is what is needed. The init example is just a need way to do what you did without poluting the constructor, making it useful with other frameworks.

@AllNamesRTaken There is no point in touching decorators right now as it has been in this state for so long and proposal is at stage 2 already. Wait for https://github.com/tc39/proposal-decorators to finalize and then we will most likely get them in whatever form that everyone agrees on.

@Kukkimonsuta
I disagree strongly with you since what I am requesting is purely about type and not functionality. Hence it does not have much if anything to do with ES stuff. My solution above already works, I just don't want to have to cast to .

@AllNamesRTaken you can do this _today_ with mixins with no warnings at all:

function setProperties(t: any, o: any, mapping: any) {}

type Constructor<T> = { new(...args: any[]): T };

interface IInitable<T> {
  init(obj: Partial<T> | any, mapping?: any): this;
}

// taken from my own lib out of context
function Initable<T extends Constructor<{}>>(constructor: T): T & Constructor<IInitable<T>> {
    return class extends constructor implements IInitable<T> {
        public init(obj: Partial<T> | any, mapping?: any) {
            setProperties(this, obj, mapping);
            return this
        }
    }
}

class Person extends Initable(Object) {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();
sam.init({age: 17, name: "Sam", superPower: "badassery"});

In the future, if we get the mixins proposal into JS, we can do this:

mixin Initable {
  public init(obj: Partial<T> | any, mapping?: any) {
    setProperties(this, obj, mapping);
    return this
  }
}

class Person extends Object with Initable {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();
sam.init({age: 17, name: "Sam", superPower: "badassery"});

@justinfagnani mixin was how I started with these features and my implementation actually also works like you describe:

class Person extends Initable(Object) {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();     
sam.init({age: 17, name: "Sam", superPower: "badassery"});

which is nicish, as a workaround, but doesn't really remove the merits from allowing decorators to change the type in my opinion.

EDIT: also it looses the typing on the partial for init.

With this feature you will be able to write HoC more easily
I hope this feature will be added

@kgtkr that is the main reason why I'm wanting this so badly...

There is also a little emergency in react-router definitions because some people decided that type safety is more important than having class decoration interface. Bottom reason is that withRouter makes some of the props optional.

Now there seems to be feud, where people are forced to use function interface of withRouter instead of decoration.

Starting to solve this feature would make world happier place.

The same feud* occurred a while back with the material-ui typings and withStyle, which was designed to be used as a decorator, but which, in order to use in a type safe way, TypeScript users need to use as a regular function. Even though the dust has mostly settled on that, it's a source of ongoing confusion for newcomers to the project!

* Well, "feud" may be a strong word

I've been watching this for a long time, and until it arrives others can hopefully benefit from my little hack to achieve this behaviour in a forward compatible way. So here it is, implementing a super basic Scala style case class copy method...

I have the decorator implemented like so:

type Constructor<T> = { new(...args: any[]): T };

interface CaseClass {
  copy(overrides?: Partial<this>): this
}

function CaseClass<T extends Constructor<{}>>(constructor: T): T & Constructor<CaseClass> {
  return class extends constructor implements CaseClass {
    public copy(overrides: Partial<this> = {}): this {
      return Object.assign(Object.create(Object.getPrototypeOf(this)), this, overrides);
    }
  }
}

This code creates an anonymous class with the copy method attached. It works exactly as expected in JavaScript when in an environment supporting decorators.

To use this in TypeScript, and actually have the type system reflect the new method in the targeted class, the following hack can be used:

class MyCaseClass extends CaseClass(class {
  constructor(
    public fooKey: string,
    public barKey: string,
    public bazKey: string
  ) {}
}) {}

All instances of MyCaseClass will expose the typings for the inherited copy method from the anonymous class inside the CaseClass decorator. And, when TypeScript supports the mutation of declared types, this code can be modified easily to the usual decorator syntax @CaseClass etc without any unexpected blips.

That would be great to see this in the next major TypeScript release - I believe that it will help cleaner and more concise code instead of exporting weird mess of proxy classes.

When will this feature become available?

I wanted to use a class decorator to take care of repeated task.
But now when initializing the class, I get the following error: Expected 0 arguments, but got 1

function Component<T extends { new(...args: any[]): {} }>(target: T) {
    return class extends target {
        public constructor(...args: any[]) {
            super(...args);
            resolveDependencies(this, args[0])
        }
    }
}

@Component
export class ExampleService {
    @Inject(ExampleDao) private exampleDao: ExampleDao;

    // @Component will automatically do this for me 
    // public constructor(deps: any) {
    //  resolveDependencies(this, deps);
    // }

    public getExample(id: number): Promise<Example | undefined> {
        return this.exampleDao.getOne(id);
    }
}

new ExampleService({ exampleDao }) // TS2554: Expected 0 arguments, but got 1.

I hope this feature will be available soon! :)

@iainreid820 did you experience any weird bugs using this approach?

The long wait! In the meantime, is this getting solved by anything on the current road map?
Such as issue 5453?

I'm using Material UI and just had to realize that TypeScript doesn't support the decorator syntax for withStyles: https://material-ui.com/guides/typescript/#decorating-components

Please fix that limitation in the next TypeScript release. Class decorators seem pretty useless to me right now.

As a maintainer of Morphism Js, this is a big limitation for this kind of library. I would love to avoid the consumer of a Function Decorator to have to specify the target Type of the Function https://github.com/nobrainr/morphism#--toclassobject-decorator, otherwise using decorators instead of HOF sounds like a bit useless 😕
Is there any plan to tackle this ? Is there a way to help making this feature happen ? Thank you in advance!

@bikeshedder that Material UI example is a class mixin use case, and you can get the right type from mixins. Instead of:

const DecoratedClass = withStyles(styles)(
  class extends React.Component<Props> {
...
}

write:

class DecoratedClass extends withStyles(styles)(React.Component<Props>) {
...
}

@justinfagnani That doesn't work for me:

ss 2018-10-16 at 10 00 47

Here's my code: https://gist.github.com/G-Rath/654dff328dbc3ae90d16caa27a4d7262

@G-Rath I think new () => React.Component<Props, State> instead should work ?

@emyann No dice. I've updated the gist with the new code, but is this what you meant?

class CardSection extends withStyles(styles)(new () => React.Component<Props, State>) {

No matter how you format it, extends withStyles(styles)(...) does not sound like a proper suggestion, fundamentally. withStyles is NOT a class mixin.

withStyles takes component class A and creates class B which when rendered renders class A and passes props to it + the classes prop.

If you extend the return value of withStyles then you are extending the B wrapper, instead of implementing the A class that actually receives the classes prop.

No matter how you format it, extends withStyles(styles)(...) does not sound like a proper suggestion, fundamentally. withStyles is NOT a class mixin.

Indeed. Not sure why there's so much pushback here.

That said, decorators are extremely close to stage 3, from what I hear, so I imagine TS will be getting proper support here soon.

I hear the folks who want a cleaner syntax though, esp. when using applying multiple HoCs as decorators. The temporary workaround we use is to actually wrap multiple decorators inside a pipe function and then use that pure function to decorate the component. e.g.

@flow([
  withStyles(styles),
  connect(mapStateToProps),
  decorateEverything(),
])
export class HelloWorld extends Component<Props, State> {
  ...
}

where flow is lodash.flow. A lot of util libraries provide something like this - recompose, Rx.pipe etc. but you can of course write your own simple pipe function if you don't want to install a library.

I find this easier to read than not using decorators,

export const decoratedHelloWorld = withStyles(styles)(
  connect(mapStateToProps)(
    decorateEverything(
       HelloWorld
))))

Another reason I use this, is that this pattern is easily find-and-replace-able/grep-able once the decorator spec is announced and properly supported.

@justinfagnani Oddly I get a syntax error from ESLint when I try changing extends React.Component<Props, State> to extends withStyles(styles)(React.Component<Props, State>), so I couldn't verify @G-Rath's type error in the same way.

But I did notice that if I do something like follows then a problem with new (maybe the same problem?) appears:

class MyComponent extends React.Component<Props, State> {
  /* ... */
}

const _MyComponent = withStyles(styles)(MyComponent)
const test = new _MyComponent // <--------- ERROR

and the error is:

Cannot use 'new' with an expression whose type lacks a call or construct signature.

Does this mean the type returned by Material UI is not a constructor (though it should be)?

@sagar-sm But, do you get the correct type augmented by the Material UI type? Doesn't seem like you will.

For reference, I stumbled on this because I was also trying to use Material UI's withStyles as a decorator, which didn't work so I asked this question: https://stackoverflow.com/questions/53138167

need this, then we can make async function return as bluebird

I tried to do something similiar. ATM I'm using following workaround:

First here's my "mixin" decorator

export type Ctor<T = {}> = new(...args: any[]) => T 
function mixinDecoratorFactory<MixinInterface>() {
    return function(toBeMixed: MixinInterface) {
        return function<MixinBase extends Ctor>(MixinBase: MixinBase) {
            Object.assign(MixinBase.prototype, toBeMixed)
            return class extends MixinBase {} as MixinBase & Ctor<MixinInterface>
        }
    }
}

Create a decorator from interface

export interface ComponentInterface = {
    selector: string,
    html: string
}
export const Component = mixinDecoratorFactory<ComponentInterface>();

And that's how I use it:

@Component({
    html: "<div> Some Text </div>",
    selector: "app-test"
})
export class Test extends HTMLElement {
    test = "test test"
    constructor() {
        super()
        console.log("inner;     test:", this.test)
        console.log("inner;     html:", this.html)
        console.log("inner; selector:", this.selector)
    }
}

export interface Test extends HTMLElement, ComponentInterface {}
window.customElements.define(Test.prototype.selector, Test)


const test = new Test();
console.log("outer;     test:", test.test)
console.log("outer;     html:", test.html)
console.log("outer; selector:", test.selector)

The Trick is to also create a an interface with the same name as the class to create a merged declaration.
Still the class only shows as type Test but checkings from typescript are working.
If you would use the decorator without the @-Notation an simply invoke it as a Function you would get the right intersection-Type but lose the ability of type-checking inside the class itself since you can't use the interface trick anymore and it looks more ugly. E.g.:

let Test2Comp = Component({
    html: "<div> Some Text 2 </div>",
    selector: "app-test2"
}) (
class Test2 extends HTMLElement {
    test = "test test"
    constructor() {
        super()
        console.log("inner;     test:", this.test)
        console.log("inner;     html:", this.html)     // no
        console.log("inner; selector:", this.selector) // no
    }
})

interface Test2 extends HTMLElement, ComponentInterface {} //no
window.customElements.define(Test2Comp.prototype.selector, Test2Comp)

const test2 = new Test2Comp();
console.log("outer;     test:", test2.test)
console.log("outer;     html:", test2.html)
console.log("outer; selector:", test2.selector)

What do you say about these approaches? It's not beautiful but works as far as a workaround.

Is there any progress on this issue? This seems like a very, very powerful feature that would unlock a whole lot of different possibilities. I assume this issue is mostly stale right now because decorators are still experimental?

Would be really great to have an official statement on this @andy-ms @ahejlsberg @sandersn. 🙏

We likely won't be doing anything here until decorators are finalized.

@DanielRosenwasser - what does "finalized" mean, here? Does Stage 3 in TC39 qualify?

I didn't realize they'd got that far! When did they reach stage 3? Is that recent?

They haven't yet; I believe they're up for advancement from Stage 2 to Stage 3 again at the January TC39 meeting.

You can keep an eye on the agenda for details.

Correct (though I can't confirm it's up for advancement in January). I was only asking if Stage 3 would qualify when they get there.

In my opinion, decorator proposal still have many serious issues, so if it advance to stage 3 in January, it will make the mistake again just like problematical class fields proposal and globalThis proposal.

@hax could you elaborate?
I would really really want this and find the lack of communication on the matter saddening and haven't heard about the problems unfortunately.

@AllNamesRTaken Check the issue list of decorator proposal. 😆 For example, export before/after decorator argument is a blocker of stage 3.

There is also some proposed changes like redesign API which I believe mean the proposal is not stable for stage 3.

And it also related to the problematical class fields proposal. Though class fields has reach stage 3, there are too many issues. A big problem I concern is it leave many issues to decorators (for example, protected ) and that's bad for both proposals.

Note, I'm not TC39 delegates, and some TC39 delegates never agree my comments about current status of many issues. (Especially I have strong opinion that the current TC39 process have big failures on many controversial issues. Postpone some issues to decorators never really solve the problem, just make decorator proposal more fragile.)

I think those things will get figured out and the proposal will be fine, but I'd prefer we didn't discuss the state of the proposal here.

I think a stage 3 with sufficient confidence or stage 4 is probably where we'd implement the new proposal, and then we could look at this issue.

Thanks Daniel! Stage 3 usually implies a strong confidence in 4, so fingers crossed for the January meeting.

And thanks for heading off decorators discussion here. It’s bizarre the level of anger and bike shedding this feature has caused. I’ve never seen anything like it 😂

just for the record there was a feature request about the same thing: https://github.com/Microsoft/TypeScript/issues/8545

annoyingly enough TypeScript supports this feature to an extent when you compile from JavaScript (https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#better-handling-for-namespace-patterns-in-js-files):

// javascript via typescript
var obj = {};
obj.value = 1;
console.log(obj.value);

and even for TypeScript code itself but only when it comes to functions (!)(https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#properties-declarations-on-functions):

function readImage(path: string, callback: (err: any, image: Image) => void) {
    // ...
}

readImage.sync = (path: string) => {
    const contents = fs.readFileSync(path);
    return decodeImageSync(contents);
}

@DanielRosenwasser @arackaf Any word on the proposal moving to Stage 3? I am looking to build a library that adds prototype functions to a class when using a decorator.

They did not advance at the latest TC39 meeting; they may be up for advancement again at the next meeting. However, it's unclear; there's a lot up in the air about the proposal at this point.

Folks here who are interested would be well advised to track the proposal itself – which will keep noise down for those of us watching the thread as well as for the TS maintainers.

any updates on this one?

workaround (angular:)) https://stackblitz.com/edit/iw-ts-extends-with-fakes?file=src%2Fapp%2Fextends-with-fakes.ts

import { Type } from '@angular/core';

export function ExtendsWithFakes<F1>(): Type<F1>;
export function ExtendsWithFakes<F1, F2>(): Type<F1 & F2>;
export function ExtendsWithFakes<F1, F2, F3>(): Type<F1 & F2 & F3>;
export function ExtendsWithFakes<F1, F2, F3, F4>(): Type<F1 & F2 & F3 & F4>;
export function ExtendsWithFakes<F1, F2, F3, F4, F5>(): Type<F1 & F2 & F3 & F4 & F5>;
export function ExtendsWithFakes<RealT, F1>(realTypeForExtend?: Type<RealT>): Type<RealT & F1>;
export function ExtendsWithFakes<RealT, F1, F2>(realTypeForExtend?: Type<RealT>): Type<RealT & F1 & F2>;
export function ExtendsWithFakes<RealT, F1, F2, F3>(realTypeForExtend?: Type<RealT>): Type<RealT & F1 & F2 & F3>;
export function ExtendsWithFakes<RealT, F1, F2, F3, F4>(
    realTypeForExtend?: Type<RealT>
): Type<RealT & F1 & F2 & F3 & F4>;
export function ExtendsWithFakes<RealT, F1, F2, F3, F4, F5>(
    realTypeForExtend?: Type<RealT>
): Type<RealT & F1 & F2 & F3 & F4 & F5> {
    if (realTypeForExtend) {
        return realTypeForExtend as Type<any>;
    } else {
        return class {} as Type<any>;
    }
}

interface IFake {
    fake(): string;
}

function UseFake() {
    return (target: Type<any>) => {
        target.prototype.fake = () => 'hello fake';
    };
}

class A {
    a() {}
}

class B {
    b() {}
}

@UseFake()
class C extends ExtendsWithFakes<A, IFake, B>(A) {
    c() {
        this.fake();
        this.a();
        this.b(); // failed at runtime
    }
}

What do you think about this "meanwhile" easy solution?
https://stackoverflow.com/a/55520697/1053872

Mobx-state-tree uses a similar approach with their cast() function. It doesn't feel nice but... It also doesn't bother me too much. It's easy to take out whenever something better comes along.

I would just like to be able to do something along the lines of this ❤️
Isn't this the power that decorators are supposed to have?

class B<C = any> {}

function ChangeType<T>(to : T) : (from : any) => T;
function InsertType<T>(from : T) : B<T>;

@ChangeType(B)
class A {}

// A === B

// or

@InsertType
class G {}

// G === B<G>

const g = new G(); // g : B<G>

A === B // equals true

const a : B = new A();  // valid
const b = new B();

typeof a === typeof b // valid

I spent quite some time creating a workaround to type decorated class properties. I don't think it will be a viable solution to the scenarios in this thread, but you may find some parts of it useful. If you're interested, you can check my post on Medium

Any updates on this issue?

How we doing with this?

What a result of this issue, how it works now?

You can use mixin-classes to get that effect
https://mariusschulz.com/blog/mixin-classes-in-typescript

@Bnaya which is totally not what what we want now is it ;).
Decorators let us avoid creating classes that we never intend to use like we were doing old school Java. Decorators would allow a very clean and tidy composition architecture where the class can continue to do core functionality and have extra generalized functionality composed on to it. Yeah mixins could do it but it is bandaid.

The decorator proposal changed significantly in past months - it's highly improbable that TS team will invest any time into current implementation that will be incompatible soon™.

I'd recommend watching following resources:

Decorators proposal: https://github.com/tc39/proposal-decorators
TypeScript roadmap: https://github.com/microsoft/TypeScript/wiki/Roadmap

@Bnaya which is totally not what what we want now is it ;).
Decorators let us avoid creating classes that we never intend to use like we were doing old school Java. Decorators would allow a very clean and tidy composition architecture where the class can continue to do core functionality and have extra generalized functionality composed on to it. Yeah mixins could do it but it is bandaid.

Its not mixing, but a class.
Its not the bad-copy-stuff mixin that
When pipeline operator will arrive, the syntax will also be reasonable

You can use mixin-classes to get that effect

Class-factory mixins can be a pain in TypeScript; even if they weren't, they are very boilerplatey by requiring you to wrap classes in a function to convert to a mixin, which sometimes you can't simply do if you are importing 3rd-party classes.

The following is much better, simpler, cleaner, and works with imported 3rd party classes. I have it working fine in plain JavaScript with no transpilation other than for transpiling the legacy-style decorator, but the classes stay totally native classes:

class One {
    one = 1
    foo() { console.log('foo', this.one) }
}

class Two {
    two = 2
    bar() { console.log('bar', this.two) }
}

class Three extends Two {
    three = 3
    baz() { console.log('baz', this.three, this.two) }
}

@with(Three, One)
class FooBar {
    yeah() { console.log('yeah', this.one, this.two, this.three) }
}

let f = new FooBar()

console.log(f.one, f.two, f.three)
console.log(' ---- call methods:')

f.foo()
f.bar()
f.baz()
f.yeah()

And the output in Chrome is:

1 2 3
 ---- call methods:
foo 1
bar 2
baz 3 2
yeah 1 2 3

This completely gives you what class-factory mixins do, without all the boilerplate. The function wrappers around your classes are no longer required.

However, as you know, the decorator can't change the type of the defined class. 😢

So, for the time being, I can do the following, with the only caveat being that protected members are not inheritable:

// `multiple` is similar to `@with`, same implementation, but not a decorator:
class FooBar extends multiple(Three, One) {
    yeah() { console.log('yeah', this.one, this.two, this.three) }
}

The issue with losing protected members is illustrated with these two examples of the multiple implementations (as far as types go, but the runtime implementation is omitted for brevity):

  • example with no error, because all properties in the combined classes are public.
  • example with error (at the very bottom), because the mapped types ignore the protected members, in this case making Two.prototype.two not inheritable.

I'd really like for a way to map types including protected members.

This would be really useful for me because I have a decorator that allows a class to be called as a function.

This works:

export const MyClass = withFnConstructor(class MyClass {});

This doesn't work:

@withFnConstructor
export class MyClass {}

A not-so-hard-to-clean-when-this-feature-comes-out workaround can be done using declaration merging and a custom types definition file.

Having already this:

// ClassModifier.ts
export interface Mod {
  // ...
}
// The decorator
export function ClassModifier<Args extends any[], T>(target: new(...args: Args) => T): new(...args: Args) => T & Mod {
  // ...
}
// MyClass.ts
@ClassModifier
export MyClass {
  // ...
}

Add the following file (file to be removed once the feature comes out):

// MyClass.d.ts
import { MyClass } from './MyClass';
import { Mod } from './ClassModifier';

declare module './MyClass' {
  export interface MyClass extends Mod {}
}

Another possible workarround

declare class Extras { x: number };
this.Extras = Object;

class X extends Extras {
   constructor() {  
      super(); 
      // a modification to object properties that ts will not pick up
      Object.defineProperty(this, 'x', {value: 3});
   }
}

const a = new X()
a.x // 3

Started in 2015 and still pending. There are many more related issues and even articles like this https://medium.com/p/caf24aabcb59/responses/show, trying to show workarounds (which are bit hacky, but helpful)

Is it possible for someone to shed some light on this. Is it even considered for internal discussion yet?

tl;dr - this feature is jammed up in TC39. Can't blame the TS people for this at all. They're not gonna implement until it's standard.

But this is Microsoft, they can just implement it and then say to the standards people - "see, people are already using our version" :trollface:

The issue here is that the decorators proposal has changed _significantly_ (in backwards incompatible ways) since TypeScript implemented decorators. This is going to lead to a painful situation when the new decorators get finalised and implemented, as some people are relying on TypeScript's current decorator behavior and semantics. I strongly suspect that the TypeScript team want to avoid actioning this issue because doing so would encourage further usage of the current non-standard decorators, which would ultimately make the transition to any new decorator implementation even more painful. Essentially, I personally just don't see this happening.

Incidentally, I recently created #36348, which is a proposal for a feature that would provide a pretty solid workaround. Feel free to take a look at/give feedback on/savage it. 🙂

tl;dr - this feature is jammed up in TC39. Can't blame the TS people for this at all. They're not gonna implement until it's standard.

Given this fact, it might be wise for the development team to write up a quick explanation and status update, and lock this converstation until TC39 move this forward to the required stage?

Does anybody reading this have any weight in the matter?

Actually, pretty much every decorators related conversation is hanging up in the air. Example: https://github.com/Microsoft/TypeScript/issues/2607

Getting some clarity on the future of decorators is going to be helpful, even if its about asking contributors to implement required functionality. Without clear guidance, the future of decorators is blurry

I would love if the TypeScript team could take on a proactive role for the decorator proposal, similar to how they helped to get optional chaining through. I would also love to help, but haven't worked in language development yet and therefore am unsure how to best do so :-/

I can't speak for the entire team, and TypeScript as a project - but I think it's unlikely we'll be doing any new decorator work until it's settled and confirmed, given how the decorator implementation has already diverged once from the TypeScript implementation.

The proposal is still in active development, and came up in TC39 this week https://github.com/tc39/proposal-decorators/issues/305

@orta in this case I think the docs about class decorator should be updated then. It states that

If the class decorator returns a value, it will replace the class declaration with the provided constructor function.

Also, the following example from the docs seems to imply that the Greeter instance type would have the property newProperty, which is not true:

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

I think it's worth to add to the docs that the interface of the returned class will not replace the original one.

Interesting, I tested a bunch of old versions of TypeScript and couldn't find an occasion where that code sample worked ( example from 3.3.3 ) - so yeah, I agree.

I've made https://github.com/microsoft/TypeScript-Website/issues/443 - if someone knows how to make that classDecorator have an explicit intersection type, please comment in that issue

I wound up here because the issue with the documentation listed above did cause me confusion. While it would be great if the type signature of the returned value from the decorator was recognized by the TS Compiler, in lieu of that larger change I agree that the documentation should be clarified.

For the record, this is a simplified version of what I was attempting in a format the related docs from @orta 's PR:

interface HasNewProperty {
  newProperty: string;
}

function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor implements HasNewProperty {
    newProperty = "new property";
    hello = "override";
  };
}

@classDecorator
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}

console.log(new Greeter("world"));

// Alas, this line makes the compiler angry because it doesn't know
// that Greeter now implements HasNewProperty
console.log(new Greeter("world").newProperty);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  ·  3Comments

blendsdk picture blendsdk  ·  3Comments

dlaberge picture dlaberge  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments