Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support specifying type of "this" in function implementations #229

Closed
abergs opened this issue Jul 24, 2014 · 58 comments
Closed

Support specifying type of "this" in function implementations #229

abergs opened this issue Jul 24, 2014 · 58 comments
Labels
Duplicate An existing issue was already created

Comments

@abergs
Copy link

abergs commented Jul 24, 2014

Migrated issue from codeplex: https://typescript.codeplex.com/workitem/507

Currently typescript will type the "this" pointer in function callbacks as "any." Arrow syntax lets us capture "this" from the outer scope, and this can enable proper typing. It would be nice to be able to provide
an optional "ambient this" declaration in function signatures:

(declare this : MyType, first: number, second: string) : any;

The rules would be:

  1. the declaration is obviously optional
  2. if specified, should be first parameter
  3. cannot be used with arrow syntax lambdas (should be compiler error)

I know you can cast as needed to get the intellisense but I'd rather not put this responsibility on the implementer of the function. Ideally it should be part of the definition.

Example with Sammy:

var app = $.sammy("#view", function() {
     // define default route
     this.get("#/", function() { ... } 
});

In the above case, typescript can only type this as any.

Thoughts?

@Bartvds
Copy link

Bartvds commented Jul 24, 2014

There are many JS libraries out there that depend on using a specifically bound "this" in callbacks.

Note the codeplex issue had 68 votes.

@johnnyreilly
Copy link

👍

2 similar comments
@abergs
Copy link
Author

abergs commented Jul 24, 2014

👍

@fdecampredon
Copy link

👍

@abergs
Copy link
Author

abergs commented Jul 25, 2014

@fdecampredon Have you moved the issue regarding exposing private types? That issue is still important for Facebook React compatibility, right?

@fdecampredon
Copy link

Not yet @abergs I'll do it

@basarat
Copy link
Contributor

basarat commented Jul 28, 2014

👍

@RyanCavanaugh
Copy link
Member

+Needs Proposal (see https://github.com/Microsoft/TypeScript/wiki/Writing-Good-Design-Proposals)

The major design impediment we had here was that it was really unclear what the subtype relationships are for functions with a declared this parameter, and how the various DOM functions that might or might not care about this should act.

Some food for thought:

class MyClass {
    x = 5;
    fn1 = () => { console.log(this.x); }
    fn2() { console.log(this.x); }
}
declare function callme(f: (this: SomeType));

var m = new MyClass();
callme(m.fn1); // Allowed? Not allowed?
callme(m.fn2); // Allowed? Not allowed?
callme(window.focus); // Allowed? Not allowed?

@RyanCavanaugh RyanCavanaugh changed the title Support optional ambient "this" pointer typing in callback/function signatures Support specifying type of "this" in function implementations Jul 30, 2014
@QueueHammer
Copy link

I like "this" for a couple of reasons. Currently there is no way to create practical private values in classes using TypeScript syntax. When the situation requires private members in a class, read "function constructed object", I end up using regular JavaScript syntax and use "this" to create my public members. However as an "any" there is no auto complete of values assigned to "this" in the class, nor can any sub value of an "any" have a type. Being able to define "this" as an interface would mitigate this. Or we could allow private members in classes... but those comments are for a different issue thread.

@redexp
Copy link

redexp commented Aug 23, 2014

What if declare this just like we declare "strict mode"; ?

function action() {
  this:SomeClass;
  // ...
}

@ivogabe
Copy link
Contributor

ivogabe commented Aug 23, 2014

@redexp I think it's better to have this in the signature, since definition files (.d.ts) don't have function implementations, so how could you generate a definition for your example? Also this gives a problem for functions that have a callback argument:

function callFunction(callback: (this: string) => void) {
    callback.call('foo');
}

How could you write that with this in the function body?

@redexp
Copy link

redexp commented Aug 23, 2014

Ok, but as for me it's kinda confusing when this is declared with params. I guess you will say that to put this declaration to square (or some other) brackets after params is also bad idea?

function callFunction(callback: ()[this:string] => void) {
    callback.call('foo');
}

function zooKeeper(cage: AnimalCage)[this:SomeClass];

interface Widget {
    sprock()[this:SomeClass]: void;
}

@ivogabe
Copy link
Contributor

ivogabe commented Aug 23, 2014

this must always be the first parameter and this has another color than the other parameters (in Visual Studio and others), that prevents most of the confusion. And, the parameters are the input of a function and the value of this is also a special kind of input, it isn't strange to have all the inputs in one list in my opinion.

@Bartvds
Copy link

Bartvds commented Aug 23, 2014

Maybe as type parameter?

interface Widget {
    sprock<this:SomeClass>(): void;
}

Would fit because it matches conceptually with how regular type parameters are specified and doesn't need to introduce another syntax pattern. I'm not sure if this can be used as type parameter name at this time but seems reasonable to claim it.

@aholmes
Copy link

aholmes commented Aug 23, 2014

Perhaps we can use a left-side type parameter for specifying this types?

class ButtonNode
{
    public Text: string;
}

class MyClass
{
    private ButtonNode ClickHandler(): void
    {
        console.log(this.Text);
    }
}

@DanielRosenwasser
Copy link
Member

@aholmes I think that would be fairly confusing for people coming from many other C-style languages like C#, Java, D, etc.

Even I read that and assume that MyClass.ClickHandler returns a ButtonNode despite working in TypeScript all day.

@aholmes
Copy link

aholmes commented Aug 23, 2014

That's a fair point, though given that the return type follows the signature, maybe this is something to simply get used to.

For what it's worth, I come from the C-style languages, and the ": type" syntax still confuses me, especially when working with JavaScript's object definition syntax.

@johnnyreilly
Copy link

I like @Bartvds suggestion - fits well I think.

@ivogabe
Copy link
Contributor

ivogabe commented Aug 24, 2014

@Bartvds I find it strange to add the this to the generic list, since a generic represents a type and this is a value, just like the arguments. Adding a value to a type list would be more confusing than adding a value to a value list.

@redexp
Copy link

redexp commented Aug 26, 2014

How about to separate this and params with |

function show (this: Node | seconds: Number, callback: Function) {}

@QueueHammer
Copy link

@redexp That's very concise. That syntax could even be used for classes.

class Something {
    constructor(this:element)
}
class SomethingElse {
    constructor(this:element | otherParam:type)
}

Great idea.

@johnnyreilly
Copy link

I'm not so keen on having the type of this specified in the parameter list as I think it forces you to do extra mental processing when you're reading the code.

To take @redexp example:

function show (this: Node | seconds: Number, callback: Function) {}

Myself, I find this more readable:

function show<this: Node> (seconds: Number, callback: Function) {}

My reason is that when you look at the parameters you don't have to remember to harvest out the this from the start. I think it improves the code readability generally (which I think is important). That's my 10 cents anyway 😄

@redexp
Copy link

redexp commented Aug 28, 2014

Guys, we extremely need it. All my code is in red because IDE don't know the type of this, which I use a lot. May be let's start some poll where people can decide which option is the best?

@aclave1
Copy link

aclave1 commented Mar 27, 2015

@basarat @nycdotnet I agree with you guys.

@englercj
Copy link

Consider also the case where you can specify a callback and the context it will be called with. For example, an EventEmitter3 event handler:

emitter.on('somevent', function () {
    // in this context 'this' is the same as the outer scope 'this' due to my third param to `.on()`
    // TS treats 'this' here as 'any', but I want it to be a specific type that I can specify with an annotation
    // without having to do a function bind or a self scope var.
}, this);

@redexp
Copy link

redexp commented Apr 13, 2015

Hey guys. Looks like we will never figure out how statically (I mean .d.ts files) type this scope. What about to switch from config to code? Just like grunt (with tons of static configs) to gulp (with nice modular "config" in code). For example we will have function this type callback witch should return type of this.

function getOnMethodThisType(on) {
  if (on.arguments.length === 3) {
    return on.arguments[2].type;
  }

  return TS.globalScope.type;
}

Thinking about "code instead of config", maybe we need new definition file type where instead of pseudo code config will be just js/ts code? For example

TS.class('Emitter')
  .method('on', function (on: TSMethod) {
    if (!on.callee) {
      throw new TS.Error.CalleeRequired();
    }

    switch (on.arguments.length) {
      case 2:
        return {
          "arguments": [String, Function],
          "this": TS.global.type,
          "return": on.callee.type
        };
      case 3:
        return {
          "arguments": [String, Function, Any],
          "this": on.arguments[2].type,
          "return": on.callee.type
        };
    }

    throw new TS.Error.WrongArguments();
  });

Pros: modular code, share code through npm, custom errors like "One argument is deprecated since v1.4", code is more readable than .d.ts, will solve any this problems.

@lazdmx
Copy link

lazdmx commented May 5, 2015

👍

@loucyx
Copy link

loucyx commented May 11, 2015

👍 ... right now I'm using the "self workaround", and I'm not enjoying it Dx

@JsonFreeman
Copy link
Contributor

I agree with @ivogabe at #229 (comment). If it is a type parameter, that means the type of 'this' can vary on a per-call basis. And if it varies, then a 'this' expression will not have a known type in the body. Better to have it be a parameter with a type annotation.

@304NotModified
Copy link

👍

@dead-claudia
Copy link

FWIW, this is required for a lot of .d.ts files on DefinitelyTyped. Here's an incomplete list of modules that require this:

  • jquery
  • underscore
  • lodash
  • lazy.js

Also, many of the native ECMAScript APIs use bound this arguments, such as many of the Array.prototype methods and some of the String methods. JSON.{stringify,parse} call their callback with either an object or array.

// Array generics
interface Callback<T, U> {
  (this: T, value: U, index: number, array: ArrayLike<U>): any;
}

interface ArrayLike<T> {
  [key: number]: T;
  length: number;
}

interface Array<T> {
  // things...
  forEach(this: ArrayLike<T>, callback: Callback<ArrayLike<T>, T>): void;
  forEach<U>(this: ArrayLike<T>, callback: Callback<U, T>, thisArg: U): void;
  // more things...
}

// JSON
interface JSONCallback {
  (this: any[], key: number, value: any) => any;
  (this: {[key: string]: any}, key: string, value: any) => any;
}

interface JSON {
  parse(text: string, reviver?: JSONCallback): any;
  stringify(value: any, reviver?: JSONCallback | string[], space?: string | number): string;
}

// Function methods
interface Function {
  bind<T, U>(thisArg: T, ...args: any[]): (this: T, ...args: any[]) => U;
}

So, definitely 👍

@kobezzza
Copy link

kobezzza commented Jul 5, 2015

+1 Very need this feature.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 16, 2015

This is now tracked by #3694.

@mhegazy mhegazy closed this as completed Sep 16, 2015
@mhegazy mhegazy added the Duplicate An existing issue was already created label Sep 16, 2015
@Zorgatone
Copy link

+1

@distinctdan
Copy link

distinctdan commented May 9, 2016

As an easy workaround, you can declare a variable at the top of the function, give it a type, and set it equal to this. This allows you to do type checking on it, as long as you don't mind renaming "this":

myMethod() {
    var self: MyClass = this;
    self.doStuff();
}

@QueueHammer
Copy link

My work around for the last 2 years has been to just not use TypeScript. Though it's was for other issues at the time, which some of them have been fixed since v0.8.

@hraban
Copy link

hraban commented May 11, 2016

@QueueHammer thanks man, but not terribly constructive. Did you find an alternative that does fix this problem, and how?

@saschanaz
Copy link
Contributor

This is fixed by #6739 on April 8.

@kitsonk
Copy link
Contributor

kitsonk commented May 11, 2016

But isn't released yet (it is in typescript@next and will be released in 2.0).

@RyanCavanaugh RyanCavanaugh removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript labels May 11, 2016
@yankeeinlondon
Copy link

Has this now been released as part of 2.0? Is there a set of reference examples of using this?

@kitsonk
Copy link
Contributor

kitsonk commented Oct 30, 2016

Yes. It is sort of covered here: https://www.typescriptlang.org/docs/handbook/functions.html

But taking an example from above:

myMethod(this: MyClass) {
    this.doStuff();
}

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests