Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit `this` type for class methods

Tags:

typescript

EDIT: It appears this is a known issue in Typescript. A solution was once implemented but ultimately pulled because of unresolvable performance problems.

This situation comes up often in our codebase:

function consumer<T>(valueProducer: () => T) {
    let value = valueProducer();
    console.log(value);
}

class Foo {
    private _value: number = 100;

    getValue(): number {
        return this._value;
    }

    constructor() {
        // Oops! Inside consumer(), getValue will be called with wrong this
        consumer(this.getValue);
    }
}

The solution in Typescript is either this:

consumer( () => this.getValue() ); // capture correct this

Or this:

consumer( this.getValue.bind(this) ); // bind to correct this

This problem might be obvious to a Typescript/Javascript programmer, but our team is porting a large body of C# to Typescript, and in C# this is not an error (i.e. a passed method in C# is automatically bound to the object instance). So I'd like the type system to catch this error, if possible.

The first, obvious step to explicitly type the this used on the callback:

function consumer<T>(valueProducer: (this: void) => T) {
    let value = valueProducer();
    console.log(value);
}

I would expect that to be enough, but it turns out I also need to explicitly type the this parameter on the Foo method:

class Foo {
    getValue(this: Foo): number { // I could also have written getValue(this: this)
        return this._value;
    }

With these two things in place, I now get exactly the error I want:

error TS2345: Argument of type '(this: Foo) => number' is not assignable to parameter of type '(this: void) => number'.
  The 'this' types of each signature are incompatible.
    Type 'void' is not assignable to type 'Foo'.

However, I don't want to have to add this: this to every method in my app. Shouldn't the value of this in a method implicitly be that of the enclosing type? Is there another way to achieve this same result without adding all that boilerplate noise to my classes?

(plunker for discussed code)

like image 814
Mud Avatar asked Mar 22 '17 16:03

Mud


People also ask

What are implicit classes?

An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.

What is implicit class in Java?

The implicit parameter in Java is the object that the method belongs to. It's passed by specifying the reference or variable of the object before the name of the method. An implicit parameter is opposite to an explicit parameter, which is passed when specifying the parameter in the parenthesis of a method call.

What is implicit object in Scala?

In Scala, objects and values are treated mostly the same. An implicit object can be thought of as a value which is found in the process of looking up an implicit of its type.

What are type classes in Scala?

A type class is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. If you are coming from Java, you can think of type classes as something like java.


1 Answers

You could use EsLint with a plugin for TypeScript. It has a rule that disallows unbound methods:

  211:19  error    Avoid referencing unbound methods which may cause unintentional scoping of `this`  @typescript-eslint/unbound-method

Using EsLint is generally a good idea, so this shouldn't be a problem. Note that this rule requires type checking, so you need to extend from all the rulesets in the plugin:

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ]
}

You could, of course, enable this rule only, but sticking with recommended configs is a better idea.

like image 50
holdn Avatar answered Sep 17 '22 13:09

holdn