GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — This function types by sandersn · Pull Request #6739 · microsoft/TypeScript

Conversation

@sandersn

@sandersn Nathan Shively-Sanders (sandersn) commented Jan 29, 2016

Copy link
Copy Markdown
Member

Implements the proposal at #6018 and finishes the work described at #3694. See the bottom of this description for a tutorial and usage recommendations for this new feature.

This change adds checking for this types in functions. With this-function types, you can prevent the use of methods as free functions. And this types allow you to make constructor functions that return a real type rather than any. A number of other common javascript patterns can be given types or even inferred with --strictThis turned on.

Correctly prevent references to this when assigning callbacks

interface Callbacks {
  property: number;
  onClick(this: void, e: Event): void;
}
function handleClick(this:void, e: Event) {
  console.log(this.property); // error, 'void' has no member 'property'
}
class C {
  property: string
  tryHandleClick(this: this, e: Event): void {
    console.log(this.property); // OK, 'C' has member 'property'
  }
  actuallyHandleClick(this: void, e: Event): void {
    console.log(this.property); // error, 'void' has no member 'property'
  }
}
let c: C;
let callbacks: Callbacks;
callbacks.onClick = handleClick; // OK, this: void for both
callbacks.onClick = c.tryHandleClick; // Error, 'C' is not assignable to 'void'
callbacks.onClick = c.actuallyHandleClick; // OK, this: void for both

Note that callback functions are prevented from referring to members of this because they declare that this: void in order to be assignable to onClick.

Also note that the less common case of callback methods still allows functions to be assigned to these properties:

class Callbacks {
  m: number;
  callback: (this: this, n: number) => number;
}
function f(this: void, n: number) {
  return n;
}
let c: Callbacks;
c.callback = f; // OK, because f does not refer to any properties of `this`.

You can even require a different this for methods that will be used as a callback:

interface Callbacks {
  property: number;
  onClick(this: Callbacks, e: Event): void;
}
class C {
  property: string
  tryHandleClick(this: Callbacks, e: Event): void {
    console.log(this.property); // OK, 'C' has member 'property'
  }
}
let c: C;
let callbacks: Callbacks;
callbacks.onClick = c.tryHandleClick;

Contextual typing of methods and functions in object literals

Now when an object literal declares that it is of some type, this is also contextually typed inside the object literal.

interface I {
  n: number;
  method(m: number): number;
  callback: (m: number) => number;
}
let o: I = {
  n: 12,
  method(m) {
    return this.n + m; // OK, `this: I` from context
  },
  callback = m => m + 1, // OK, `this: void` from context
}

Build object literals from existing functions

You can also define functions alone and then later build an object literal from them. If the functions specify the this type, then the compiler will check references to this in the function's body. And it will also check that the function's this is assignable to the this of the object literal's type.

interface I {
  n: number;
  method: (m: number) => number;
}
function futureMethod(this: I, m: number) {
  return this.n + m;
}
let o: I = { n: 12, method: futureMethod };

Defining a function this way also requires that it cannot be called free:

futureMethod(12); // error, 'void' is not assignable to 'I'
o.futureMethod(12); // ok, 'o' is of type 'I'

The type of functions when used as constructors is now known.

When you use new with a function, you can now declare the type of the object that will be constructed with this. Previously, the type was always any. The compiler will also check that your assignments to this are correct in the body of the function.

function Symbol(this: Symbol, flags: SymbolFlags, name: string) {
  this.flags = flags;
  this.name = name;
  this.declarations = undefined;
}
let s = new Symbol(flags, "core"); // s: Symbol

strictThisChecks flag default types

UPDATE: --strictThisChecks removed

Because of performance concerns and lack of information on how people will use this types, we decided to remove --strictThisChecks for this release. I left the original explanation below.

--noImplicitThis

--noImplicitThis makes it an error to use this that is implicitly any inside of a function:

