Skip to content

Allow this parameter in property accessors (getter/setter)Β #52923

Open
@jdatskuid

Description

@jdatskuid

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, the this 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions