# HG changeset patch # User Paul Boddie # Date 1257466627 -3600 # Node ID 37f1668cee4d676aa9cd47b90e42597389049126 # Parent 001d543dc80524266b3427420d15636dbe9dd808# Parent 7afcab571b699a936d1019d46e92849c4268e541 Merged branches, retaining the namespace-based recording of attribute usage, instead of the Attr-based recording. diff -r 001d543dc805 -r 37f1668cee4d micropython/data.py --- a/micropython/data.py Fri Nov 06 01:07:48 2009 +0100 +++ b/micropython/data.py Fri Nov 06 01:17:07 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,52 @@ 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) + return defs[name] + + def _reset_attributes(self, name): + defs = self.attributes_used[-1] + defs[name] = set() + + def _reset_all_attributes(self): + self.attributes_used[-1] = {} + + 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] = defs[name].intersection(attrnames) + + for name, attrnames in defs.items(): + if active.has_key(name): + active[name] = active[name].intersection(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,10 +342,6 @@ self.parent = parent self.name = name - # Attribute usage. - - self.attributes_used = set() - # Possible values. self.context_values = set() @@ -298,15 +350,6 @@ self.assignments = None - # Attribute usage methods. - - def use_attribute(self, attrname): - self.attributes_used.add(attrname) - return self.attributes_used - - def exposes_name(self, attrname): - return attrname in self.attributes_used - # Value-related methods. def get_contexts(self): @@ -808,8 +851,6 @@ name, pos = held.pop() namearray[i] = name - #print self.name, positions - #print "->", namearray return namearray def _cmp_positions(self, a, b): @@ -895,6 +936,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 001d543dc805 -r 37f1668cee4d micropython/inspect.py --- a/micropython/inspect.py Fri Nov 06 01:07:48 2009 +0100 +++ b/micropython/inspect.py Fri Nov 06 01:17:07 2009 +0100 @@ -293,7 +293,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." @@ -303,9 +303,30 @@ "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 reset_all_attributes(self): + self.get_namespace()._reset_all_attributes() + + def use_attribute(self, attr, attrname): + return self.get_namespace()._use_attribute(attr, attrname) + # Visitor methods. def default(self, node, *args): @@ -353,7 +374,7 @@ function = Function( name, - self.get_parent(), + self.get_namespace(), node.argnames, node.defaults, (node.flags & 4 != 0), @@ -437,7 +458,8 @@ # Note usage of the attribute. - node._attrnames = expr.use_attribute(node.attrname) + if expr.parent is self.get_namespace(): + node._attrnames = self.use_attribute(expr, node.attrname) return None @@ -459,6 +481,7 @@ raise InspectError(self.full_name(), node, "Deletion of attribute %r is not supported." % node.name) self.store(node.name, self.expr) + self.reset_attributes(node.name) self.use_name(node.name) return None @@ -497,7 +520,8 @@ visitBitxor = _visitBinary - visitBreak = NOP + def visitBreak(self, node): + self.reset_all_attributes() visitCallFunc = OP @@ -512,7 +536,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. @@ -576,7 +600,8 @@ self.use_name(self.importer.get_constant_type_name(node.value)) return self.importer.make_constant(node.value) - visitContinue = NOP + def visitContinue(self, node): + self.reset_all_attributes() visitDecorators = NOP @@ -595,17 +620,34 @@ 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. + # Propagate attribute usage to branches. self.in_loop = 1 - self.NOP(node) + self.new_branch() + self.dispatch(node.body) + self.shelve_branch() self.in_loop = 0 + + # Maintain a branch for the else clause or the current retained usage + # where execution avoids the conditional clauses. + + self.new_branch() + if node.else_ is not None: + self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None def visitFrom(self, node): @@ -664,7 +706,8 @@ # Note usage of the attribute. - node._attrnames = expr.use_attribute(node.attrname) + if expr.parent is self.get_namespace(): + node._attrnames = self.use_attribute(expr, attrname) elif self.builtins is not None: attr = self.builtins.get(attrname) @@ -687,11 +730,26 @@ # The name is recorded in an earlier process. def visitIf(self, node): + self.new_branchpoint() + + # Propagate attribute usage to branches. + for test, body in node.tests: self.dispatch(test) + + self.new_branch() self.dispatch(body) + self.shelve_branch() + + # Maintain a branch for the else clause or the current retained usage + # where execution avoids the conditional clauses. + + self.new_branch() if node.else_ is not None: self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None visitIfExp = NOP @@ -792,7 +850,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. @@ -800,8 +862,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 @@ -813,9 +882,26 @@ visitUnarySub = _visitUnary def visitWhile(self, node): + self.new_branchpoint() + + # Propagate attribute usage to branches. + 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 + + # Maintain a branch for the else clause or the current retained usage + # where execution avoids the conditional clauses. + + self.new_branch() + if node.else_ is not None: + self.dispatch(node.else_) + self.shelve_branch() + + self.merge_branches() return None visitWith = NOP diff -r 001d543dc805 -r 37f1668cee4d tests/attribute_access_type_restriction.py --- a/tests/attribute_access_type_restriction.py Fri Nov 06 01:07:48 2009 +0100 +++ b/tests/attribute_access_type_restriction.py Fri Nov 06 01:17:07 2009 +0100 @@ -11,34 +11,63 @@ def g(self): return 3 +class E: + def f(self): + return 4 + + def h(self): + return 5 + def test_one(obj): - obj.f() - return obj.g() + obj.f() # C, D, E -> D + return obj.g() # D + # obj: D + +def test_two(obj, obj2): + if obj.f(): # C, D, E (f) + obj.g() # D (f, g) + # else: + # ... # obj: C, D, E (f) + # # (f, g) ^ (f) + return 2 + # obj: C, D, E (f) + +def test_new(obj, obj2): + if obj.f(): # C, D, E (f) + obj = obj2 + obj.g() # D (g) + # else: + # ... # obj: C, D, E (f) + # # (g) ^ (f) + return obj.f() # C, D, E (f) + # obj: C, D, E (f) def test_neither(obj, obj2): - if obj.f(): - obj = obj2 - obj.g() - return 2 + if 0: + obj.g() # D (g) + else: + obj.f() # C, D, E (f) + # # (g) ^ (f) + return 4 + # obj: -def test_either(obj, obj2): - if obj: +def test_three(obj, obj2): + if obj.f(): # C, D, E (f) obj = obj2 - obj.g() - return obj.f() - -def test_neither2(obj, obj2): - if obj: - obj.g() + obj.g() # D (g) else: - obj.f() - return 4 + obj.h() # E (f, h) + # # (g) ^ (f, h) + return 5 + # obj: c = C() d = D() +e = E() result1_3 = test_one(d) -result1_2 = test_neither(c, d) -result2_2 = test_either(c, d) -result1_4 = test_neither2(c, d) +result1_2 = test_two(c, d) +result2_2 = test_new(c, d) +result1_4 = test_neither(c, d) +result1_5 = test_three(e, d) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 001d543dc805 -r 37f1668cee4d tests/failure/instance_initialisation_incomplete.py