function F(x: number, y: number) {
    this.x = x; // this: any, so anything is legal
    this.z = y; // missed horrible typo
}
let f = new F(); // also, f: any  <-- that's sad :(

Add an annotation to fix this:

interface F { 
    x: number;
    y: number 
}
function F(this: F, x: number, y: number) {
    this.x = x;
    this.y = y;
}
let f = new F(12, 13); // f: F <-- hooray! :)

Previous explanation of strictThisChecks

For backward compatibility, this types default to any if not specified. However, if --strictThis is specified, then functions will default this: void and methods will default this: this. This removes the need for most of the annotations in the previous examples. For example, an interface can declare a function with this: void by using function syntax and a method with this: this by using method syntax:

interface I {
  f: (n: number) => number; // this: void
  m(n: number): number; // this: this
}
function g(n: number) { // this: void
  return n;
}
class C {
  private special: number = 12;
  m(n: number): number { return n + this.special; }
}

let i: I;
let c: C;
i.f = g; // ok, this: void
i.f = c.m; // error, 'void' is not assignable to 'C' (missing member 'm')
i.m = c.m; // error, 'I' is not assignable to 'C' (missing member 'special')

This breaks a lot of existing code, but is easy to write for future code.

How to upgrade code to --strictThis

When you switch on --strictThis you'll see a lot of errors with calling a method as if it were a function:

interface Object {
  method(n: number): void;
}
const f = object.method; 
f(12); // ERROR, this should be 'Object' not 'void'

You can fix the usage or the interface definition:

// Fix usage:
const fix1 = n => object.method(n);
correct(12); // OK, lambda captures the object and doesn't require 'this'
f.call(object, 12); // alternate fix: use Function.call

/// OR ///
// Fix definition:
interface Object {
  method(this: void, n: number): void;
}
// alternate fix: use function syntax to implicitly set this: void
interface Object {
  method: (n: number) => void;
}
const f = object.method;
f(12); // OK, this is void

Designing new, strict-this code

To be able to switch --strictThis on, the style that you write interfaces needs to change. You need to consider how people will use your interfaces. If they will treat your interface's functions as methods on an object, you should use the normal method declaration syntax. If they will treat them as a callback or some other kind of free function, you should declare them using the function property syntax. For example:

interface Extractor {
  extract(input: string): Row[]; // method style
}

interface Callbacks {
  callback: (e: Event) => void; // function property style
}

A safe default is to use the method declaration syntax. I discuss the tradeoffs below.

OO programming style

If you are writing pure OO Typescript/ES6, then you don't need to change much to work with --strictThis.

  • OO interfaces should have this: this
  • The only exception should be callbacks, which should have this: void.

Fortunately, these are the default types for the method and function syntax, respectively, so you don't have to write anything much different:

interface Extractor {
    extract(input: string): Row[];
    // the default (this: this) makes this the same as writing:
    // extract(this: this, input: string): Item[];

}
class XmlExtractor implements Extractor {
    extract(input: string): Row[] {
        // read from Xml into your Row objects
        // you can call private methods, etc, like before.
    }
    private helperMethod(sub: string): Row {
        // ...
    }
}

Now you can't assign extract to a function by mistake:

const xml = new XmlExtractor();
otherObject.onCall = xml.extract; // error: types of this are not compatible
let ex = xml.extract; // ok ...
ex('<row>...</row>'); // error, this: void is not compatible with this: XmlExtractor
otherObject.onExtract = ex; // error this: void is not compatible with this: XmlExtractor

And if you implement an interface, your methods get the right this-type regardless of what syntax you use:

interface Callbacks {
  callback: (e: Event) => void;
}
class XmlExtractor implements Extractor, Callbacks {
  extract(input: string): Row[] {
    // here, this: XmlExtractor
  }
  callback(e: Event) {
    // here, this: void
    this.extract(e.data); // error! 'this: void' has no method 'extract'
  }
}
otherObject.onExtract = xml.extract // error
otherObject.onExtract = xml.callback // ok!

