Transcrypt icon indicating copy to clipboard operation
Transcrypt copied to clipboard

Scope transpiled class attributes in a function instead of an object

Open AlexECX opened this issue 5 years ago • 2 comments

Change Summary

Currently, the transpiled attributes of a class are scoped in an object as key/value pairs. This is problematic when trying to handle class scoped operation, for example:

class A:
    print("hello from A")
    A = 1
    B = A + 1

The main objective of this PR is to facilitate the implementation of class scoped operations.

Secondary changes:

  • Add support for class variable operations.
  • Add support for class scoped expressions.
  • Handle 'assignationless' annotated class variable.

Related issue number

Related to #630 and #663.

PR Checklist

  • [ ] Adapted tests
  • [ ] Passes tests
  • [ ] Documented changes
  • [x] Compatibility with __iter__
  • [x] Compatibility with __next__
  • [x] Compatibility with dataclass class decorator
  • [x] Compatibility with user and built-in decorators
  • [x] Compatibility with nested classes

Sample before/after

Python source:

class A:
    print("hello from A")
    z: str
    a: str = ""
    b = 1
    c = b + 1

    def __iter__(self):
        return iter([1])

    def __next__(self):
        return next(iter([1]))

    def func(self, arg):
        return arg

    @decor
    def decorated_func(self, arg):
        return arg

    @staticmethod
    def static_func(arg):
        return arg

    @classmethod
    def classmethod_func(cls, arg):
        return arg

    @property
    def property_func(self):
        return 1

Before:

export var A =  __class__ ('A', [object], {
    __module__: __name__,
    // no print()
    // z: str causes an error at transpile time
    a: '',
    b: 1,
    c: b + 1,  // causes an error at JS runtime
    get __iter__ () {return __get__ (this, function (self) {
        return py_iter ([1]);
    });},
    [Symbol.iterator] () {return this.__iter__ ()},
    get __next__ () {return __get__ (this, function (self) {
        return py_next (py_iter ([1]));
    });},
    next: __jsUsePyNext__,
    get func () {return __get__ (this, function (self, arg) {
	return arg;
    });},
    get decorated_func () {return __get__ (this, decor (function (self, arg) {
	return arg;
    }));},
    get static_func () {return function (arg) {
	return arg;
    };},
    get classmethod_func () {return __getcm__ (this, function (cls, arg) {
	return arg;
    });},
    get _get_property_func () {return __get__ (this, function (self) {
	return 1;
    });}
});
Object.defineProperty (A, 'property_func', property.call (A, A._get_property_func));;

After:

export var A =  __class__ ('A', [object], (() => {
    let cls = {};
    cls.__module__ = __name__;
    print ("hello from A");
    var z = cls.z;
    var a = cls.a = '';
    var b = cls.b = 1;
    var c = cls.c = b + 1;
    __def__(cls, function __iter__() { return __get__ (this, function (self) {
	return py_iter ([1]);
    });});
    cls[Symbol.iterator] = () => cls.__iter__();
    __def__(cls, function __next__() { return __get__ (this, function (self) {
	return py_next (py_iter ([1]));
    });});
    cls.next = __jsUsePyNext__;
    __def__(cls, function func() { return __get__ (this, function (self, arg) {
	return arg;
    });});
    __def__(cls, function decorated_func() { return __get__ (this, decor (function (self, arg) {
	return arg;
    }));});
    __def__(cls, function static_func() { return function (arg) {
	return arg;
    };});
    __def__(cls, function classmethod_func() { return __getcm__ (this, function (cls, arg) {
	return arg;
    });});
    __def__(cls, function _get_property_func() { return __get__ (this, function (self) {
	return 1;
    });});
    return cls;
})());
Object.defineProperty (A, 'property_func', property.call (A, A._get_property_func));;

AlexECX avatar May 14 '20 03:05 AlexECX

It creates major problems like creating unnecessary scope when class attributes will overshadow global variables with the same name. If you have global name a and want to use it inside method, you will access var a in this class function instead.

faerot avatar Nov 15 '22 08:11 faerot

I haven't been able to work on this, and probably won't unless I take a deeper dive into compiler.py.

Given:

class A:
    z: str
    a: str = ""
    b = 1
    c = b + 1

need a way to get

export var A =  __class__ ('A', [object], (() => {
    let cls = {};
    cls.__module__ = __name__;
    cls.z = undefined;
    cls.a = '';
    cls.b = 1;
    cls.c = cls.b + 1;
};

instead of my current

export var A =  __class__ ('A', [object], (() => {
    let cls = {};
    cls.__module__ = __name__;
    var z = cls.z;
    var a = cls.a = '';
    var b = cls.b = 1;
    var c = cls.c = b + 1;
};

the difficult part being to replace cls.c = b + 1 by cls.c = cls.b + 1.

AlexECX avatar Mar 02 '23 20:03 AlexECX