Skip to content

Scope transpiled class attributes in a function instead of an object #725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev_fall_2019
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions transcrypt/modules/org/transcrypt/__core__.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ export function __init__ (module) {
return module.__all__;
};

// Binds a function to the target class object
export var __def__ = function (cls, method, name) {
let method_name = (name ? name : method.name);
Object.defineProperty (cls, method_name, {
get: method, set: (func) => __def__ (this, func, method_name),
configurable: true, enumerable: true
});
};

// Since we want to assign functions, a = b.f should make b.f produce a bound function
// So __get__ should be called by a property rather then a function
// Factory __get__ creates one of three curried functions for func
Expand Down
77 changes: 49 additions & 28 deletions transcrypt/modules/org/transcrypt/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ def getAdjacentClassScopes (self, inMethod = False):
reversedClassScopes.append (scope)
return reversed (reversedClassScopes)

def emitSemiColon (self, index, blank = True):
if self.noskipCodeGeneration and self.conditionalCodeGeneration and index:
self.emit ('; ' if blank else ';')

def emitComma (self, index, blank = True):
if self.noskipCodeGeneration and self.conditionalCodeGeneration and index:
self.emit (', ' if blank else ',')
Expand Down Expand Up @@ -1914,7 +1918,7 @@ def visit_ClassDef (self, node):
self.emit ('export var {} = '.format (self.filterId (node.name)))
self.allOwnNames.add (node.name)
elif type (self.getScope () .node) == ast.ClassDef:
self.emit ('\n{}:'.format (self.filterId (node.name)))
self.emit ('\nlet {0} = cls.{0} = '.format (self.filterId (node.name)))
else:
self.emit ('var {} ='.format (self.filterId (node.name)))

Expand Down Expand Up @@ -1963,11 +1967,12 @@ def visit_ClassDef (self, node):
)
else:
self.emit ('object')
self.emit ('], {{')
self.emit ('], (() => {{') # class scope start
self.inscope (node)

self.indent ()
self.emit ('\n__module__: __name__,')
self.emit('\nlet cls = {{}};')
self.emit ('\ncls.__module__ = __name__;')

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

Expand All @@ -2007,8 +2012,8 @@ def visit_ClassDef (self, node):
else:
# Simple class var assignment, can be generated in-line as initialisation field of a JavaScript object literal
inlineAssigns.append (statement)
self.emitComma (index, False)
self.emit ('\n{}: ', self.filterId (statement.targets [0] .id))
self.emitSemiColon (index, False)
self.emit ('\nlet {0} = cls.{0} = ', self.filterId (statement.targets [0] .id))
self.visit (statement.value)
self.adaptLineNrString (statement)
index += 1
Expand All @@ -2032,18 +2037,24 @@ def visit_ClassDef (self, node):
initAssigns.append (statement)
reprAssigns.append (statement)
compareAssigns.append (statement)
self.emitComma (index, False)
self.emit ('\n{}: ', self.filterId (statement.target.id))
self.visit (statement.value)
self.emitSemiColon (index, False)
if statement.value is None:
self.emit('\nlet {0} = cls.{0}', self.filterId(statement.target.id))
else:
self.emit('\nlet {0} = cls.{0} = ', self.filterId(statement.target.id))
self.visit(statement.value)
self.adaptLineNrString (statement)
index += 1
elif type (statement.target) == ast.Name:
try:
# Simple class var assignment
inlineAssigns.append (statement)
self.emitComma (index, False)
self.emit ('\n{}: ', self.filterId (statement.target.id))
self.visit (statement.value)
self.emitSemiColon (index, False)
if statement.value is None:
self.emit('\nlet {0} = cls.{0}', self.filterId(statement.target.id))
else:
self.emit('\nlet {0} = cls.{0} = ', self.filterId(statement.target.id))
self.visit(statement.value)
self.adaptLineNrString (statement)
index += 1
except:
Expand All @@ -2052,12 +2063,21 @@ def visit_ClassDef (self, node):
# LHS is attribute or array element, we can't use it for representation or comparison
delayedAssigns.append (statement)

elif self.getPragmaFromExpr (statement):
# It's a pragma
self.visit (statement)
elif type (statement) == ast.Expr:
if self.getPragmaFromExpr (statement):
# It's a pragma
self.visit (statement)
else:
# It's a class scoped expression
self.emitSemiColon (index, False)
self.emit ('\n')
self.visit (statement)

self.emitSemiColon(index, False)
self.emit('\nreturn cls;')
self.dedent ()

self.emit ('\n}}')
self.emit ('\n}})()') # class scope end

if node.keywords:
if node.keywords [0] .arg == 'metaclass':
Expand Down Expand Up @@ -2162,7 +2182,7 @@ def visit_ClassDef (self, node):
returns = None,
docstring = None
))
self.emit (',')
self.emit (';')
self.allowKeywordArgs = originalAllowKeywordArgs

# Generate __repr__
Expand Down Expand Up @@ -2210,7 +2230,7 @@ def visit_ClassDef (self, node):
returns = None,
docstring = None
))
self.emit (',')
self.emit (';')

# Generate comparators !!! TODO: Add check that self and other are of same class
comparatorNames = []
Expand Down Expand Up @@ -2266,7 +2286,7 @@ def visit_ClassDef (self, node):
decorator_list = []
))
returns = None,
self.emit (',')
self.emit (';')

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

self.emit ('{}: ', self.filterId (nodeName))
self.emit ('{} = ', self.filterId (nodeName))
else:
self.emit ('get {} () {{return {} (this, ', self.filterId (nodeName), getter)
self.emit('__def__(cls, function {}() {{ return {} (this, ', self.filterId (nodeName), getter)
elif isGlobal:
if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames:
self.emit ('export ')
Expand Down Expand Up @@ -2705,12 +2725,12 @@ def pushPropertyAccessor(functionName):
else:
if isMethod:
if jsCall:
self.emit ('{}: function', self.filterId (nodeName), 'async ' if anAsync else '')
self.emit ('{} = function', self.filterId (nodeName), 'async ' if anAsync else '')
else:
if isStaticMethod:
self.emit ('get {} () {{return {}function', self.filterId (nodeName), 'async ' if anAsync else '')
self.emit ('__def__(cls, function {}() {{ return {}function', self.filterId (nodeName), 'async ' if anAsync else '')
else:
self.emit ('get {} () {{return {} (this, {}function', self.filterId (nodeName), getter, 'async ' if anAsync else '')
self.emit ('__def__(cls, function {}() {{ return {} (this, {}function', self.filterId (nodeName), getter, 'async ' if anAsync else '')
elif isGlobal:
if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames:
self.emit ('export ')
Expand Down Expand Up @@ -2759,18 +2779,19 @@ def pushPropertyAccessor(functionName):
if isMethod:
if not jsCall:
if isStaticMethod:
self.emit (';}}')
self.emit (';}})')
else:
if self.allowMemoizeCalls:
self.emit (', \'{}\'', nodeName) # Name will be used as attribute name to add bound function to instance

self.emit (');}}')
self.emit (');}})')

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

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

if isGlobal:
self.allOwnNames.add (nodeName)
Expand Down