How can I use a method as a callback?

You may have noticed that XmlExtractor.callback isn't that useful as a method since you can't actually refer to any other methods. The solution is the same as you use today: wrap the method call inside a lambda:

class XmlExtractor implements Extractors, Callbacks {
  extract(input: string): Row[] { ... }
  callback = e => this.extract(e.data); // ok, => doesn't capture 'this'
}

This formulation is OK because lambda (=>) doesn't bind this, so this comes from the class instead of from the implementing function.

Functional style

If you are writing your code in functional style, you probably use interfaces to describe records of functions. You can still declare the functions using the method style and build instances of the interface using an object literal of your functions:

interface Compiler {
  parse(program: string): Node;
  bind(tree: Node): Map<Symbol>;
  check(tree: Node): Diagnostic[];
}
function parse(program: string) {
  // code inside does not refer to this
}
// etc ...
let compiler: Compiler = { parse, bind, check }

But this prevents users from pulling these functions off of compiler and using them individually:

let tree = compiler.parse("console.log('hello')");
let parse = compiler.parse;
parse("console.log('goodbye')"); // error, this is 'void' which is not assignable to 'Compiler'

This is safe because of course they are free functions that do not require access to this. If you want enable this usage, you need to use the function property declaration syntax for the interface:

interface Compiler {
  parse: (program: string) => Node;
  bind: (tree: Node) => Map<Symbol>;
  check: (tree: Node) => Diagnostic[];
}
function parse(program: string) {
  // code inside doesn't refer to this
}
let compiler: Compiler = { parse, bind, check }
let parse = compiler.parse;
parse("console.log('goodbye')");

How can I implement an OO-style interface with nothing but functions?

If you want to implement an OO-style interface using only functions, you'll need to declare the this type in order to have access to that interface's members inside the function body.

function extract(this: Extractor, input: string): Row[] {
  // code here can refer to members of Extractor
}

Then you can create an object literal that contains that function:

let extractor: Extractor = {
  extract
}

This is essentially the code you would write today, but now usages of this are checked. For even more convenience you can use the contextual typing that comes from writing a function inside an object literal:

let extract: Extractor = {
  function extract(input: string): Row[] {
    // code here can refer to members of Extractor
  }
}

But at this point you might as well write a new class that implements Extractor.

Interfacing with JavaScript

The problem with interfacing to JavaScript is that it may use any or all of the above styles. A good default for writing types for Javascript code is the method declaration style. This makes implementing the interface easier. On the other hand, it prevents users of the interface from pulling functions off of the interface in order to save them as variables or to use them as callbacks.

This is the main reason that DefinitelyTyped definitions can't automatically compiled with --strictThis: it's impossible to predict which style of usage is the desired one for a currently-unmarked interface:

