Skip to content

Commit 122515d

Browse files
committed
Scope transpiled class attributes in a function instead of an object
1 parent 5cf5324 commit 122515d

File tree

2 files changed

+58
-28
lines changed

2 files changed

+58
-28
lines changed

transcrypt/modules/org/transcrypt/__core__.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ export function __init__ (module) {
106106
return module.__all__;
107107
};
108108

109+
// Binds a function to the target class object
110+
export var __def__ = function (cls, method, name) {
111+
let method_name = (name ? name : method.name);
112+
Object.defineProperty (cls, method_name, {
113+
get: method, set: (func) => __def__ (this, func, method_name),
114+
configurable: true, enumerable: true
115+
});
116+
};
117+
109118
// Since we want to assign functions, a = b.f should make b.f produce a bound function
110119
// So __get__ should be called by a property rather then a function
111120
// Factory __get__ creates one of three curried functions for func

transcrypt/modules/org/transcrypt/compiler.py

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,10 @@ def getAdjacentClassScopes (self, inMethod = False):
792792
reversedClassScopes.append (scope)
793793
return reversed (reversedClassScopes)
794794

795+
def emitSemiColon (self, index, blank = True):
796+
if self.noskipCodeGeneration and self.conditionalCodeGeneration and index:
797+
self.emit ('; ' if blank else ';')
798+
795799
def emitComma (self, index, blank = True):
796800
if self.noskipCodeGeneration and self.conditionalCodeGeneration and index:
797801
self.emit (', ' if blank else ',')
@@ -1914,7 +1918,7 @@ def visit_ClassDef (self, node):
19141918
self.emit ('export var {} = '.format (self.filterId (node.name)))
19151919
self.allOwnNames.add (node.name)
19161920
elif type (self.getScope () .node) == ast.ClassDef:
1917-
self.emit ('\n{}:'.format (self.filterId (node.name)))
1921+
self.emit ('\nvar {0} = cls.{0} = '.format (self.filterId (node.name)))
19181922
else:
19191923
self.emit ('var {} ='.format (self.filterId (node.name)))
19201924

@@ -1963,11 +1967,12 @@ def visit_ClassDef (self, node):
19631967
)
19641968
else:
19651969
self.emit ('object')
1966-
self.emit ('], {{')
1970+
self.emit ('], (() => {{') # class scope start
19671971
self.inscope (node)
19681972

19691973
self.indent ()
1970-
self.emit ('\n__module__: __name__,')
1974+
self.emit('\nlet cls = {{}};')
1975+
self.emit ('\ncls.__module__ = __name__;')
19711976

19721977
# LHS plays a role in a.o. __repr__ in a dataclass
19731978
inlineAssigns = [] # LHS is simple name, class var assignment generates initialisation of field in object literal
@@ -1994,7 +1999,7 @@ def visit_ClassDef (self, node):
19941999
if self.isCommentString (statement):
19952000
pass
19962001
elif type (statement) in (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef):
1997-
self.emitComma (index, False)
2002+
self.emitSemiColon (index, False)
19982003
self.visit (statement)
19992004
index += 1
20002005

@@ -2007,8 +2012,8 @@ def visit_ClassDef (self, node):
20072012
else:
20082013
# Simple class var assignment, can be generated in-line as initialisation field of a JavaScript object literal
20092014
inlineAssigns.append (statement)
2010-
self.emitComma (index, False)
2011-
self.emit ('\n{}: ', self.filterId (statement.targets [0] .id))
2015+
self.emitSemiColon (index, False)
2016+
self.emit ('\nvar {0} = cls.{0} = ', self.filterId (statement.targets [0] .id))
20122017
self.visit (statement.value)
20132018
self.adaptLineNrString (statement)
20142019
index += 1
@@ -2032,18 +2037,24 @@ def visit_ClassDef (self, node):
20322037
initAssigns.append (statement)
20332038
reprAssigns.append (statement)
20342039
compareAssigns.append (statement)
2035-
self.emitComma (index, False)
2036-
self.emit ('\n{}: ', self.filterId (statement.target.id))
2037-
self.visit (statement.value)
2040+
self.emitSemiColon (index, False)
2041+
if statement.value is None:
2042+
self.emit('\nvar {0} = cls.{0}', self.filterId(statement.target.id))
2043+
else:
2044+
self.emit('\nvar {0} = cls.{0} = ', self.filterId(statement.target.id))
2045+
self.visit(statement.value)
20382046
self.adaptLineNrString (statement)
20392047
index += 1
20402048
elif type (statement.target) == ast.Name:
20412049
try:
20422050
# Simple class var assignment
20432051
inlineAssigns.append (statement)
2044-
self.emitComma (index, False)
2045-
self.emit ('\n{}: ', self.filterId (statement.target.id))
2046-
self.visit (statement.value)
2052+
self.emitSemiColon (index, False)
2053+
if statement.value is None:
2054+
self.emit('\nvar {0} = cls.{0}', self.filterId(statement.target.id))
2055+
else:
2056+
self.emit('\nvar {0} = cls.{0} = ', self.filterId(statement.target.id))
2057+
self.visit(statement.value)
20472058
self.adaptLineNrString (statement)
20482059
index += 1
20492060
except:
@@ -2052,12 +2063,21 @@ def visit_ClassDef (self, node):
20522063
# LHS is attribute or array element, we can't use it for representation or comparison
20532064
delayedAssigns.append (statement)
20542065

