# HG changeset patch # User Paul Boddie # Date 1257026375 -3600 # Node ID fa2c07dd048f243c42bdc33eac5d93a5332f9f30 # Parent 28161d2a2e1935b729a0da6e2dff140b4b19f930 Added elementary attribute name tracking based on the namespace where the names of the affected objects reside. Added an example of access-based type restrictions. diff -r 28161d2a2e19 -r fa2c07dd048f micropython/data.py --- a/micropython/data.py Sun Oct 25 18:35:53 2009 +0100 +++ b/micropython/data.py Sat Oct 31 22:59:35 2009 +0100 @@ -119,6 +119,14 @@ self.module = module self.finalised = 0 + # Attributes accessed on objects, potentially narrowing their types. + # Specific namespaces should define known names during initialisation. + + self.attributes_used = [{}] # stack of usage + self.attribute_shelves = [] # stack of unmerged definitions + + # Attribute/name definition and access. + def __delitem__(self, name): del self.namespace[name] @@ -241,6 +249,8 @@ else: return 0 + # Attribute positioning. + def attributes_as_list(self): "Return the attributes in a list." @@ -265,6 +275,48 @@ self.finalised = 1 + # Attribute usage methods. + + def _use_attribute(self, attr, attrname): + name = attr.name + defs = self.attributes_used[-1] + if not defs.has_key(name): + defs[name] = set() + defs[name].add(attrname) + + def _reset_attributes(self, name): + defs = self.attributes_used[-1] + defs[name] = set() + + def _new_branchpoint(self): + self.attribute_shelves.append([]) + + def _new_branch(self): + d = {} + for name, attrnames in self.attributes_used[-1].items(): + d[name] = set(attrnames) + self.attributes_used.append(d) + + def _shelve_branch(self): + self.attribute_shelves[-1].append(self.attributes_used.pop()) + + def _merge_branches(self): + active = self.attributes_used[-1] + + shelved_defs = self.attribute_shelves.pop() + defs = shelved_defs[0] + + for next_defs in shelved_defs[1:]: + for name, attrnames in next_defs.items(): + if defs.has_key(name): + defs[name].intersection_update(attrnames) + + for name, attrnames in defs.items(): + if active.has_key(name): + active[name].intersection_update(attrnames) + else: + active[name] = attrnames + # Program data structures. There are two separate kinds of structures: those # with context, which are the values manipulated by programs, and those without # context, which are typically constant things which are stored alongside the @@ -286,6 +338,8 @@ self.parent = parent self.name = name + # Possible values. + self.context_values = set() # Number of assignments per name. @@ -791,8 +845,6 @@ name, pos = held.pop() namearray[i] = name - #print self.name, positions - #print "->", namearray return namearray def _cmp_positions(self, a, b): @@ -878,6 +930,11 @@ self.default_attrs = [] + # Initialise attribute usage. + + for arg in argnames: + self.attributes_used[-1][arg] = set() + # Caches. self.localnames = None # cache for locals diff -r 28161d2a2e19 -r fa2c07dd048f micropython/inspect.py --- a/micropython/inspect.py Sun Oct 25 18:35:53 2009 +0100 +++ b/micropython/inspect.py Sat Oct 31 22:59:35 2009 +0100 @@ -286,7 +286,7 @@ self.namespaces[-2].add_instance_attribute(name) - def get_parent(self): + def get_namespace(self): "Return the parent (or most recent) namespace currently exposed." @@ -296,9 +296,27 @@ "Use the given 'name' within the current namespace/unit." - unit = self.get_parent() + unit = self.get_namespace() self.importer.use_name(name, unit.name) + def new_branchpoint(self): + self.get_namespace()._new_branchpoint() + + def new_branch(self): + self.get_namespace()._new_branch() + + def shelve_branch(self): + self.get_namespace()._shelve_branch() + + def merge_branches(self): + self.get_namespace()._merge_branches() + + def reset_attributes(self, name): + self.get_namespace()._reset_attributes(name) + + def use_attribute(self, attr, attrname): + self.get_namespace()._use_attribute(attr, attrname) + # Visitor methods. def default(self, node, *args): @@ -346,7 +364,7 @@ function = Function( name, - self.get_parent(), + self.get_namespace(), node.argnames, node.defaults, (node.flags & 4 != 0), @@ -417,6 +435,9 @@ def visitAssAttr(self, node): expr = self.dispatch(node.expr) + + # Record the attribute on the presumed target. + if isinstance(expr, Attr): if expr.name == "self": if not self.store_class_attr(node.attrname): @@ -424,6 +445,12 @@ elif isinstance(expr.get_value(), Module): self.store_module_attr(node.attrname, expr.get_value()) print "Warning: attribute %r of module %r set outside the module." % (node.attrname, expr.get_value().name) + + # Note usage of the attribute. + + if expr.parent is self.get_namespace(): + self.use_attribute(expr, node.attrname) + return None def visitAssList(self, node): @@ -441,6 +468,7 @@ def visitAssName(self, node): self.store(node.name, self.expr) + self.reset_attributes(node.name) self.use_name(node.name) return None @@ -494,7 +522,7 @@ print "Class %r in %r is not global: ignored." % (node.name, self.namespaces[-1].full_name()) return None else: - cls = Class(node.name, self.get_parent(), self, node) + cls = Class(node.name, self.get_namespace(), self, node) # Visit the base class expressions, attempting to find concrete # definitions of classes. @@ -577,17 +605,30 @@ visitFloorDiv = _visitBinary def visitFor(self, node): + self.new_branchpoint() # Declare names which will be used by generated code. self.use_name("__iter__") self.use_name("next") + self.dispatch(node.assign) + self.dispatch(node.list) + # Enter the loop. self.in_loop = 1 - self.NOP(node) + self.new_branch() + self.dispatch(node.body) + self.shelve_branch() self.in_loop = 0 + + if node.else_ is not None: + self.new_branch() + self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None def visitFrom(self, node): @@ -633,6 +674,8 @@ expr = self.dispatch(node.expr) attrname = node.attrname + # Attempt to identify the nature of the attribute. + if isinstance(expr, Attr): value = expr.get_value() if isinstance(value, (Class, Module)): @@ -641,6 +684,12 @@ attr = UnresolvedName(attrname, value.full_name(), self) else: attr = None + + # Note usage of the attribute. + + if expr.parent is self.get_namespace(): + self.use_attribute(expr, attrname) + elif self.builtins is not None: attr = self.builtins.get(attrname) else: @@ -662,11 +711,21 @@ # The name is recorded in an earlier process. def visitIf(self, node): + self.new_branchpoint() + for test, body in node.tests: self.dispatch(test) + + self.new_branch() self.dispatch(body) + self.shelve_branch() + if node.else_ is not None: + self.new_branch() self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None visitIfExp = NOP @@ -767,7 +826,11 @@ def visitTryExcept(self, node): self.dispatch(node.body) + + self.new_branchpoint() + for name, var, n in node.handlers: + self.new_branch() # Establish the local for the handler. @@ -775,8 +838,15 @@ self.dispatch(var) if n is not None: self.dispatch(n) + + self.shelve_branch() + if node.else_ is not None: + self.new_branch() self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None visitTryFinally = NOP @@ -788,9 +858,21 @@ visitUnarySub = _visitUnary def visitWhile(self, node): + self.new_branchpoint() + self.in_loop = 1 - self.NOP(node) + self.dispatch(node.test) + self.new_branch() + self.dispatch(node.body) + self.shelve_branch() self.in_loop = 0 + + if node.else_ is not None: + self.new_branch() + self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None visitWith = NOP diff -r 28161d2a2e19 -r fa2c07dd048f tests/attribute_access_type_restriction.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/attribute_access_type_restriction.py Sat Oct 31 22:59:35 2009 +0100 @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +class C: + def f(self): + return 1 + +class D: + def f(self): + return 2 + + def g(self): + return 3 + +def test_one(obj): + obj.f() + return obj.g() + +def test_neither(obj, obj2): + if obj.f(): + obj = obj2 + obj.g() + return 2 + +def test_either(obj, obj2): + if obj: + obj = obj2 + obj.g() + return obj.f() + +def test_neither2(obj, obj2): + if obj: + obj.g() + else: + obj.f() + return 4 + +c = C() +d = D() +result1_3 = test_one(d) +result1_2 = test_neither(c, d) +result2_2 = test_either(c, d) +result1_4 = test_neither2(c, d) + +# vim: tabstop=4 expandtab shiftwidth=4