Implements the proposal at #6018 and finishes the work described at #3694. See the bottom of this description for a tutorial and usage recommendations for this new feature.
This change adds checking for this types in functions. With this-function types, you can prevent the use of methods as free functions. And this types allow you to make constructor functions that return a real type rather than any. A number of other common javascript patterns can be given types or even inferred with --strictThis turned on.
Correctly prevent references to this when assigning callbacks
interface Callbacks {
property: number;
onClick(this: void, e: Event): void;
}
function handleClick(this:void, e: Event) {
console.log(this.property); // error, 'void' has no member 'property'
}
class C {
property: string
tryHandleClick(this: this, e: Event): void {
console.log(this.property); // OK, 'C' has member 'property'
}
actuallyHandleClick(this: void, e: Event): void {
console.log(this.property); // error, 'void' has no member 'property'
}
}
let c: C;
let callbacks: Callbacks;
callbacks.onClick = handleClick; // OK, this: void for both
callbacks.onClick = c.tryHandleClick; // Error, 'C' is not assignable to 'void'
callbacks.onClick = c.actuallyHandleClick; // OK, this: void for both
Note that callback functions are prevented from referring to members of this because they declare that this: void in order to be assignable to onClick.
Also note that the less common case of callback methods still allows functions to be assigned to these properties:
class Callbacks {
m: number;
callback: (this: this, n: number) => number;
}
function f(this: void, n: number) {
return n;
}
let c: Callbacks;
c.callback = f; // OK, because f does not refer to any properties of `this`.
You can even require a different this for methods that will be used as a callback:
interface Callbacks {
property: number;
onClick(this: Callbacks, e: Event): void;
}
class C {
property: string
tryHandleClick(this: Callbacks, e: Event): void {
console.log(this.property); // OK, 'C' has member 'property'
}
}
let c: C;
let callbacks: Callbacks;
callbacks.onClick = c.tryHandleClick;
Contextual typing of methods and functions in object literals
Now when an object literal declares that it is of some type, this is also contextually typed inside the object literal.
interface I {
n: number;
method(m: number): number;
callback: (m: number) => number;
}
let o: I = {
n: 12,
method(m) {
return this.n + m; // OK, `this: I` from context
},
callback = m => m + 1, // OK, `this: void` from context
}
Build object literals from existing functions
You can also define functions alone and then later build an object literal from them. If the functions specify the this type, then the compiler will check references to this in the function's body. And it will also check that the function's this is assignable to the this of the object literal's type.
interface I {
n: number;
method: (m: number) => number;
}
function futureMethod(this: I, m: number) {
return this.n + m;
}
let o: I = { n: 12, method: futureMethod };
Defining a function this way also requires that it cannot be called free:
futureMethod(12); // error, 'void' is not assignable to 'I'
o.futureMethod(12); // ok, 'o' is of type 'I'
The type of functions when used as constructors is now known.
When you use new with a function, you can now declare the type of the object that will be constructed with this. Previously, the type was always any. The compiler will also check that your assignments to this are correct in the body of the function.
function Symbol(this: Symbol, flags: SymbolFlags, name: string) {
this.flags = flags;
this.name = name;
this.declarations = undefined;
}
let s = new Symbol(flags, "core"); // s: Symbol
strictThisChecks flag default types
UPDATE: --strictThisChecks removed
Because of performance concerns and lack of information on how people will use this types, we decided to remove --strictThisChecks for this release. I left the original explanation below.
--noImplicitThis
--noImplicitThis makes it an error to use this that is implicitly any inside of a function:
function F(x: number, y: number) {
this.x = x; // this: any, so anything is legal
this.z = y; // missed horrible typo
}
let f = new F(); // also, f: any <-- that's sad :(
Add an annotation to fix this:
interface F {
x: number;
y: number
}
function F(this: F, x: number, y: number) {
this.x = x;
this.y = y;
}
let f = new F(12, 13); // f: F <-- hooray! :)
Previous explanation of strictThisChecks
For backward compatibility, this types default to any if not specified. However, if --strictThis is specified, then functions will default this: void and methods will default this: this. This removes the need for most of the annotations in the previous examples. For example, an interface can declare a function with this: void by using function syntax and a method with this: this by using method syntax:
interface I {
f: (n: number) => number; // this: void
m(n: number): number; // this: this
}
function g(n: number) { // this: void
return n;
}
class C {
private special: number = 12;
m(n: number): number { return n + this.special; }
}
let i: I;
let c: C;
i.f = g; // ok, this: void
i.f = c.m; // error, 'void' is not assignable to 'C' (missing member 'm')
i.m = c.m; // error, 'I' is not assignable to 'C' (missing member 'special')
This breaks a lot of existing code, but is easy to write for future code.
How to upgrade code to --strictThis
When you switch on --strictThis you'll see a lot of errors with calling a method as if it were a function:
interface Object {
method(n: number): void;
}
const f = object.method;
f(12); // ERROR, this should be 'Object' not 'void'
You can fix the usage or the interface definition:
// Fix usage:
const fix1 = n => object.method(n);
correct(12); // OK, lambda captures the object and doesn't require 'this'
f.call(object, 12); // alternate fix: use Function.call
/// OR ///
// Fix definition:
interface Object {
method(this: void, n: number): void;
}
// alternate fix: use function syntax to implicitly set this: void
interface Object {
method: (n: number) => void;
}
const f = object.method;
f(12); // OK, this is void
Designing new, strict-this code
To be able to switch --strictThis on, the style that you write interfaces needs to change. You need to consider how people will use your interfaces. If they will treat your interface's functions as methods on an object, you should use the normal method declaration syntax. If they will treat them as a callback or some other kind of free function, you should declare them using the function property syntax. For example:
interface Extractor {
extract(input: string): Row[]; // method style
}
interface Callbacks {
callback: (e: Event) => void; // function property style
}
A safe default is to use the method declaration syntax. I discuss the tradeoffs below.
OO programming style
If you are writing pure OO Typescript/ES6, then you don't need to change much to work with --strictThis.
- OO interfaces should have
this: this
- The only exception should be callbacks, which should have
this: void.
Fortunately, these are the default types for the method and function syntax, respectively, so you don't have to write anything much different:
interface Extractor {
extract(input: string): Row[];
// the default (this: this) makes this the same as writing:
// extract(this: this, input: string): Item[];
}
class XmlExtractor implements Extractor {
extract(input: string): Row[] {
// read from Xml into your Row objects
// you can call private methods, etc, like before.
}
private helperMethod(sub: string): Row {
// ...
}
}
Now you can't assign extract to a function by mistake:
const xml = new XmlExtractor();
otherObject.onCall = xml.extract; // error: types of this are not compatible
let ex = xml.extract; // ok ...
ex('<row>...</row>'); // error, this: void is not compatible with this: XmlExtractor
otherObject.onExtract = ex; // error this: void is not compatible with this: XmlExtractor
And if you implement an interface, your methods get the right this-type regardless of what syntax you use:
interface Callbacks {
callback: (e: Event) => void;
}
class XmlExtractor implements Extractor, Callbacks {
extract(input: string): Row[] {
// here, this: XmlExtractor
}
callback(e: Event) {
// here, this: void
this.extract(e.data); // error! 'this: void' has no method 'extract'
}
}
otherObject.onExtract = xml.extract // error
otherObject.onExtract = xml.callback // ok!
How can I use a method as a callback?
You may have noticed that XmlExtractor.callback isn't that useful as a method since you can't actually refer to any other methods. The solution is the same as you use today: wrap the method call inside a lambda:
class XmlExtractor implements Extractors, Callbacks {
extract(input: string): Row[] { ... }
callback = e => this.extract(e.data); // ok, => doesn't capture 'this'
}
This formulation is OK because lambda (=>) doesn't bind this, so this comes from the class instead of from the implementing function.
Functional style
If you are writing your code in functional style, you probably use interfaces to describe records of functions. You can still declare the functions using the method style and build instances of the interface using an object literal of your functions:
interface Compiler {
parse(program: string): Node;
bind(tree: Node): Map<Symbol>;
check(tree: Node): Diagnostic[];
}
function parse(program: string) {
// code inside does not refer to this
}
// etc ...
let compiler: Compiler = { parse, bind, check }
But this prevents users from pulling these functions off of compiler and using them individually:
let tree = compiler.parse("console.log('hello')");
let parse = compiler.parse;
parse("console.log('goodbye')"); // error, this is 'void' which is not assignable to 'Compiler'
This is safe because of course they are free functions that do not require access to this. If you want enable this usage, you need to use the function property declaration syntax for the interface:
interface Compiler {
parse: (program: string) => Node;
bind: (tree: Node) => Map<Symbol>;
check: (tree: Node) => Diagnostic[];
}
function parse(program: string) {
// code inside doesn't refer to this
}
let compiler: Compiler = { parse, bind, check }
let parse = compiler.parse;
parse("console.log('goodbye')");
How can I implement an OO-style interface with nothing but functions?
If you want to implement an OO-style interface using only functions, you'll need to declare the this type in order to have access to that interface's members inside the function body.
function extract(this: Extractor, input: string): Row[] {
// code here can refer to members of Extractor
}
Then you can create an object literal that contains that function:
let extractor: Extractor = {
extract
}
This is essentially the code you would write today, but now usages of this are checked. For even more convenience you can use the contextual typing that comes from writing a function inside an object literal:
let extract: Extractor = {
function extract(input: string): Row[] {
// code here can refer to members of Extractor
}
}
But at this point you might as well write a new class that implements Extractor.
Interfacing with JavaScript
The problem with interfacing to JavaScript is that it may use any or all of the above styles. A good default for writing types for Javascript code is the method declaration style. This makes implementing the interface easier. On the other hand, it prevents users of the interface from pulling functions off of the interface in order to save them as variables or to use them as callbacks.
This is the main reason that DefinitelyTyped definitions can't automatically compiled with --strictThis: it's impossible to predict which style of usage is the desired one for a currently-unmarked interface:
interface WholeClass {
method(): void; // probably a method?
}
interface Record {
func(x: string): number; // probably a function?
}