interface WholeClass {
  method(): void; // probably a method?
}
interface Record {
  func(x: string): number; // probably a function?
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

HerringtonDarkholme, zpdDG4gta8XKpMCd, piotrwitek, GoToLoop, aersam, yahiko00, and SlurpTheo reacted with thumbs up emoji chuckd, Gaelan, plantain-00, zpdDG4gta8XKpMCd, HerringtonDarkholme, piotrwitek, GoToLoop, mucaho, niieani, and masaeedu reacted with hooray emoji NicolasBehra reacted with heart emoji
All reactions
  • 👍 7 reactions
  • 🎉 10 reactions
  • ❤️ 1 reaction
Nathan Shively-Sanders (sandersn) added 10 commits January 29, 2016 14:10
@sandersn
Parse this type using parameter syntax
0a968f0
Syntax is the same as a normal parameter:

```ts
function f(this: void, x: number) {
}
```
@sandersn
Check this type in functions.
d8a77c0
If `this` is not provided, it defaults to `void` for functions and `this`
for methods. The rules for checking are similar to parameter checking, but
there's still quite a bit of duplication for this implementation.
@sandersn
Skip emit of this types as first parameter.
a639b71
@sandersn
Add new error message and strictThis flag
9bd7afb
@sandersn
Make compiler strictThis clean.
ca16209
@sandersn
Add services support for this types.
22e571f
@sandersn
Add overloads for Function.apply/call/bind
5fe8478
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
@sandersn
Add tests and baselines for this-function types.
04e7d81
@sandersn
Fix free function bug in cachingInServerLSHost
a4f1154
@sandersn
Update baselines
d030889
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@msftclas Microsoft Contribution License Agreements (msftclas) added the cla-not-required label Jan 29, 2016
@sandersn

Nathan Shively-Sanders (sandersn) commented Feb 1, 2016

Copy link
Copy Markdown
Member Author

Anders Hejlsberg (@ahejlsberg) and Daniel Rosenwasser (@DanielRosenwasser), I believe you were both interested in this.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn Nathan Shively-Sanders (sandersn) mentioned this pull request Feb 1, 2016
bind(), call(), and apply() are untyped #212
Closed
Nathan Shively-Sanders (sandersn) added 4 commits February 2, 2016 14:46
@sandersn
Make this-type of bind's return explicit
675e081
@sandersn
Undo strictThis-clean changes
f6361ce
Also fix other lint.
@sandersn
Merge branch 'master' into this-function-types
8032b06
@sandersn
Update error numbers in new tests after merge
0af56c0
ahejlsberg
ahejlsberg reviewed Feb 3, 2016
View reviewed changes
Comment thread src/compiler/binder.ts Outdated

@ahejlsberg Anders Hejlsberg (ahejlsberg) Feb 3, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So any interface containing a method is now generic. Probably unavoidable, but we should get some data on what it means for performance.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@sandersn Nathan Shively-Sanders (sandersn) Feb 3, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran some numbers this afternoon and didn't see a big change. Could be misreading the results though.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Feb 3, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to recap your findings: On the Monaco project this ends up adding as much as 10% to the check time for an overall impact of up to 5%.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@sandersn

Nathan Shively-Sanders (sandersn) commented Apr 2, 2016

Copy link
Copy Markdown
Member Author

(2) is on the last line of thisTypeInFunctions.ts
I'll add (1).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Gaelan

Gaelan Steele (Gaelan) commented Apr 4, 2016

Copy link
Copy Markdown

Not that #7097 is fixed, can strictThisChecks be readded?

sethbrenith and SlurpTheo reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@DanielRosenwasser

Daniel Rosenwasser (DanielRosenwasser) commented Apr 4, 2016

Copy link
Copy Markdown
Member

Gaelan Steele (@Gaelan) I believe we concluded at our design meeting that we were going to hold off on --strictThis, partially for perf reasons, partially because alongside readonly and non-nullable types, this was going to cause a lot of issues for the community in updating .d.ts files in a compatible manner. Check out #7689.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Gaelan

Gaelan Steele (Gaelan) commented Apr 4, 2016

Copy link
Copy Markdown

Daniel Rosenwasser (@DanielRosenwasser) Ah.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) mentioned this pull request Apr 4, 2016
No referencing/intellisense fo nested properties/functions within class #7801
Closed
@sandersn
Add contextual typing test with this specified
6c735b5
@sandersn

Nathan Shively-Sanders (sandersn) commented Apr 7, 2016

Copy link
Copy Markdown
Member Author

Daniel Rosenwasser (@DanielRosenwasser) I added a contextual typing test as in (2) with an implicit any on a this parameter. Note that the types are not quite right — the this parameter is never contextually typed, just this expressions in the function body. This is because getSignatureFromDeclaration stores thisType eagerly -- not thisSymbol like the rest of the compiler. I discussed this with Anders Hejlsberg (@ahejlsberg) and we decided it was OK for now since it simplifies the code a lot. I might change it later.

The effect is that this:

o.someMethod = function(this, m) { return this.n + m };

