Skip to content

Proposal to improve static analysis for the thisArg of a function #1985

Description

@nathggns

This is an official proposal for one of the issues outlined in #229.

Firstly, I'd like to give a big thank you to Anders Hejlsberg (@ahejlsberg) for identifying the issue, and to Sergii Kliuchnyk (@redexp), Ivo Gabe de Wolff (@ivogabe), Andrew Bradley (@cspotcode), and others for their contributions to the original issue as they have formed most of the grunt work of this proposal. I've just decided I want the feature enough that I will take the time to write an official proposal.

I'd also like to note this is my first official proposal (for not just TypeScript, any language). I'm sorry if I've missed anything glaring out. Any advice on how to improve my proposals would be appreciated.

The Problem

When working with many libraries that take callbacks (including jQuery), TypeScript will give very little intelligence as to the type of the thisArg inside a callback's body �– intact, it will simply be typed as any.

A few examples follow

// A standard jQuery example. While jQuery does
// also provide the value of `thisArg` as a argument
// to the callback, it is still helpful to provide a familiar example 
$('body').on('click', function() {
    this // any?
});

// The example from the original issue
// There would be no intellisense in this function, and TypeScript will
// just assume everything is okay even if we misspell get or Sammy's API changes
// because this is typed to any. 
$.sammy('#view', function() {
    this.get('#/', function() { ... });
});

// Here is a typescript example
// myClickHandler obviously relies on its thisArg being a HTMLElement
// but nothing stops code from calling it with any thisArg either
// directly (myClickHandler(...)) or with call/bind (myClickHandler.call(...))
function myClickHandler(event : MouseEvent) {
    this.innerHTML = [event.pageX, event.pageY].join(', ')
}

document.body.addEventListener('click', myClickHandler);

Because the thisArg is of type any, we get no type checking and no autocomplete or any other kind of intellisense. Also, when defining functions within TypeScript user land code that rely on the thisArg being a certain type, there is nothing stopping consuming code calling it with whatever type they provide.

Proposed Change

Ideally, a function definition would be able to define the type of its thisArg. This means that any consuming code must provide a thisArg of this type (using call, apply, or bind). It also means that within the body of that function, we can assume that the type of its thisArg is of the provided type, rather than reverting to an any type.

Quick Note: None of this applies to functions defined using arrow functions. You cannot specify the type of the thisArg with arrow functions as their type is lexically bound. I believe if you try to use the syntax specified below with arrow functions it should be either a compiling error or a parsing error.

There are many different places where you can define a function in TypeScript. However, the main syntax is as follows at the moment.

function someFunction(someArg : someType) : someResult { ... }

I propose adding to this syntax a way of specifying the type of the thisArg within the function body.
There are a few suggestions for this syntax including some from the original issue and some I have thought of myself, but I think the best out of the suggested is the following syntax:

// Function Statement
function someFunction<this : SomeThisType>(someArg : SomeArgType) : SomeReturnType {
    // this is of type SomeThisType
}

// Function expression
var someFunction = function<this : SomeThisType>(someArg : SomeArgType) : SomeReturnType {
    // this is of type SomeThisType
}

// Function argument
// This would work similairly for declared functions (declare function ...) in definition files
function someFunction(someCallback : <this : SomeThisType>(someArg : SomeArgType) => SomeReturnType) : SomeReturnType { .. }

someFunction(function() {
    // this is of  type SomeThisType
});

// Interface function argument

interface SomeFunctionArgument {
    <this : SomeThisType>(someArg : SomeArgType) : SomeReturnType;
}

function someFunction(someCallback : SomeFunctionArgument) : SomeReturnType { ... }

someFunction(function() {
    // this is of  type SomeThisType
});

In the statement and expression examples, we can assume that the type of thisArg inside their bodies is SomeThisType. In the last two examples, we can assume that type type of thisArg inside the passed callbacks is of type SomeThisType.

