Skip to content

Commit

Permalink
Fix issue #78
Browse files Browse the repository at this point in the history
  • Loading branch information
sccolbert committed Jan 13, 2014
1 parent f5de6cf commit f0d1fc7
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 21 deletions.
3 changes: 2 additions & 1 deletion enaml/core/block_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ def visit_TemplateInst(self, node):
def visit_TemplateInstBinding(self, node):
# Generate the code for the template inst binding.
cg = self.code_generator
cmn.gen_template_inst_binding(cg, node)
index = self.parent_index()
cmn.gen_template_inst_binding(cg, node, index)

def visit_Binding(self, node):
# Generate the code for the operator binding.
Expand Down
14 changes: 10 additions & 4 deletions enaml/core/compiler_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,11 @@ def gen_template_inst_node(cg, node, local_names):
cg.load_const(names)
cg.load_const(starname)
cg.load_fast(SCOPE_KEY)
cg.call_function(4)
cg.load_const(bool(node.body))
cg.call_function(5)


def gen_template_inst_binding(cg, node):
def gen_template_inst_binding(cg, node, index):
""" Generate the code for a template inst binding.
The caller should ensure that UNPACK_MAP and F_GLOBALS are present
Expand All @@ -505,21 +506,25 @@ def gen_template_inst_binding(cg, node):
node : TemplateInstBinding
The enaml ast node of interest.
index : int
The index of the template inst node in the node list.
"""
op_node = node.expr
mode = COMPILE_MODE[type(op_node.value)]
code = compile(op_node.value.ast, cg.filename, mode=mode)
with cg.try_squash_raise():
cg.set_lineno(node.lineno)
load_helper(cg, 'run_operator')
load_node(cg, index)
cg.load_fast(UNPACK_MAP)
cg.load_const(node.name)
cg.binary_subscr()
cg.load_const(node.chain)
cg.load_const(op_node.operator)
cg.load_const(code)
cg.load_fast(F_GLOBALS)
cg.call_function(5)
cg.call_function(6)
cg.pop_top()


Expand Down Expand Up @@ -550,11 +555,12 @@ def gen_operator_binding(cg, node, index, name):
cg.set_lineno(node.lineno)
load_helper(cg, 'run_operator')
load_node(cg, index)
cg.dup_top()
cg.load_const(name)
cg.load_const(node.operator)
cg.load_const(code)
cg.load_fast(F_GLOBALS)
cg.call_function(5)
cg.call_function(6)
cg.pop_top()


Expand Down
50 changes: 35 additions & 15 deletions enaml/core/compiler_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,12 @@ def template_node(scope_key):
return node


def template_inst_node(template_inst, names, starname, scope_key):
def template_inst_node(templ, names, starname, scope_key, copy):
""" Create and return a new template inst node.
Parameters
----------
template_inst : TemplateInst
templ : TemplateInst
The template instantiation object.
names : tuple
Expand All @@ -281,14 +281,19 @@ def template_inst_node(template_inst, names, starname, scope_key):
scope_key : object
The key for the local scope in the local storage maps.
copy : bool
Whether a copy of the underlying template node is required. A
copy will be required when the template instance has bindings
so that the closure keys remain isolated to this instance.
Returns
-------
result : TemplateInstNode
The compiler node for the template instantiation.
"""
node = TemplateInstanceNode()
node.template = template_inst.node
node.template = templ.node.copy() if copy else templ.node
node.names = names
node.starname = starname
node.scope_key = scope_key
Expand Down Expand Up @@ -435,7 +440,7 @@ def bind_aliased_member_impl(name, node, member, pair, scope_key):
node.closure_keys.add(scope_key)


def bind_aliased_member(node, name, alias, pair):
def bind_aliased_member(node, name, alias, pair, scope_key):
""" Bind a handler pair to an aliased member.
Parameters
Expand All @@ -452,15 +457,18 @@ def bind_aliased_member(node, name, alias, pair):
pair : HandlerPair
The handler pair to add to the expression engine.
scope_key : object
The closure scope key for adding to the node's closure keys.
"""
target_node, member = resolve_alias(node, alias)
if target_node is None or member is None:
msg = "alias '%s' does not resolve to a declarative member"
raise TypeError(msg % name)
bind_aliased_member_impl(name, target_node, member, pair, node.scope_key)
bind_aliased_member_impl(name, target_node, member, pair, scope_key)


