Description
Suggestion
Allow the this
parameter in property accessors:
const extension = {
get bar(this:ObjectWithFoo):string {
return this.foo;
}
}
π Search Terms
getter setter accessor this argument parameter
β Viability Checklist
My suggestion meets these guidelines:
-
This wouldn't be a breaking change in existing TypeScript/JavaScript code
As with functions, thethis
parameter for accessors is optional and would default to the home object. -
This wouldn't change the runtime behavior of existing JavaScript code
As with most of TypeScript, this proposal has no effect on emitted JavaScript. -
This could be implemented without emitting different JS based on the types of the expressions
As demonstrated below, this proposal technically already emits functioning JavaScript. This proposal only affects type checking (as with functions). -
This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
This proposal only affects type checking during compilation. -
This feature would agree with the rest of TypeScript's Design Goals.
While this feature agrees with all of the design goals, it fits most neatly under goals 5 and 9: "Produce a language that is composable and easy to reason about." and "Use a consistent, fully erasable, structural type system."
β Suggestion
It is currently possible to assign a this
parameter to a method to ensure that it is used correctly. For example:
interface ObjectWithFoo {
foo: "foo";
};
const extensions = {
baz(this:ObjectWithFoo):string {
return this.foo + "baz";
},
};
const target:ObjectWithFoo = {
foo: "foo",
};
const extendedTarget = Object.assign(target, extensions);
extendedTarget.baz(); // "foobaz"
This capability is enormously useful when defining objects that will be used to extend other types.
However, there is a glaring missing feature. While it is possible to add property accessors to the extension object, they do not have access to the this
reference, even though the getter and setter accessors are fundamentally just the get
and set
functions on the property descriptor. This is severely limiting, even though it is fully supported in JavaScript.
const extensions = {
// The following accessor currently produces:
// Error: 'get' and 'set' accessors cannot declare 'this' parameters.(2784)
get bar(this:ObjectWithFoo):string {
return this.foo;
},
baz(this:ObjectWithFoo):string {
return this.foo + "baz";
},
};
π Motivating Example
Consider the following simple extension framework code. It uses getOwnPropertyDescriptor
and defineProperty
, which preserves the original accessor functions (the getter/setter). JavaScript evaluates the this
references in the accessors in the context of the target object, allowing any property getter/setter logic to behave as if the properties were defined on the target from the beginning.
function extend<T, E>(target:T, extension:E): T & E {
// Please note that this function is offered as a reasonable stand in for an extension framework.
// It is not, on its own, a complete solution that handles all edge-cases.
for (const extensionName in extensions) {
const extensionDescriptor = Object.getOwnPropertyDescriptor( extensions, extensionName );
if (!extensionDescriptor) throw Error("Missing property descriptor for: " + extensionName);
Object.defineProperty(target, extensionName, extensionDescriptor);
}
return target as T & E;
}
TypeScript's support for this
on property accessors would allow extension framework authors to express this powerful feature of JavaScript and ensure correctness in their code.
interface ObjectWithFoo {
foo: "foo";
};
const extensions = {
// Proposed:
get bar(this:ObjectWithFoo):string {
return this.foo;
},
baz(this:ObjectWithFoo):string {
return this.foo + "baz";
},
};
const target:ObjectWithFoo = {
foo: "foo",
};
const extendedTarget = extend(target, extensions);
extendedTarget.bar; // "foo"
In addition to the convenience of accessing the intended this
reference, TypeScript could use the same kind of type safety for properties that it currently offers for functions.
// Current:
extensions.baz(); // The 'this' context of type '...' is not assignable to method's 'this' of type '...'
// Proposed:
extensions.bar; // The 'this' context of type '...' is not assignable to accessor's 'this' of type '...'
Checking the intended this
reference for property accessors could be used to help prevent common mistakes, such as the following:
Object.assign(target, extensions);
Here, the engineer intended to attach the bar
property accessor functions but accidentally triggered the evaluation of the property value instead. Instead of assigning a dynamic getter, the target received a new property with a simple, fixed value (or a potential runtime error).
A playground link which demonstrates the proposed code (and actually runs, despite the TS error!)
π» Use Cases
The use cases for the this
parameter in property accessors includes all the use cases for the this
parameter in functions, including:
- Extension frameworks
- Mixins
- Class and property decorators
...and any other code where a property accessor defined on one object will ultimately be used by another.