However, these assumptions rely on enforcing that any TypeScript code calling these functions have to pass the correct thisArgs. Calling functions in JavaScript can get very complicated but here are the different ways that you can pass a thisArg, along with the semantics of this proposal.

Taken the following code:

class SomeType {
   doStuff() { ... }
}

var method = function<this : SomeType>() {
    this.doStuff();
};

We would no longer be able to call this function simply by invoking it, as the thisArg would be set to the global object instead of an instance of SomeType. Trying to do so should throw a TypeError of some description (the exact error is intentionally left undecided at this point).

method(); // Error: Window cannot be converted to SomeType ...

Specifying the type of thisArg using call, apply, or bind with an incompatible type will throw the same error. With call and apply the error is triggered at call time. With bind, it is triggered at "bind time" (to more easily find the source of an error).

method.call({});
method.apply({});
method.bind({})();

Only invoking with a compatible type of SomeType will be accepted by the type engine.

method.call(new SomeType());
method.apply(new SomeType());
method.bind(new SomeType())();

Again, I would like to stress that this proposal has no affect on arrow functions. Even attempting to specify the thisArg for arrow functions should be an error.

var someFunction = <this : SomeType>() => { ... }; // Error

One notably exception to this is that you are able to pass arrow functions as arguments where a thisArg has been specified.

Example:

declare function someFunction(someCallback : <this : SomeType>() => number);

someFunction(() => 1);

Note: bind does not change the specified thisArg of a function. This can only be set at define-time.

The Syntax

You will have noticed that we pass the thisArg within the type arguments of a function, where you would normally pass generic type information. This does not conflict however, as the thisArg must be the last type argument, separated by a , if any other arguments are passed, and must be prefixed with this: as follows.

class SomeType<T> {
    constructor(public someProp : T) {}
}

function someFunction<T, this : SomeType<T>>() : T {
    return this.someProp;
}

someFunction(); // Error
someFunction.call({}); // Error
someFunction.call(new SomeType<number>(1)); // 1

Note: This is a very contrived example, as very rarely will a function ever take a generic argument as well as specifying the thisArg as specifying the thisArg is usually done when declaring function arguments and then passing anonymous functions like so:

declare someFunction(someCallback : <this : SomeType>() => number) : number;

someFunction(function() {
    // this is of type SomeType
});

Why this syntax?

I chose this syntax as the main use case for this feature is for anonymous functions, in which you almost if not never have generic type arguments for. Alternatives were to add the this into the parameter list, which I disliked as it made for a messy-looking solution in the common use case.

declare function someFunction<this : SomeType>(someArg : SomeArgType): any;

// vs

declare function someFunction(this : SomeType | someArg : SomeArgType): any;

Personally, I find the latter much harder to read.

How is this problem solved?

With this change, you can now specify the thisArg within function bodies. While this works for any functions, the main use case is for passing anonymous functions to library code. With this change, you can now specify in a declaration the thisArg in the body of a function argument.

This is a very basic example of how the on function could be written in a jQuery definition file. I've put the definition and usage in one file for convenience.

Note how in this example, the whole semantics of enforcing the type of thisArg when calling a function is irrelevant as we're simply telling TypeScript how existing JavaScript works. This is actually the common use case for this code.

// Definition
interface jQueryStatic {
    (...any) : jQueryStatic; // Simple call definition. 
    on(eventName : string, callback : <this : HTMLElement>() => any) : jQueryStatic;
}

var $ : jQueryStatic;

// Usage

$('body').on('click', function() {
    // this is now of type HTMLElement
});

Void thisArg

In some cases, the value of thisArg can be undefined (directly invoking a function in strict mode). It can be helpful to disallow consuming code from being able to use the value of this even if it isn't undefined, by making TypeScript assume it is undefined.

You can do this like so:

declare function someFunction(arg : <this : void>() => void);

someFunction(function() {
    // this === undefined
});

Classes

