This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

This function types - #6739

Merged
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types
Apr 7, 2016
Merged

This function types#6739
Nathan Shively-Sanders (sandersn) merged 46 commits into
masterfrom
this-function-types

Conversation

@sandersn

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

interfaceCallbacks{property: number;onClick(this: void,e: Event): void;}functionhandleClick(this:void,e: Event){console.log(this.property);// error, 'void' has no member 'property'}classC{property: stringtryHandleClick(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'}}letc: C;letcallbacks: Callbacks;callbacks.onClick=handleClick;// OK, this: void for bothcallbacks.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:

classCallbacks{m: number;callback: (this: this,n: number)=>number;}functionf(this: void,n: number){returnn;}letc: Callbacks;c.callback=f;// OK, because f does not refer to any properties of `this`.

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

interfaceCallbacks{property: number;onClick(this: Callbacks,e: Event): void;}classC{property: stringtryHandleClick(this: Callbacks,e: Event): void{console.log(this.property);// OK, 'C' has member 'property'}}letc: C;letcallbacks: 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.

interfaceI{n: number;method(m: number): number;callback: (m: number)=>number;}leto: I={n: 12,method(m){returnthis.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.

interfaceI{n: number;method: (m: number)=>number;}functionfutureMethod(this: I,m: number){returnthis.n+m;}leto: 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.

functionSymbol(this: Symbol,flags: SymbolFlags,name: string){this.flags=flags;this.name=name;this.declarations=undefined;}lets=newSymbol(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:

functionF(x: number,y: number){this.x=x;// this: any, so anything is legalthis.z=y;// missed horrible typo}letf=newF();// also, f: any <-- that's sad :(

Add an annotation to fix this:

interfaceF{x: number;y: number}functionF(this: F,x: number,y: number){this.x=x;this.y=y;}letf=newF(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:

interfaceI{f: (n: number)=>number;// this: voidm(n: number): number;// this: this}functiong(n: number){// this: voidreturnn;}classC{privatespecial: number=12;m(n: number): number{returnn+this.special;}}leti: I;letc: C;i.f=g;// ok, this: voidi.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:

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

You can fix the usage or the interface definition:

// Fix usage:constfix1=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:interfaceObject{method(this: void,n: number): void;}// alternate fix: use function syntax to implicitly set this: voidinterfaceObject{method: (n: number)=>void;}constf=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:

interfaceExtractor{extract(input: string): Row[];// method style}interfaceCallbacks{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:

interfaceExtractor{extract(input: string): Row[];// the default (this: this) makes this the same as writing:// extract(this: this, input: string): Item[];}classXmlExtractorimplementsExtractor{extract(input: string): Row[]{// read from Xml into your Row objects// you can call private methods, etc, like before.}privatehelperMethod(sub: string): Row{// ...}}

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

constxml=newXmlExtractor();otherObject.onCall=xml.extract;// error: types of this are not compatibleletex=xml.extract;// ok ...ex('<row>...</row>');// error, this: void is not compatible with this: XmlExtractorotherObject.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:

interfaceCallbacks{callback: (e: Event)=>void;}classXmlExtractorimplementsExtractor,Callbacks{extract(input: string): Row[]{// here, this: XmlExtractor}callback(e: Event){// here, this: voidthis.extract(e.data);// error! 'this: void' has no method 'extract'}}otherObject.onExtract=xml.extract// errorotherObject.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:

classXmlExtractorimplementsExtractors,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:

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

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

lettree=compiler.parse("console.log('hello')");letparse=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:

interfaceCompiler{parse: (program: string)=>Node;bind: (tree: Node)=>Map<Symbol>;check: (tree: Node)=>Diagnostic[];}functionparse(program: string){// code inside doesn't refer to this}letcompiler: Compiler={ parse, bind, check }letparse=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.

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

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

letextractor: 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:

letextract: Extractor={functionextract(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:

interfaceWholeClass{method(): void;// probably a method?}interfaceRecord{func(x: string): number;// probably a function?}

Syntax is the same as a normal parameter:
```ts
function f(this: void, x: number) {
}
```
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.
The new overloads use this types to specify the return type of these
functions as well as the type of `thisArg`.
1. Display of `this` changes for quick info.
2. The type of Function.call/apply/bind is more precise.
@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Comment threadsrc/compiler/binder.ts Outdated

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
MemberAuthor

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.

Copy link
Copy Markdown
Member

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%.

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

@Gaelan

Copy link
Copy Markdown

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

@DanielRosenwasser

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.

@Gaelan

Copy link
Copy Markdown

@sandersn

Copy link
Copy Markdown
MemberAuthor

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){returnthis.n+m};

has these types:

o.someMethod: (this: O,m: number)=>number// source of contextfunction(this: any,m: number)=>number// this: any, but returns numberthis: O// inside the body, this is contextually typedthis.n: number
m: numberreturnthis.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.

@sandersn
Nathan Shively-Sanders (sandersn) deleted the this-function-types branch April 7, 2016 17:31
Basarat Ali Syed (basarat) added a commit to TypeStrong/atom-typescript that referenced this pull request Apr 8, 2016
@avivcarmis

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?

@sandersn

Copy link
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@sandersn@mhegazy@DanielRosenwasser@Gaelan@avivcarmis@Arnavion@ahejlsberg@msftclas