def bind_extended_member(node, chain, pair):
def bind_extended_member(node, chain, pair, scope_key):
""" Bind a handler pair to an extended member.
Parameters
Expand All @@ -474,6 +482,9 @@ def bind_extended_member(node, chain, pair):
pair : HandlerPair
The handler pair to add to the expression engine.
scope_key : object
The closure scope key for adding to the node's closure keys.
"""
# Resolve everything but the last item in the chain. Everything
# up to that point must be aliases which resolve to an object.
Expand All @@ -485,7 +496,7 @@ def bind_extended_member(node, chain, pair):
if not isinstance(alias, Alias):
raise TypeError("'%s' is not an alias" % '.'.join(seen))
target_node, member = resolve_alias(target_node, alias)
if node is None or member is not None:
if target_node is None or member is not None:
msg = "'%s' does not alias an object"
raise TypeError(msg % '.'.join(seen))

Expand All @@ -503,7 +514,7 @@ def bind_extended_member(node, chain, pair):
raise TypeError(msg % '.'.join(chain))

# Bind the final aliased member.
bind_aliased_member_impl(name, target_node, member, pair, node.scope_key)
bind_aliased_member_impl(name, target_node, member, pair, scope_key)


def bind_member(node, name, pair):
Expand Down Expand Up @@ -535,16 +546,20 @@ def bind_member(node, name, pair):
node.engine.add_pair(name, pair)


def run_operator(node, name, op, code, f_globals):
def run_operator(scope_node, node, name, op, code, f_globals):
""" Run the operator for a given node.
Parameters
----------
scope_node : DeclarativeNode
The node which holds the scope key for the scope in which the
code will execute.
node : DeclarativeNode
The compiler node holding the declarative class.
The compiler node holding the declarative class being bound.
name : str
The name being bound for the class.
name : str or tuple
The name or names being bound for the class.
op : str
The operator string which should be run to create the handlers.
Expand All @@ -559,14 +574,19 @@ def run_operator(node, name, op, code, f_globals):
operators = __get_operators()
if op not in operators:
raise TypeError("failed to load operator '%s'" % op)
pair = operators[op](code, node.scope_key, f_globals)
scope_key = scope_node.scope_key
pair = operators[op](code, scope_key, f_globals)
if isinstance(name, tuple):
bind_extended_member(node, name, pair)
# The template inst binding with a single name will take this
# path by using a length-1 name tuple. See bug #78.
bind_extended_member(node, name, pair, scope_key)
else:
item = getattr(node.klass, name, None)
if isinstance(item, Alias):
bind_aliased_member(node, name, item, pair)
bind_aliased_member(node, name, item, pair, scope_key)
else:
# This is the path for a standard binding on a child def.
# It does not need the closure scope key. See bug #78.
bind_member(node, name, pair)


Expand Down
6 changes: 5 additions & 1 deletion enaml/core/enaml_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@
# 19 : Fix a bug in variadic template args - 20 September 2013
# The code generated for variadic template functions did not set
# the varargs flag on the code object. This is now fixed.
COMPILER_VERSION = 19
# 20 : Fix a bug in template instantiation scoping - 13 January 2014
# The generated code did not properly handle the scope key for
# binding expressions on template instantiations.
# https://github.com/nucleic/enaml/issues/78
COMPILER_VERSION = 20


# Code that will be executed at the top of every enaml module
Expand Down
19 changes: 19 additions & 0 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,25 @@ def test_instantiation_17():
assert Main() is Main()


# regression: https://github.com/nucleic/enaml/issues/78
def test_instantiation_18():
source = dedent("""\
from enaml.widgets.api import *
template Foo():
Field:
placeholder = 'foo'
enamldef Main(Window):
Container:
Foo(): f:
f.text = f.placeholder
""")
main = compile_source(source, 'Main')()
field = main.children[0].children[0]
assert field.text == u'foo'


#------------------------------------------------------------------------------
# Bad Template Instantiation
#------------------------------------------------------------------------------
Expand Down

0 comments on commit f0d1fc7

Please sign in to comment.