{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6018","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6018/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6018/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6018/events","html_url":"https://github.com/Microsoft/TypeScript/issues/6018","id":121308321,"number":6018,"title":"Function `this` types","user":{"login":"sandersn","id":293473,"avatar_url":"https://avatars.githubusercontent.com/u/293473?v=3","gravatar_id":"","url":"https://api.github.com/users/sandersn","html_url":"https://github.com/sandersn","followers_url":"https://api.github.com/users/sandersn/followers","following_url":"https://api.github.com/users/sandersn/following{/other_user}","gists_url":"https://api.github.com/users/sandersn/gists{/gist_id}","starred_url":"https://api.github.com/users/sandersn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sandersn/subscriptions","organizations_url":"https://api.github.com/users/sandersn/orgs","repos_url":"https://api.github.com/users/sandersn/repos","events_url":"https://api.github.com/users/sandersn/events{/privacy}","received_events_url":"https://api.github.com/users/sandersn/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/In%20Discussion","name":"In Discussion","color":"556b75"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-12-09T18:21:02Z","updated_at":"2015-12-09T21:33:35Z","closed_at":null,"body":"This is a proposal for the other half of this-types outlined in #3694. The first half -- class 'this' types -- was implemented in #4910.\r\n\r\n## Motivation ##\r\nThis-types for functions allows Typescript authors to specify the type of `this` that is bound within the function body. Standalone functions are an important part of Javascript programming, so Typescript's ability to check the type of `this` will capture patterns that it does not today. This feature enables three main scenarios.\r\n\r\n1. Assignability checking for callbacks -- methods must match methods and lambdas must match lambdas.\r\n2. Objects built from standalone functions.\r\n3. Specific sub-type requirements on the caller of a method that can't be captured in an inheritance hierarchy.\r\n\r\nTypescript currently sets the type of `this` to `any` except in when checking method bodies, where it is the class' `this` type. To be backward compatible, almost all of this feature will be hidden behind a `--strictThis` flag at first. This is because some working Javascript patterns will not be legal until they are annotated. \r\n\r\nExamples\r\nThese examples assume that `--strictThis` is enabled. I'll add examples without `--strictThis` later.\r\n\r\n### Prevent incorrect assignment between callback functions and methods ###\r\n\r\n```ts\r\ndeclare class Callbacks {\r\n  onClick(e: Event): void; // this defaults to Callback's this type\r\n}\r\nfunction handleClick(e: Event) {\r\n  // Callback's this is not accessible here\r\n}\r\nfunction handleClick2(this: Callbacks, e: Event) {\r\n  // Callback's this *is* accessible here\r\n}\r\ndeclare class Methods {\r\n clickMethod(e: Event): void; // this defaults to Method's this type\r\n}\r\nlet c = new Callbacks();\r\nlet m = new Methods();\r\n// assign methods\r\nc.onClick = handleClick; // ok \r\nc.onClick = handleClick2; // ok \r\nc.onClick = m.clickMethod; // ERROR: 'Mine' is not assignable to 'Callbacks'\r\nc.onClick = e => console.log(\"this is not defined inside a lambda\"); // ok \r\n```\r\n\r\n### Build new objects from existing functions and methods ###\r\n\r\n```ts\r\nlet f = function(this: {data: number}) {\r\n  console.log(this.data2) \r\n}\r\nlet o = {\r\n  data: 12\r\n  f: f\r\n  g: function() {   // this is inferred from the contextual type\r\n    console.log(this.data); \r\n  }\r\n}\r\n```\r\n\r\n### Require a suitable 'this' for substituting the current one ###\r\n\r\nFor example, `Function.call` allows the caller to specify a new `this`. This results in an interesting type:\r\n\r\n```ts\r\ninterface Function {\r\n  call<This,Return>(this: (this:This, ...args:any[]) => Return, \r\n                                  thisArg: This,\r\n                                  ...args:any[]):\r\n    Return;\r\n}\r\n```\r\n\r\n\r\n## Syntax ##\r\n\r\n`this` is an optional first argument to all functions and methods except for lambdas. This syntax is a good representation of Javascript's actual semantics, where `this` is available inside all function bodies and is checked the same way as any other parameter once its type is known. \r\n\r\nExamples of the syntax:\r\n\r\n```ts\r\nfunction f(this: {n: number}, m: number) {\r\n    return this.n + m;\r\n}\r\nclass C {\r\n  n: number\r\n  m1(this:this, m: number) {\r\n    return this.n + m\r\n  }\r\n  m2(this: {n: number}, m: number) {\r\n    return this.n + m\r\n  }\r\n}\r\n```\r\n\r\nNote that, although it is syntactically an argument, `this` is treated specially during checking and erased at emit.\r\n\r\n## Semantics ##\r\n\r\nThe semantics fall into 3 areas\r\n1. Function body checking.\r\n2. Assignability checking.\r\n3. Call-site checking.\r\n\r\n### Function Body Checking ###\r\nFunction bodies are checked as if `this` were a normal parameter. References to `this` in the body are required to satisfy the type provided for `this`. If `this` is not provided:\r\n* `this` is the class' `this` type for methods.\r\n* `this` is not bound for lambdas.\r\n* `this` is of type `void` for functions (for backward compatibility).\r\n\r\nFor unannotated methods and lambdas, the behaviour does not change from current Typescript. For unannotated functions, the void type has no members, so uses of `this` are essentially disallowed. This will have to change for \"loose this\" mode.\r\n\r\nExamples:\r\n\r\n```ts\r\nclass C {\r\n  n: number;\r\n  m1() { console.log(this.n); }// ok: this is C's this\r\n  m2(this: void) { console.log(this.n); } // ERROR: void has no member 'n'\r\n}\r\nfunction f(this: {n: number}) { console.log(this.n); }\r\nfunction g() { console.log(this.n); } // ERROR\r\nf.call(new C()); // use C as `this`\r\nlet lambda = () => this.notThere; // ERROR: `this` is the global object\r\n```\r\n\r\n### Call-Site Checking ###\r\nCall sites check a `this` argument against the function or method's `this` parameter. To determine the `this` argument:\r\n\t• If the call is of the form `o.f()`, the type of `this` is the type of `o`.\r\n\t• If the call is of the form `f()`, the type of `this` is void.\r\n\r\nFor example:\r\n\r\n```ts\r\ndeclare function f(this:void, n: number);\r\nf(12); // ok: no object, so this: void, which matches f's `this`.\r\n```\r\n\r\nThe `this` parameter's type can be given, as in the previous example. If it is not, then, similarly to body checking, it is:\r\n* the class' type `this` for methods. \r\n* not present for lambdas.\r\n* of type `void` for functions.\r\n\r\nIt is important that methods cannot be called without an object as if they were standalone functions. Given the types above, normal assignability rules will ensure this. On the other hand, functions actually can be called as if they were methods -- they just happen not to refer to `this`. To support this, we add an exception to normal assignability rules that applies when calling a function as if it were a method.  \r\n\r\nSpecifically, when the callee's `this` type -- the `this` parameter -- is of type `void`, a non-void `this` type will satisfy it. Let's look at examples of the two cases:\r\n\r\n```ts\r\ndeclare class C { m(); }\r\nlet c = new C();\r\nlet f = c.m; // ok, f: (this:C) => void\r\nf(); // ERROR, standalone call -- 'this:void' is not assignable to 'this:C'\r\n\r\ndeclare function g(this:void, n: number);\r\nlet o = { g };\r\no.g(12); // ok: even though `o` is not void, you can still call `g`.\r\n```\r\n\r\nHere is an example using lambdas to build up an object literal:\r\n\r\n```ts\r\nlet o = { n: 12, f: m => m + 1 };\r\no.f(12); // ok: `this:o`, but the lambda ignores it.\r\n```\r\n\r\n### Assignability Checking ###\r\n\r\nThe rules for assignability are similar to those for call checking. The only complication is that default types for `this` have to be determined for both the source and the target. Like call checking, they conspire to make both functions and lambdas assignable to methods, but methods *not* assignable to functions. \r\n\r\n## Open questions ##\r\n1. How should loose-this work?\r\n\t1. No annotations allowed.\r\n\t2. No checking at call sites or assignments.\r\n\t3. Default to any or missing at call sites or assignments.\r\n2. Should function literal's `this` be contextually typed?\r\n\tIt would make callback-like methods require no additional type annotations to be fully checked:\r\n\r\n\t```ts\r\n\tc.callback = function(arg) { return arg + this.property; }\r\n\t```\r\n\t\r\n\tNotably, allowing the contextual type of an object literal to contextually type a function member would allow checked ad-hoc construction from motivating scenario (2):\r\n\r\n\t```ts\r\n\tlet o = {\r\n\t  n: 12\r\n\t  f: function() { return this.n }\r\n\t}\r\n\t```\r\n\r\n3. How should interfaces work?\r\n\tShould the default be `this`? Missing? `any`? `void`?\r\n\tWhat if the interface is merged with a class?\r\n\r\n## Implementation Progress ##\r\n\r\nA prototype is at sandersn/TypeScript/check-this-function-types. It checks function and methods bodies, call sites and assignability but does not erase the 'this' parameter during emit. It also stuffs 'this' into the parameter list whenever it's convenient. It is somewhere between strict-this and loose-this in this proposal.\r\n\r\nHere's what's left to do:\r\n\r\n1. Lambdas should work.\r\n2. Interfaces should work.\r\n3. Strict mode should actually be strict.\r\n4. Separate representation for this-parameters.\r\n5. Tests to ensure that type parameters and type guards work.\r\n6. Loose mode should work.\r\n"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/163399092","html_url":"https://github.com/Microsoft/TypeScript/issues/6018#issuecomment-163399092","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6018","id":163399092,"user":{"login":"sandersn","id":293473,"avatar_url":"https://avatars.githubusercontent.com/u/293473?v=3","gravatar_id":"","url":"https://api.github.com/users/sandersn","html_url":"https://github.com/sandersn","followers_url":"https://api.github.com/users/sandersn/followers","following_url":"https://api.github.com/users/sandersn/following{/other_user}","gists_url":"https://api.github.com/users/sandersn/gists{/gist_id}","starred_url":"https://api.github.com/users/sandersn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sandersn/subscriptions","organizations_url":"https://api.github.com/users/sandersn/orgs","repos_url":"https://api.github.com/users/sandersn/repos","events_url":"https://api.github.com/users/sandersn/events{/privacy}","received_events_url":"https://api.github.com/users/sandersn/received_events","type":"User","site_admin":false},"created_at":"2015-12-09T21:33:35Z","updated_at":"2015-12-09T21:33:35Z","body":"No, your example already works in Typescript, `this` just has type `any`. It's definitely part of this feature. I added your example to open question (2)."}}