Currently, explicitly setting the type of thisArg in class methods is unsupported (triggering a syntax error) as doing this comes with its own set of problems, and can be added on at a later date once the basic semantics have been sorted.

This also means you cannot dynamically change a method on a object to a function that has a specified thisArg. Also, to maintain currently functionality, TypeScript will continue to assume that this is of the type in which the method is defined within the method body, but will allow you to trigger it with any thisArg.

class Foo {
    someMethod() {
        // this is assumed to be of type Foo
    }
}

var foo = Foo();
var method = foo.someMethod;

// All okay
foo.someMethod();
someMethod.call(foo);
someMethod();
someMethod.call({});
someMethod.apply({});
someMethod.bind({})();

// Not okay

foo.someMethod = function<this : number>() { .. };

Automatically setting the thisArg within class methods

In the original issue, Andrew Bradley (@cspotcode) suggested that the thisArg automatically be set for class methods as such that the following is an error.

class SomeType {
    someMethod() { ... }    
}

(new SomeType()).someMethod.call({});

I have intentionally left this out of this proposal as it causes a lot of its own backwards incompatibility issues, hence why there is no syntax addressed for setting the thisArg for class methods. However, should this proposal be accepted, then it might be worth extending it to address this. Should you choose to do this, this comment should be of some help to you.

Interfaces

While specifying a thisArg type for function interfaces is allowed, trying to do the same for methods or properties is not. This is something that can be revisited if/when class method support for thisArg type specifications is added.

interface SomeFunctionInterface {
    <this : number>(): number;
}

declare function numberMap(arr : number[], transformer : SomeFunctionInterface) : number[]; // Okay

interface SomeOtherInterface {
    numberMapper : SomeFunctionInterface; // Error
}

Assignability

If B is assignable to A, function<this:B> is assignable to function<this:A>, but not vice versa.

