Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
Fix critical bug in compiler handling of local deletes and name shado…
Browse files Browse the repository at this point in the history
…wing
  • Loading branch information
sccolbert committed Dec 10, 2012
1 parent d10b0da commit dfb6f64
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 36 deletions.
15 changes: 11 additions & 4 deletions enaml/core/enaml_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from .byteplay import (
Code, LOAD_FAST, CALL_FUNCTION, LOAD_GLOBAL, STORE_FAST, LOAD_CONST,
LOAD_ATTR, STORE_SUBSCR, RETURN_VALUE, POP_TOP, MAKE_FUNCTION, STORE_NAME,
LOAD_NAME, DUP_TOP, SetLineno, BINARY_SUBSCR, STORE_ATTR, ROT_TWO
LOAD_NAME, DUP_TOP, SetLineno, BINARY_SUBSCR, STORE_ATTR, ROT_TWO,
DELETE_NAME, DELETE_FAST
)
from .code_tracing import inject_tracing, inject_inversion

Expand Down Expand Up @@ -59,7 +60,10 @@
# upfront, instead of needed to specialize at runtime for a given
# operator context. This results in a much smaller footprint since
# then number of code objects created is n instead of n x m.
COMPILER_VERSION = 6
# 7 : Fix bug with local deletes - 10 December 2012
# This fixes a bug in the locals optimization where the DELETE_NAME
# opcode was not being replaced with DELETE_FAST.
COMPILER_VERSION = 7


# The Enaml compiler translates an Enaml AST into Python bytecode.
Expand Down Expand Up @@ -157,8 +161,9 @@ def optimize_locals(codelist):
""" Optimize the given code object for fast locals access.
All STORE_NAME opcodes will be replaced with STORE_FAST. Names which
are stored and then loaded via LOAD_NAME are rewritten to LOAD_FAST.
This transformation is applied in-place.
are stored and then loaded via LOAD_NAME are rewritten to LOAD_FAST
and DELETE_NAME is rewritten to DELETE_FAST. This transformation is
applied in-place.
Parameters
----------
Expand All @@ -174,6 +179,8 @@ def optimize_locals(codelist):
for idx, (op, op_arg) in enumerate(codelist):
if op == LOAD_NAME and op_arg in fast_locals:
codelist[idx] = (LOAD_FAST, op_arg)
elif op == DELETE_NAME and op_arg in fast_locals:
codelist[idx] = (DELETE_FAST, op_arg)


def compile_simple(py_ast, filename):
Expand Down
64 changes: 32 additions & 32 deletions enaml/core/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ class SimpleExpression(BaseExpression):
#--------------------------------------------------------------------------
# AbstractExpression Interface
#--------------------------------------------------------------------------
def eval(self, obj, name):
def eval(self, owner, name):
""" Evaluate and return the expression value.
"""
overrides = {'nonlocals': Nonlocals(obj, None)}
scope = DynamicScope(obj, self._f_locals, overrides, None)
with obj.operators:
overrides = {'nonlocals': Nonlocals(owner, None)}
scope = DynamicScope(owner, self._f_locals, overrides, None)
with owner.operators:
return call_func(self._func, (), {}, scope)


Expand All @@ -252,16 +252,16 @@ class NotificationExpression(BaseExpression):
#--------------------------------------------------------------------------
# AbstractListener Interface
#--------------------------------------------------------------------------
def value_changed(self, obj, name, old, new):
""" Called when the attribute on the object has changed.
def value_changed(self, owner, name, old, new):
""" Called when the attribute on the owner has changed.
"""
overrides = {
'event': NotificationEvent(obj, name, old, new),
'nonlocals': Nonlocals(obj, None),
'event': NotificationEvent(owner, name, old, new),
'nonlocals': Nonlocals(owner, None),
}
scope = DynamicScope(obj, self._f_locals, overrides, None)
with obj.operators:
scope = DynamicScope(owner, self._f_locals, overrides, None)
with owner.operators:
call_func(self._func, (), {}, scope)


Expand All @@ -280,15 +280,15 @@ class UpdateExpression(BaseExpression):
#--------------------------------------------------------------------------
# AbstractListener Interface
#--------------------------------------------------------------------------
def value_changed(self, obj, name, old, new):
""" Called when the attribute on the object has changed.
def value_changed(self, owner, name, old, new):
""" Called when the attribute on the owner has changed.
"""
nonlocals = Nonlocals(obj, None)
nonlocals = Nonlocals(owner, None)
overrides = {'nonlocals': nonlocals}
inverter = StandardInverter(nonlocals)
scope = DynamicScope(obj, self._f_locals, overrides, None)
with obj.operators:
scope = DynamicScope(owner, self._f_locals, overrides, None)
with owner.operators:
call_func(self._func, (inverter, new), {}, scope)


Expand All @@ -302,14 +302,14 @@ class SubscriptionNotifier(object):
""" A simple object used for attaching notification handlers.
"""
__slots__ = ('obj', 'name', 'keyval', '__weakref__')
__slots__ = ('owner', 'name', 'keyval', '__weakref__')

def __init__(self, obj, name, keyval):
def __init__(self, owner, name, keyval):
""" Initialize a SubscriptionNotifier.
Parameters
----------
obj : Declarative
owner : Declarative
The declarative object which owns the expression.
name : str
Expand All @@ -319,17 +319,17 @@ def __init__(self, obj, name, keyval):
An object to use for testing equivalency of notifiers.
"""
self.obj = ref(obj)
self.owner = ref(owner)
self.name = name
self.keyval = keyval

def notify(self):
""" Notify that the expression is invalid.
"""
obj = self.obj()
if obj is not None:
obj.refresh_expression(self.name)
owner = self.owner()
if owner is not None:
owner.refresh_expression(self.name)


class SubscriptionExpression(BaseExpression):
Expand All @@ -348,14 +348,14 @@ def __init__(self, func, f_locals):
#--------------------------------------------------------------------------
# AbstractExpression Interface
#--------------------------------------------------------------------------
def eval(self, obj, name):
def eval(self, owner, name):
""" Evaluate and return the expression value.
"""
tracer = TraitsTracer()
overrides = {'nonlocals': Nonlocals(obj, tracer)}
scope = DynamicScope(obj, self._f_locals, overrides, tracer)
with obj.operators:
overrides = {'nonlocals': Nonlocals(owner, tracer)}
scope = DynamicScope(owner, self._f_locals, overrides, tracer)
with owner.operators:
result = call_func(self._func, (tracer,), {}, scope)

# In most cases, the objects comprising the dependencies of an
Expand All @@ -372,7 +372,7 @@ def eval(self, obj, name):
keyval = tuple(sorted((id(obj), attr) for obj, attr in traced))
notifier = self._notifier
if notifier is None or keyval != notifier.keyval:
notifier = SubscriptionNotifier(obj, name, keyval)
notifier = SubscriptionNotifier(owner, name, keyval)
self._notifier = notifier
handler = notifier.notify
for obj, attr in traced:
Expand All @@ -396,15 +396,15 @@ class DelegationExpression(SubscriptionExpression):
#--------------------------------------------------------------------------
# AbstractListener Interface
#--------------------------------------------------------------------------
def value_changed(self, obj, name, old, new):
""" Called when the attribute on the object has changed.
def value_changed(self, owner, name, old, new):
""" Called when the attribute on the owner has changed.
"""
nonlocals = Nonlocals(obj, None)
nonlocals = Nonlocals(owner, None)
inverter = StandardInverter(nonlocals)
overrides = {'nonlocals': nonlocals}
scope = DynamicScope(obj, self._f_locals, overrides, None)
with obj.operators:
scope = DynamicScope(owner, self._f_locals, overrides, None)
with owner.operators:
call_func(self._func._update, (inverter, new), {}, scope)


Expand Down

0 comments on commit dfb6f64

Please sign in to comment.