TranscryptOrg / Transcrypt

Python 3.9 to JavaScript compiler - Lean, fast, open!
https://www.transcrypt.org
Apache License 2.0
2.82k stars 215 forks source link

Descriptor Protocol #825

Open nigglefish opened 2 years ago

nigglefish commented 2 years ago

This is a little bit of a patch (hack) I apply to allow the use of the descriptor protocol (or, most of it). Descriptors are really useful for reactive UI design, and easy to implement in a way that shares sensibilities with Transcrypt's implementation of properties. The readme suggested I start by raising the idea as an issue, so I didn't bother reworking it as a pull request that modifies the code-base directly. Hope this is interesting and doesn't wreck anything fundemental.

"""
Add the descriptor protocol to `py_metatype`.
Limitations:
  - `__delete__` is not supported.
  - Assigning a new value to an attribute that is a set operation on a class
    will not replace the descriptor (much like Transcrypt's `@property`).
  - Cannot access descriptor instance using `vars` (`vars` is not supported).
  - Missing `__get__` or `__set__` are noop (much like Transcrypt's
    `@property`).
"""
__pragma__('js', '{}', '''
(function(undefined) {

    function _get_function(descriptor) {
        return function() {
            if(this.hasOwnProperty('__class__')) {
                return descriptor.__get__(this, py_typeof(this))
            } else {
                return descriptor.__get__(null, this)
            }
        };
    }

    function _set_function(descriptor) {
        return function(value) {
            if(this.hasOwnProperty('__class__')) {
                descriptor.__set__(this, value);
            }
        };
    }

    const _py_metatype_new = py_metatype.__new__;

    py_metatype.__new__ = function (meta, name, bases, attributes) {
        const new_type = _py_metatype_new(meta, name, bases, attributes);
        for(const attribute in attributes) {
            const descriptor = new_type[attribute];
            if(
                descriptor &&
                descriptor.hasOwnProperty('__class__') &&
                (descriptor.__get__ || descriptor.__set__)
            ) {
                const data = { enumerable: true };
                if(descriptor.__get__) {
                    data.get = _get_function(descriptor);
                }
                if(descriptor.__set__) {
                    data.set = _set_function(descriptor);
                }
                Object.defineProperty(new_type, attribute, data);
                if(descriptor.__set_name__) {
                    descriptor.__set_name__(new_type, attribute);
                }
            }
        }
        return new_type;
    };

})();
''')