function<this:any> is assignable to function<this:A> andfunctionthis:B.functionthis:Aandfunctionthis:Bis assignable tofunctionthis:any`.

class A {}
class B extends A {}
var x = function<this : number>(){};
var y = function<this : string>(){};
var z = function<this : A>(){};
var zz = function<this : B>(){};
var a = function<this : any>(){};

x = y; // Error
y = x; // Error
zz = z; // Error

z = zz; // Okay
x = a; // Okay
y = a; // Okay
z = a; // Okay
zz = a; // Okay

a = x; // Okay
a = y; // Okay
a = z; // Okay
a = zz; // Okay

Updating lib.d.ts

Andrew Bradley (@cspotcode) also showed that updating lib.d.ts to properly set the thisArg could cause a lot of breakage. Again, to simplify this proposal, I have left this out. Again, this is something I think should be addressed after choosing to merge this proposal, and this feature is still useful without updating lib.d.ts.

Emitted JavaScript

Regardless of the syntax used to specify the thisArg, it must be stripped from the outputted JavaScript. This is simply a syntactical addition to the type engine, which is always stripped from emitted javascript.

Incompatibilities

I won't claim to know of every feature currently proposed for ES6 and ES7 / current TypeScript proposals, but I don't believe that the proposed syntax conflicts any proposed feature.

Breaking Changes

There are no breaking changes by simply adding this feature (as functions must manually add an annotation for the type of their thisArg). Breaking changes would only be introduced should this be added to lib.d.ts, or if classes begin to automatically set their thisArg.

Activity

  1. Arnavion commented on Feb 9, 2015

    @Arnavion
    Contributor
    1. When you say that method.bind({})(); (where method has thisArg of type SomeType) is an error, is it an error at the call to bind or the call to the bound function? I guess it's the former.

    2. If type Foo is assignable to type Bar, is function<this: Foo>() assignable to function<this: Bar>() ? Is function<this: Bar>() assignable to function<this: Foo>() ? I guess the first is no and the second is yes.

    3. Is function<this: any>() assignable to function<this: Foo>() ? Is function<this: Foo>() assignable to function<this: any>() ? I guess both are yes (this follows from the previous point, since all types are assignable to any and any is assignable to all types).

    4. Is any function without an explicit thisArg considered to have a thisArg of type any ? (I think this would maintain current behavior.) Do fat arrow functions fall in this category? What about class methods, since you say they don't have an implicit thisArg that's the type of the class?

    5. Is it allowed for the thisArg to be void ? This would cover the case where a function takes a callback and calls it without providing a particular this value. In unstrict mode this inside the callback would be the global object, and in strict mode it would be undefined, so it would be nice to be able to say a callback parameter has <this: void> so that this cannot be accidentally used inside the callback argument.

    6. Does the result of bind automatically get a thisArg type? Say:

      var foo: Foo;
      var bar: () => void;
      var baz = bar.bind(foo);

      Is baz of type function(): void or function<this: Foo>(): void ? If the latter, this might introduce the same breaking changes that you're trying to avoid by not having an implicit thisArg on class methods, so I guess it's the former.

    7. You have an example of an interface method with a thisArg type that's not the same as the interface. This can be easily implemented by object literals, but how would a class implement such an interface? That is, are class methods also allowed to have a thisArg type that's not the same as the class type? Or is it simply impossible for a class type to implement such an interface. If the former, then such a method would have to be disallowed to be called on an instance of the class:

      interface Foo {
          foo: number;
      }
      
      class Bar {
          bar: string;
      
          someMethod<this: Foo>() { return this.foo; }
      }
      
      var barInstance = new Bar();
      barInstance.someMethod(); // Error?
      
      class Baz extends Bar {
          foo = 5;
      }
      
      var bazInstance = new Baz();
      bazInstance.someMethod(); // Valid?
    8. Is this allowed?

      class Foo {
          foo: number;
          someMethod() { return this.foo; }
      }
      
      interface Bar {
          bar: string;
      }
      
      var foo = new Foo();
      foo.someMethod = function<this: Bar>() { return this.bar.length; } // Error?
      
      foo.someMethod(); // Direct call to redefined someMethod(). Error?
      
      (function (x: Foo) { x.someMethod(); })(foo); // Indirect call to redefined someMethod(). Error?

      Since someMethod doesn't have an explicit thisArg nor an implicit thisArg per your proposal, it would seem it's allowed.

  2. nathggns commented on Feb 9, 2015

    @nathggns
    Author

    Arnav Singh (@Arnavion) Thanks for identifying some of the issues with my proposal.

    Here are my solutions:

    1. While it would be nice to make method.bind({})() error at call time (for consistency with other possible errors introduced by this proposal, I feel it could be confusing and hard to find the source of this error if we did do that. For that reason, it should fail when calling bind.

    2. When specifying a desired type for the thisArg, passed thisArgs must be either of that type or a subtype of that.

      Example:

      class Foo {}
      class Bar {}
      class FooBar extends Foo {}
      
      declare function someFunction(someCallback : <this : Foo>() => void);
      
      someFunction.call(new Foo()); // okay
      someFunction.call(new FooBar()); // okay
      someFunction.call(new Bar()); // error
    3. Yes, for the reasons you said.

    4. Yes. Fat arrows do also fall in to this category. Class methods are a bit of both. From outside, they have their thisArg specified as any inherently (it's actually a syntax error to try to set it explicitly as class methods are outside of the scope of this proposal). However, inside the body of a class method, the thisArg is assumed to be the type of the class in which the method is defined. This is to maintain current functionality.

      class Foo {
         someMethod() {
             // this is assumed to be of type Foo
         }
      
         // syntax error as doing this is outside the code of this proposal
         // to simplify a lot of things 
         // Can look at adding support for this once the proposal is accepted
         someOtherMethod<this : number[]>() {
      
         }
      }
      
      var foo = new Foo();
      var method = foo.someMethod();
      foo.someMethod(); // Allowed
      method(); // Allowed
      method.call({}); // Allowed
      method.apply({}); // Allowed
      method.bind({})(); // Allowed

      Note: If I haven't explained this well enough, let me know. I'll try again.

    5. Yes. TypeScript should assume that this === undefined inside the body of function<this : void>s.

    6. The type of thisArg can only be set at definition time. Using bind has no effect on this as long as you're binding to a compatible type. Trying to bind to an incompatible type is an error as explained in the proposal.

    7. This is not actually an interface method, it's a interface defining a function. Interface methods are not within the scope of this proposal as class methods are not within this proposal.

    8. Your first call with fail. The second won't, as it expects x to be of type Foo, so x.someMethod has an inherent type of function<this : any> (though code inside its body will assume that it is of type Foo, as explained in point 4.

    I hope this answers some of your questions.

  3. nathggns commented on Feb 9, 2015

    @nathggns
    Author

    I would just like to say, for me at least, adding new syntax that further conflicts with React's JSX would be a bad idea. If the proposed syntax conflicts with the implementation of jsx-typescript over at https://github.com/fdecampredon/jsx-typescript/ then I think the syntax should change.

    (Look at react/react#759 for more info on TypeScript/JSX compatibly).

  4. jasonmeisel commented on Feb 9, 2015

    @jasonmeisel

    If the proposed syntax conflicts with JSX (which it seems like it would, but this should be verified), here are some other possibilities:

    1. declare function someFunction(this : SomeType, someArg : SomeArgType): any;
    2. declare function someFunction((this : SomeType), someArg : SomeArgType): any;
    3. declare function someFunction(this : SomeType)(someArg : SomeArgType): any;
    4. declare function someFunction(SomeType)(someArg : SomeArgType): any;
    5. declare function someFunction : SomeType (someArg : SomeArgType): any;

    The biggest advantages of the proposed syntax are:

    • It won't conflict with any other syntax rules, since right now angle brackets cannot be in a function signature at all
    • It's clear to readers that the type is declared for "this" without prior knowledge of the syntax (unlike 4 and 5 above)

    For those reasons, I think 3 is the best replacement for the proposed syntax.

    I would also like to mention that I think this is a very important feature for the future of TypeScript, as many JS libraries rely on this-binding. The specific library where I found this problematic was @meteor-typescript.

  5. cspotcode commented on Feb 9, 2015

    @cspotcode

    Jason Meisel (@jasonmeisel) Your first advantage is incorrect, as angle brackets can be in a function signature. Angle brackets are used for TypeScript generics.

    Example: (view in TypeScript playground)

    class AbstractBuzz {
        foo: number;
    }
    class BuzzImpl extends AbstractBuzz {
        bar: string;
    }
    class Wrapper<T> {
        constructor(public wrapped: T) {}
    }
    function wrapIt<T extends AbstractBuzz>(instanceToWrap: T): Wrapper<T> {
        return new Wrapper(instanceToWrap);
    }
    
    var wrappedBuzz = wrapIt(new BuzzImpl());
    console.log(wrappedBuzz.wrapped.bar); // Compiles correctly thanks to generics.
    

    EDIT: A much better example of generic function signatures are the underscore / LoDash type definitions: https://github.com/borisyankov/DefinitelyTyped/blob/master/lodash/lodash.d.ts

  6. nathggns commented on Feb 9, 2015

    @nathggns
    Author

    For more examples of generics, look at the declarations for Q.js

    // Some trivial example. Would never do this
    var nums = Q.Promise<number[]>(resolve => {
        resolve([1, 2, 3]);
    });
    
    nums.then(nums => {
        nums // typescript knows this is a number[]
    });
  7. nathggns commented on Feb 9, 2015

    @nathggns
    Author

    Jason Meisel (@jasonmeisel) The guys over at React feel this wouldn't conflict with JSX anymore than TypeScript already does, and wouldn't break their current implementation. I propose we stick with the originally suggested syntax of function<this : Type> for this reason.

    react/react#759 (comment)

  8. jasonmeisel commented on Feb 9, 2015

    @jasonmeisel

    Andrew Bradley (@cspotcode) Nate Higgins (@nathggns) My mistake, I forgot about function generics :) If the proposed syntax doesn't break any existing systems, I am all for it!

  9. Arnavion commented on Feb 9, 2015

    @Arnavion
    Contributor

    Nate Higgins (@nathggns)

    1.For that reason, it should fail when calling bind.

    I do agree it should fail when calling bind. I just wanted to confirm that was your intent.

    2.When specifying a desired type for the thisArg, passed thisArgs must be either of that type or a subtype of that.

    That is not what I asked. I asked about the assignability of a function type with a particular thisArg to another function type with a different thisArg based on the assignability of the type of the thisArgs.

    7.This is not actually an interface method, it's a interface defining a function. Interface methods are not within the scope of this proposal as class methods are not within this proposal.

    Sorry, I misread your example. That said, by allowing them on the function operator you're also allowing them on interface and class methods. Consider:

    interface FooMethod {
        <this: Foo>(x: string): number;
    }
    
    interface Bar {
        func: FooMethod; // Equivalent to   func<this: Foo>(x: string): number;
    }

    Do you intend that func's thisArg type should revert to any in this case?

    8.Your first call with fail. The second won't, as it expects x to be of type Foo, so x.someMethod has an inherent type of function<this : any> (though code inside its body will assume that it is of type Foo, as explained in point 4.

    Can you clarify what you mean by "first call" ? There are three lines in my example - the assignment, the direct call on the instance, and the indirect call through the class type. Which of these would compile with your proposal and which wouldn't?

    If by "first call" you mean the foo.someMethod() line, then this is surprising. I don't expect any difference between the two method calls (the second and third lines), as a difference there would require foo to stop being of type Foo, which TS does not support. So either all three lines should compile, or the assignment (the first line) should not compile and the other two lines become irrelevant.

  10. nathggns commented on Feb 10, 2015

    @nathggns
    Author

    That is not what I asked. I asked about the assignability of a function type with a particular thisArg to another function type with a different thisArg based on the assignability of the type of the thisArgs.

    Sorry do you mean the following?

     class A {}
     class B extends A {}
    var x = function<this : number>(){};
    var y = function<this : string>(){};
    var z = function<this : A>(){};
    var zz = function<this : B>(){};
    
    x = y; // Error
    y = x; // Error
    zz = z; // Error
    z = zz; // Okay

    Do you intend that func's thisArg type should revert to any in this case?

    I think trying to set a property of an interface to a function interface that has a specified thisArg should be an error at this point, in order to avoid undefined behaviour. Eventually, the intention is to support explicitly setting the thisArg for class and interface methods but that is outside of the scope of this initial proposal, so I would make it an error for now.

    Can you clarify what you mean by "first call" ? There are three lines in my example - the assignment, the direct call on the instance, and the indirect call through the class type. Which of these would compile with your proposal and which wouldn't?

    Sorry I was being short-sited here. Yes, the assignment should error in order to stop changing Foo.

    class Foo {
        someMethod() {
    
        }
    }
    
    var foo = new Foo();
    
    foo.someMethod = function<this : number>() {}; // Error
    
    foo.someMethod(); // Wouldn't even reach this. 
  11. Arnavion commented on Feb 10, 2015

    @Arnavion
    Contributor

    Thanks, that answers my questions. You could put these points into your proposal post.

  12. nathggns commented on Feb 10, 2015

    @nathggns
    Author

    Arnav Singh (@Arnavion) I have done that now.

    Can I ask, do you support this proposal? While I know it would help me massively, and possibly the guys over at DefinitelyTyped (@DefinitelyTyped), I'm not sure how much other people need this feature.

  13. Arnavion commented on Feb 10, 2015

    @Arnavion
    Contributor

    I have been subscribed to #229 for a long time, so yes I do support it :)

    this-typing is very helpful for callbacks, of course, but my own use for this feature would be to annotate interface methods with <this: void> (see #1545 (comment)) and to see how implementing such an interface would look like. I know your proposal specifically excludes this-typing on interface methods, but it's a starting step.

  14. aholmes commented on Feb 10, 2015

    @aholmes

    Nate Higgins (@nathggns)

    I'm not sure how much other people need this feature.

    For what it's worth, I'm only an average TypeScript developer and constantly litter my code with var self = <MyType>this; just to get "this" type support. I think it's really greatly needed, especially with any prototypal inheritance being done. In my opinion it's a blindingly obvious hole in the language.

  15. nathggns commented on Feb 10, 2015

    @nathggns
    Author

    Thank you. Would've been a bit rubbish to have written the proposal and for no one to really want it.

    Sent from my iPhone

    On 10 Feb 2015, at 03:19, Aaron Holmes [email protected] wrote:

    Nate Higgins (@nathggns)

    I'm not sure how much other people need this feature.

    For what it's worth, I'm only an average TypeScript developer and constantly litter my code with var self = this; just to get "this" type support. I think it's really greatly needed, especially with any prototypal inheritance being done. In my opinion it's a blindingly obvious hole in the language.

    —
    Reply to this email directly or view it on GitHub.

  16. 28 remaining items

  17. wizofaus commented on Apr 15, 2015

    @wizofaus

    "TypeScript intends to add a type system onto vanilla ECMAScript, which means not covering up JS semantics"
    Why does it mean that? TypeScript is, to me, just a "better" language based on JavaScript, that happens to compile to JavaScript. If it continue to propagate things that make JavaScript difficult to code well with then it's failed in being an improvement.
    Understood about the complete spec - my main concern is still some sort of "smart" detection that warns when it appears you're probably misusing "this", i.e. when the method/field you're referring to after "this." is one in the current class. Even with full typing for 'this' such a mistake is possible.

  18. cspotcode commented on Apr 15, 2015

    @cspotcode

    I'm just saying that, realistically, proposals that stray too far from the TS developers' goals are likely to be rejected. They want to align with ECMAScript 6, adding a type system and tooling on top of Javascript, allowing easy interop with existing Javascript libraries. They intentionally avoid overly-complex code transformations. It's fairly easy to migrate existing codebases to TypeScript because they've focused on a type system and tooling, not on changing language semantics. Even the current arrow functions and classes compile into ES6 arrow functions and classes.

    If the goal is merely a far better language, one might argue you should write C# and compile that into JS.

  19. Artazor commented on Apr 16, 2015

    @Artazor
    Contributor

    For syntax I'd propose

    declare class MyClass {
        someMethod(x: integer);
    }
    
    var x = function[MyClass](x: integer) {
       // this: MyClass
       this.someMethod(x);
    }

    And for generics:

    declare class OtherClass<T> {
        someMethod(arg: T);
    }
    
    var y = function<T>[OtherClass<T>](x: T) {
        // this: OtherClass<T>
        this.someMethod(x);
    }

    About type system (roughly): this type should be strictly co-variated since we can not assign to this. And that is why in TS it can't be an additional parameter.

  20. kitsonk commented on Jun 8, 2015

    @kitsonk
    Contributor

    Sorry for arriving late at this, as the biggest "item" is the ability to do essentially late binding of typing to allow chaining. I have seen a fair few machinations, but maybe we are missing something that would not impede compatibility with JavaScript/EcmaScript and would be essentially very much TypeScript without challenging generic usages. Admittedly, I am not at all familiar with the sort of hoops that this "late" typing would have on the compiler, but what about the following syntax:

    interface A {
        foo(): typeof this;
    }
    
    class B implements A {
        foo(): typeof this {
            return this;
        }
    }
    
    class C extends B {
        bar(a: typeof this): typeof this {
            return a.foo();
        }
    }
    
    let c = new C();
    c.foo().bar(c).foo();  /* should be totally valid */

    So essentially, instead of a version of generics, or something else, whenever you need to type something of the current class (or validate that something is implemented an interface properly) you would use typeof this.

    While, I haven't worked through all the emitting of the above, I think it could easily be determined and that the "reserved" type of typeof this would always be determined within the context of block it was existing in, meaning it can be scoped properly.

  21. pmccloghrylaing commented on Jun 10, 2015

    @pmccloghrylaing

    Kitson Kelly (@kitsonk) I think that suggestion belongs in #285.

    My vote is for this as the first argument, without a special separator. It seems to more accurately represent what is happening without adding a special syntax. I think by including this : any implicitly for functions that don't specify this means assignability is already taken care of as this is treated as another argument. It should also make things easier to implement. Unfortunately this does make assignability looser than it could be (see: Function Argument Bivariance).

    Nate Higgins (@nathggns): your proposal has assignability the wrong way around for z and zz:

    class A {}
    class B extends A {}
    var z = function<this : A>(){};
    var zz = function<this : B>(){};
    
    z.call(new A()); // Okay
    z.call(new B()); // Okay
    zz = z // Okay
    
    zz.call(new A()); // Error
    zz.call(new B()); // Okay
    z = zz; // Error
  22. jussi-kalliokoski commented on Jun 16, 2015

    @jussi-kalliokoski

    FWIW, I (unaware of this issue) opened a similar one on facebook/flow#452, in my last comment proposing the same syntax that was proposed here (e.g. function add (this : number, b: number) { return this + b; }).

    I think that would be the most intuitive syntax for this, and I use it in trine for annotating function types (I use _this because babel currently considers this as an illegal parameter name). This would nicely also leave room for something like a named this syntax which would make trine's paradigm more readable (and also give full control over the scoping and destructuring of this):

    function add (&a : number, b : number) { return a + b; }
    function vec2add (&[x1, y1], [x2, y2]) { return [ x1 + x2, y1 + y2 ]; }
    // bind syntax:
    [1,2]::add([5,3]) // [6, 5]
  23. Artazor commented on Aug 3, 2015

    @Artazor
    Contributor

    With respect to the new ES7 "::" bind operator I'd propose

    var abc = function MyClass::(x: integer) {
       // this: MyClass
       this.someMethod(x);
    }
    
    function MyClass::abc(x: integer) {
       // this: MyClass
       this.someMethod(x);
    }
    
    var abc = function<T> OtherClass<T>::(x: T) {
        // this: OtherClass<T>
        this.someMethod(x);
    }
    
    function<T> OtherClass<T>::abc(x: T) {
        // this: OtherClass<T>
        this.someMethod(x);
    }

    Will it lead to the breaking changes?

  24. Artazor commented on Aug 3, 2015

    @Artazor
    Contributor

    It will work also with {} types:

    function {count:number}::inc() {
         this.count++
    }

    The most difficult seems to parse the case where type should be enclosed into parentheses:

    function (()=>void)::inc() { ... }
    function (A | B)::test() { ... }

    And even

    function<T> (T::(x: number)=>number)::bar(y: number, t: T): number {
         return t::this(y) + y
    }
    
    var foo = function {factor:number}::(x: number) { return this.factor*x*x; }
    console.log(foo::bar(5, {factor: 0.1})) // 7.5

    However it seems that here we need not more than two or three look-ahead tokens. Need we?

  25. nathggns commented on Aug 6, 2015

    @nathggns
    Author

    Is there any official progress/comments on this?

  26. danquirk commented on Aug 6, 2015

    @danquirk
    Member

    Nate Higgins (@nathggns) see #3694 for the latest proposal

  27. mhegazy commented on Sep 16, 2015

    @mhegazy
    Contributor

    this is now tracked by #3694

  28. Zorgatone commented on Sep 28, 2015

    @Zorgatone

    +1

  29. locked and limited conversation to collaborators on Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already createdSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions