# HG changeset patch # User Paul Boddie # Date 1276472802 -7200 # Node ID 6be1700e490254d1c375a7c8cca578624172708e # Parent c7c2119483cf3ce47eb80ecc8ddd0eea5370c663 Moved the namespace lookup logic into NamespaceDict; this permits scope usage analysis and conflict detection. Renamed the ambiguous 'module' attribute to 'astnode' on various classes. Moved the AtLeast class into micropython.common. Added various test cases around scope conflicts. diff -r c7c2119483cf -r 6be1700e4902 TO_DO.txt --- a/TO_DO.txt Sun Jun 13 02:24:35 2010 +0200 +++ b/TO_DO.txt Mon Jun 14 01:46:42 2010 +0200 @@ -1,3 +1,7 @@ +Consider labelling _scope on assignments and dealing with the assignment of removed +attributes, possibly removing the entire assignment, and distinguishing between such cases +and unknown names. + Check name origin where multiple branches could yield multiple scope interpretations: ---- diff -r c7c2119483cf -r 6be1700e4902 micropython/ast.py --- a/micropython/ast.py Sun Jun 13 02:24:35 2010 +0200 +++ b/micropython/ast.py Mon Jun 14 01:46:42 2010 +0200 @@ -126,8 +126,8 @@ block = self.new_block() self.set_block(block) - if self.module.module is not None: - self.dispatch(self.module.module) + if self.module.astnode is not None: + self.dispatch(self.module.astnode) # Finish off the translated program if appropriate. @@ -417,17 +417,7 @@ def visitListCompIf(self, node): raise TranslationNotImplementedError("ListCompIf") def visitName(self, node): - - # Handle names referring to constants. - - if self.importer.predefined_constants.has_key(node.name): - const = self.importer.get_predefined_constant(node.name) - self.new_op(LoadConst(const)) - - # Handle all other names. - - else: - self._visitName(node, self.name_load_instructions) + self._visitName(node, self.name_load_instructions) def visitSlice(self, node): if node.lower is None: diff -r c7c2119483cf -r 6be1700e4902 micropython/common.py --- a/micropython/common.py Sun Jun 13 02:24:35 2010 +0200 +++ b/micropython/common.py Mon Jun 14 01:46:42 2010 +0200 @@ -111,6 +111,51 @@ pass +# Special representations. + +class AtLeast: + + "A special representation for numbers of a given value or greater." + + def __init__(self, count): + self.count = count + + def __eq__(self, other): + return 0 + + __lt__ = __le__ = __eq__ + + def __ne__(self, other): + return 1 + + def __gt__(self, other): + if isinstance(other, AtLeast): + return 0 + else: + return self.count > other + + def __ge__(self, other): + if isinstance(other, AtLeast): + return 0 + else: + return self.count >= other + + def __iadd__(self, other): + if isinstance(other, AtLeast): + self.count += other.count + else: + self.count += other + return self + + def __radd__(self, other): + if isinstance(other, AtLeast): + return AtLeast(self.count + other.count) + else: + return AtLeast(self.count + other) + + def __repr__(self): + return "AtLeast(%r)" % self.count + # Useful data. comparison_methods = { diff -r c7c2119483cf -r 6be1700e4902 micropython/data.py --- a/micropython/data.py Sun Jun 13 02:24:35 2010 +0200 +++ b/micropython/data.py Mon Jun 14 01:46:42 2010 +0200 @@ -53,6 +53,7 @@ """ from micropython.program import DataObject, DataValue, ReplaceableContext, PlaceholderContext +from micropython.common import AtLeast, InspectError def shortrepr(obj): if obj is None: @@ -60,51 +61,6 @@ else: return obj.__shortrepr__() -# Special representations. - -class AtLeast: - - "A special representation for numbers of a given value or greater." - - def __init__(self, count): - self.count = count - - def __eq__(self, other): - return 0 - - __lt__ = __le__ = __eq__ - - def __ne__(self, other): - return 1 - - def __gt__(self, other): - if isinstance(other, AtLeast): - return 0 - else: - return self.count > other - - def __ge__(self, other): - if isinstance(other, AtLeast): - return 0 - else: - return self.count >= other - - def __iadd__(self, other): - if isinstance(other, AtLeast): - self.count += other.count - else: - self.count += other - return self - - def __radd__(self, other): - if isinstance(other, AtLeast): - return AtLeast(self.count + other.count) - else: - return AtLeast(self.count + other) - - def __repr__(self): - return "AtLeast(%r)" % self.count - # Mix-ins and abstract classes. class Naming: @@ -122,10 +78,9 @@ "A mix-in providing dictionary methods." def __init__(self, module=None): + self.module = module self.namespace = {} self.globals = set() - self.scope_usage = {} - self.module = module self.finalised = 0 # Attributes accessed on objects, potentially narrowing their types. @@ -138,7 +93,13 @@ self.user_shelves = [] self.loop_users = [{}] # stack of loop nodes + # Scope usage, indicating the origin of names. + + self.scope_usage = [{}] # stack of scope usage + self.scope_shelves = [] + # Define attribute usage to identify active program sections. + # Attribute users are AST nodes defining names. self.all_attribute_users = set() @@ -162,6 +123,56 @@ def __getitem__(self, name): return self.namespace[name] + def get_using_node(self, name, node): + + """ + Access the given 'name' through this namespace, making use of the module + and builtins namespaces if necessary, annotating the given 'node' with + the scope involved. + """ + + attr, scope, full_name = self._get_with_scope(name) + + if scope is not None: + node._scope = scope + self.note_scope(name, scope) + + if full_name is not None: + self.use_specific_attribute(full_name, name) + + return attr + + def _get_with_scope(self, name, external=0): + + module = self.module + builtins = module and module.builtins or None + importer = module and module.importer or None + + # Constants. + + if importer is not None and importer.predefined_constants.has_key(name): + return importer.get_predefined_constant(name), "constant", None + + # Locals. + + elif not external and self.has_key(name): + return self[name], "local", self.full_name() + + # Globals. + + elif module is not None and module.has_key(name): + return module[name], "global", module.full_name() + + # Builtins. + + elif builtins is not None and builtins.has_key(name): + return builtins[name], "builtins", builtins.full_name() + + # Unknown. + + else: + return None, None, None + def get(self, name, default=None): return self.namespace.get(name, default) @@ -181,6 +192,7 @@ self.module.set(name, value, 0) else: self._set(name, value, single_assignment) + self.note_scope(name, "local") def set_module(self, name, value): @@ -268,31 +280,11 @@ if not self.namespace.has_key(name): self.globals.add(name) + self.note_scope(name, "global") return 1 else: return 0 - def note_scope(self, name, scope): - - "Note usage of 'name' from the given 'scope' in the current namespace." - - if not self.scope_usage.has_key(name): - self.scope_usage[name] = scope - return 1 - elif self.scope_usage[name] == scope: - return 1 - else: - return 0 - - def used_in_scope(self, name, scope): - - """ - Return whether 'name' is used from the given 'scope' in the current - namespace. - """ - - return self.scope_usage.get(name) == scope - # Attribute positioning. def attributes_as_list(self): @@ -346,6 +338,24 @@ return self._use_attribute(name, attrname) + def use_specific_attribute(self, objname, attrname): + + """ + Note attribute usage specifically on 'objname' - an object which is + known at inspection time - or in the current unit if 'objname' is None, + nominating a specific attribute 'attrname'. + + This bypasses attribute user mechanisms. + """ + + from_name = self.full_name() + objname = objname or from_name + module = self.module + importer = module and module.importer + + if importer is not None: + importer.use_specific_name(objname, attrname, from_name) + # These shadow various methods in the InspectedModule class, and provide # implementations generally. @@ -417,6 +427,8 @@ node._attrnames[name] = set() + # Branch management methods. + def _new_branchpoint(self): """ @@ -425,6 +437,7 @@ """ self.user_shelves.append([]) + self.scope_shelves.append([]) def _new_branch(self, loop_node=None): @@ -438,24 +451,31 @@ # Retain a record of active users. - d = {} - d.update(self.attribute_users[-1]) - self.attribute_users.append(d) + new_users = {} + new_users.update(self.attribute_users[-1]) + self.attribute_users.append(new_users) - new_users = self.attribute_users[-1] + # Where a loop is the cause of the branch, register the loop node as a + # user of each name so that attribute usage is also recorded for the + # loop. - d = {} - d.update(self.loop_users[-1]) + loop_users = {} + loop_users.update(self.loop_users[-1]) + self.loop_users.append(loop_users) if loop_node is not None: for name in new_users.keys(): - if not d.has_key(name): - d[name] = set([loop_node]) + if not loop_users.has_key(name): + loop_users[name] = set([loop_node]) else: - d[name] = d[name].union([loop_node]) + loop_users[name] = loop_users[name].union([loop_node]) self._init_attribute_user_for_name(loop_node, name) - self.loop_users.append(d) + # Retain a record of scope usage. + + scope_usage = {} + scope_usage.update(self.scope_usage[-1]) + self.scope_usage.append(scope_usage) def _abandon_branch(self): pass @@ -472,6 +492,9 @@ users = self.attribute_users.pop() self.user_shelves[-1].append(users) + scope_usage = self.scope_usage.pop() + self.scope_shelves[-1].append(scope_usage) + def _merge_branches(self): """ @@ -513,6 +536,60 @@ self.attribute_users[-1] = new_users + # Combine the scope usage. + + scope_usage = self.scope_usage[-1] + new_scope_usage = {} + + all_scope_usage = self.scope_shelves.pop() + all_scope_names = set() + + # Find all the names for whom scope information has been defined. + + for shelved_usage in all_scope_usage: + all_scope_names.update(shelved_usage.keys()) + + for shelved_usage in all_scope_usage: + for name in all_scope_names: + + # Find the recorded scope for the name. + + if shelved_usage.has_key(name): + scope = shelved_usage[name] + elif scope_usage.has_key(name): + scope = scope_usage[name] + + # If no scope is recorded, find a suitable external source. + + else: + attr, scope, full_name = self._get_with_scope(name, external=1) + + # Attempt to record the scope, testing for conflicts. + + if scope: + if not new_scope_usage.has_key(name): + new_scope_usage[name] = scope + elif new_scope_usage[name] != scope: + raise InspectError("Scope conflict for %r." % name) + + # Scope usage methods. + + def note_scope(self, name, scope): + + "Note usage of 'name' from the given 'scope' in the current namespace." + + self.scope_usage[-1][name] = scope + + def used_in_scope(self, name, scope): + + """ + Return whether 'name' is used from the given 'scope' in the current + namespace. + """ + + scope_usage = self.scope_usage[-1] + return scope_usage.get(name) == scope + # Program data structures. class Attr: @@ -1409,9 +1486,10 @@ "An inspected module's core details." - def __init__(self, name): + def __init__(self, name, importer): NamespaceDict.__init__(self, self) self.name = name + self.importer = importer self.parent = None # Original location details. diff -r c7c2119483cf -r 6be1700e4902 micropython/inspect.py --- a/micropython/inspect.py Sun Jun 13 02:24:35 2010 +0200 +++ b/micropython/inspect.py Mon Jun 14 01:46:42 2010 +0200 @@ -93,13 +93,12 @@ """ ASTVisitor.__init__(self) - Module.__init__(self, name) + Module.__init__(self, name, importer) self.visitor = self # Import machinery links. - self.importer = importer - self.optimisations = importer.optimisations + self.optimisations = self.importer.optimisations self.builtins = self.importer.modules.get("__builtins__") self.loaded = 0 @@ -114,7 +113,6 @@ self.in_function = 0 # Note function presence, affecting definitions. self.in_loop = 0 # Note loop "membership", affecting assignments. self.namespaces = [] - self.module = None self.functions = [] def parse(self, filename): @@ -128,7 +126,7 @@ "Process the given 'module'." - self.astnode = self.module = module + self.astnode = module # Add __name__ to the namespace by adding an explicit assignment to the # module. @@ -387,20 +385,6 @@ return self.get_namespace()._use_attribute(name, attrname) - def use_specific_attribute(self, objname, attrname): - - """ - Note attribute usage specifically on 'objname' - an object which is - known at inspection time - or in the current unit if 'objname' is None, - nominating a specific attribute 'attrname'. - - This bypasses attribute user mechanisms. - """ - - from_name = self.get_namespace().full_name() - objname = objname or from_name - self.importer.use_specific_name(objname, attrname, from_name) - # Visitor methods. def default(self, node, *args): @@ -933,60 +917,7 @@ visitMul = _visitBinary def visitName(self, node): - name = node.name - - # Constants. - - if self.importer.predefined_constants.has_key(name): - attr = self.importer.get_predefined_constant(name) - node._scope = "constant" - - # Locals. - - elif self.namespaces and self.namespaces[-1].has_key(name): - attr = self.namespaces[-1][name] - - # Note usage of the local (potentially a class attribute). - - self.use_specific_attribute(None, name) - node._scope = "local" - - # Globals. - - elif self.has_key(name): - attr = self[name] - - # Note usage of the module attribute. - - self.use_specific_attribute(self.full_name(), name) - node._scope = "global" - - # Note global usage in any local namespace. - - if self.namespaces: - self.namespaces[-1].note_scope(name, "global") - - # Builtins. - - elif self.builtins is not None and self.builtins.has_key(name): - attr = self.builtins[name] - self.use_specific_attribute(self.builtins.full_name(), name) - node._scope = "builtins" - - # Note builtins usage in any local namespace. - - if self.namespaces: - self.namespaces[-1].note_scope(name, "builtins") - else: - self.note_scope(name, "builtins") - - # Unknown. - - else: - attr = None - self.use_name(name) - - return attr + return self.get_namespace().get_using_node(node.name, node) visitNot = OP diff -r c7c2119483cf -r 6be1700e4902 micropython/trans.py --- a/micropython/trans.py Sun Jun 13 02:24:35 2010 +0200 +++ b/micropython/trans.py Mon Jun 14 01:46:42 2010 +0200 @@ -59,12 +59,8 @@ "Return the scope for the given 'name'." - if self.unit is not self.module and self.unit.has_key(name): - return "local" - elif self.module.has_key(name): - return "global" - else: - return "builtins" + attr, scope, from_name = self.unit._get_with_scope(name) + return scope def load_builtin(self, name, node): @@ -1161,8 +1157,6 @@ # Get the expected scope of the name. scope = getattr(node, "_scope", None) or self.get_scope(name) - - #print self.module.name, node.lineno, name, predicted_scope self._generateName(name, scope, classes, node) def _generateName(self, name, scope, classes, node): @@ -1174,7 +1168,15 @@ NameInstruction, AddressInstruction, AddressContextInstruction = classes - if scope == "local": + # Handle names referring to constants. + + if scope == "constant": + const = self.importer.get_predefined_constant(name) + self.new_op(LoadConst(const)) + + # Handle all other names. + + elif scope == "local": unit = self.unit if isinstance(unit, Function): self.new_op(NameInstruction(unit.all_locals()[name])) @@ -1200,7 +1202,8 @@ self.new_op(AddressInstruction(self.get_builtin(name, node))) else: - raise TranslateError("Program unit uses unknown name %r." % name) + # NOTE: This may happen because a class attribute is optimised away. + print "Program unit uses unknown name %r." % name def _visitUnary(self, node): diff -r c7c2119483cf -r 6be1700e4902 tests/failure/shadow_globals_builtins_conflict.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/failure/shadow_globals_builtins_conflict.py Mon Jun 14 01:46:42 2010 +0200 @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +x = [1, 2, 3] + +if x: + y = len(x) +else: + def len(arg): + return 0 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c7c2119483cf -r 6be1700e4902 tests/failure/shadow_globals_builtins_conflict_implicit.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/failure/shadow_globals_builtins_conflict_implicit.py Mon Jun 14 01:46:42 2010 +0200 @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +x = [1, 2, 3] + +if not x: + def len(arg): + return 0 + +y = len(x) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c7c2119483cf -r 6be1700e4902 tests/failure/shadow_locals_globals_if.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/failure/shadow_locals_globals_if.py Mon Jun 14 01:46:42 2010 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +a = 1 + +def reassign(i): + if i: + a = a + return a # name has varying origin + +result2_1 = reassign(3) + +# vim: tabstop=4 expandtab shiftwidth=4