2055-
elif self.getPragmaFromExpr (statement):
2056-
# It's a pragma
2057-
self.visit (statement)
2066+
elif type (statement) == ast.Expr:
2067+
if self.getPragmaFromExpr (statement):
2068+
# It's a pragma
2069+
self.visit (statement)
2070+
else:
2071+
# It's a class scoped expression
2072+
self.emitSemiColon (index, False)
2073+
self.emit ('\n')
2074+
self.visit (statement)
2075+
2076+
self.emitSemiColon(index, False)
2077+
self.emit('\nreturn cls;')
20582078
self.dedent ()
20592079

2060-
self.emit ('\n}}')
2080+
self.emit ('\n}})()') # class scope end
20612081

20622082
if node.keywords:
20632083
if node.keywords [0] .arg == 'metaclass':
@@ -2162,7 +2182,7 @@ def visit_ClassDef (self, node):
21622182
returns = None,
21632183
docstring = None
21642184
))
2165-
self.emit (',')
2185+
self.emit (';')
21662186
self.allowKeywordArgs = originalAllowKeywordArgs
21672187

21682188
# Generate __repr__
@@ -2210,7 +2230,7 @@ def visit_ClassDef (self, node):
22102230
returns = None,
22112231
docstring = None
22122232
))
2213-
self.emit (',')
2233+
self.emit (';')
22142234

22152235
# Generate comparators !!! TODO: Add check that self and other are of same class
22162236
comparatorNames = []
@@ -2266,7 +2286,7 @@ def visit_ClassDef (self, node):
22662286
decorator_list = []
22672287
))
22682288
returns = None,
2269-
self.emit (',')
2289+
self.emit (';')
22702290

22712291
# After inserting at init hoist location, jump forward as much as we jumped back
22722292
# Simply going back to the original fragment index won't work, since fragments were prepended
@@ -2675,9 +2695,9 @@ def pushPropertyAccessor(functionName):
26752695
message='\n\tdecorators are not supported with jscall\n'
26762696
)
26772697

2678-
self.emit ('{}: ', self.filterId (nodeName))
2698+
self.emit ('{} = ', self.filterId (nodeName))
26792699
else:
2680-
self.emit ('get {} () {{return {} (this, ', self.filterId (nodeName), getter)
2700+
self.emit('__def__(cls, function {}() {{ return {} (this, ', self.filterId (nodeName), getter)
26812701
elif isGlobal:
26822702
if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames:
26832703
self.emit ('export ')
@@ -2705,12 +2725,12 @@ def pushPropertyAccessor(functionName):
27052725
else:
27062726
if isMethod:
27072727
if jsCall:
2708-
self.emit ('{}: function', self.filterId (nodeName), 'async ' if anAsync else '')
2728+
self.emit ('{} = function', self.filterId (nodeName), 'async ' if anAsync else '')
27092729
else:
27102730
if isStaticMethod:
2711-
self.emit ('get {} () {{return {}function', self.filterId (nodeName), 'async ' if anAsync else '')
2731+
self.emit ('__def__(cls, function {}() {{ return {}function', self.filterId (nodeName), 'async ' if anAsync else '')
27122732
else:
2713-
self.emit ('get {} () {{return {} (this, {}function', self.filterId (nodeName), getter, 'async ' if anAsync else '')
2733+
self.emit ('__def__(cls, function {}() {{ return {} (this, {}function', self.filterId (nodeName), getter, 'async ' if anAsync else '')
27142734
elif isGlobal:
27152735
if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames:
27162736
self.emit ('export ')
@@ -2759,18 +2779,19 @@ def pushPropertyAccessor(functionName):
27592779
if isMethod:
27602780
if not jsCall:
27612781
if isStaticMethod:
2762-
self.emit (';}}')
2782+
self.emit (';}})')
27632783
else:
27642784
if self.allowMemoizeCalls:
27652785
self.emit (', \'{}\'', nodeName) # Name will be used as attribute name to add bound function to instance
27662786

2767-
self.emit (');}}')
2787+
self.emit (');}})')
27682788

27692789
if nodeName == '__iter__':
2770-
self.emit (',\n[Symbol.iterator] () {{return this.__iter__ ()}}')
2790+
self.emit (';\ncls[Symbol.iterator] = () => cls.__iter__()')
27712791

27722792
if nodeName == '__next__':
2773-
self.emit (',\nnext: __jsUsePyNext__') # ??? Shouldn't this be a property, to allow bound method pointers
2793+
self.emit (';\ncls.next = __jsUsePyNext__') # ??? Shouldn't this be a property, to allow bound
2794+
# method pointers
27742795

27752796
if isGlobal:
27762797
self.allOwnNames.add (nodeName)

0 commit comments

Comments
 (0)