has these types:

o.someMethod: (this: O, m: number) => number // source of context
function(this: any, m: number) => number // this: any, but returns number
this: O // inside the body, this is contextually typed
this.n: number
m: number
return this.n +  m // correctly returns number

Note that the thisType: any is not contagious because it's not actually used -- for this.n, checkThisExpression uses the contextual type instead, which is this: O.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn
Nathan Shively-Sanders (sandersn) merged commit 3704ad7 into master Apr 7, 2016
@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
This was referenced Apr 7, 2016
Supporting 'this' type #3694
Closed
Function this types #6018
Closed
Meta-issue: this disambiguator #513
Closed
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@basarat
chore(typescript) update to latest
efdfcfd
refs microsoft/TypeScript#6739
@saschanaz Kagami Sascha Rosylight (saschanaz) mentioned this pull request May 11, 2016
Support specifying type of "this" in function implementations #229
Closed
@hax HE Shi-Jun (hax) mentioned this pull request Jun 15, 2016
Allow this inside the body if no explicit name provided gilbert/es-explicit-this#3
Closed
@JoshuaKGoldberg Josh Ghoulberg 👻 (JoshuaKGoldberg) mentioned this pull request Jun 20, 2016
Add in scope-bound equivalents of addEvent, addEventInterval FullScreenShenanigans/TimeHandlr#6
Open
@olee Björn Zeutzheim (olee) mentioned this pull request Jul 15, 2016
Incorrect errors when adding this parameter to functions palantir/tslint#1393
Closed
@gcnew Marin Marinov (gcnew) mentioned this pull request Aug 11, 2016
Compilation of function indirection leads to runtime error #10267
Closed
@danielearwicker Daniel Earwicker (danielearwicker) mentioned this pull request Aug 18, 2016
this reference in Object #10410
Closed
@thoughtentity Erik Medina (thoughtentity) mentioned this pull request Sep 14, 2016
TypeScript this Type Information Dosen't Surface Properly in Editor microsoft/vscode#12032
Closed
@waderyan Wade Anderson (waderyan) mentioned this pull request Sep 15, 2016
TypeScript this Type Information Dosen't Surface Properly in Editor #10937
Closed
@dsole Dave Sole (dsole) mentioned this pull request Sep 20, 2016
'this' banned term conflicts with Typescript's function this-types microsoft/tslint-microsoft-contrib#261
Closed
@HerringtonDarkholme Herrington Darkholme (HerringtonDarkholme) mentioned this pull request Oct 10, 2016
[Feature Request] contextually type partially annotated function #11475
Closed
@EricTetz Eric Tetz (EricTetz) mentioned this pull request Mar 30, 2017
this in methods not type of enclosing class #14936
Closed
@robotlolita Quil (robotlolita) mentioned this pull request Apr 15, 2017
typescript typings origamitower/folktale#65
Open
@avivcarmis

Aviv Carmi (avivcarmis) commented Sep 14, 2017

Copy link
Copy Markdown

This feature is exactly what I'm after, I see it was merged but i can't find any docs regarding, and i saw here that it was not working well.
Is there any update? Can it be found in some newer version? Will it be added in the future?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn

Nathan Shively-Sanders (sandersn) commented Sep 14, 2017

Copy link
Copy Markdown
Member Author

https://www.typescriptlang.org/docs/handbook/functions.html has the documentation near the bottom of the page.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CNBoland Craig Boland (CNBoland) mentioned this pull request May 22, 2018
Add explicit typing for 'this' references. DefinitelyTyped/DefinitelyTyped#25945
Merged
@mhegazy Mohamed Hegazy (mhegazy) mentioned this pull request Jun 4, 2018
Fixing undefined 'this' in Javascript #24636
Closed
4 tasks done
@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

8 participants

@sandersn @mhegazy @DanielRosenwasser @Gaelan @avivcarmis @Arnavion @ahejlsberg @msftclas
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.