# HG changeset patch # User paulb@localhost.localdomain # Date 1164572891 -3600 # Node ID 5b213d091b9f05370ff4f9cc8bca40388c0d87ce # Parent f991da4a64a49ef2e86f1fcfcf6c330a3628a46c Introduced a CheckExc node for the specific purpose of testing exception types. Changed the processing of exception details to test explicitly for Tuple nodes and to flatten such structures in order to build CheckExc nodes, which are then used directly in testing exception types and pruning the exceptions from the current namespace. Introduced improved namespace management in Try nodes. Added a get_class convenience method in the Instance class. Improved tests. diff -r f991da4a64a4 -r 5b213d091b9f annotate.py --- a/annotate.py Sun Nov 26 15:45:53 2006 +0100 +++ b/annotate.py Sun Nov 26 21:28:11 2006 +0100 @@ -234,27 +234,52 @@ # With Return nodes inside the body/else sections, the changes are # communicated to the caller. - conditional.test = self.dispatch(conditional.test) - saved_namespace = self.namespace - is_global = self.namespace is self.global_namespace + is_module = self.namespace is self.module.namespace + + # Where the test is closely associated with the body, save the namespace + # before entering the test. + + if conditional.isolate_test: + saved_namespace = self.namespace + self.namespace = Namespace() + if is_module: + self.module.namespace = self.namespace + self.namespace.merge_namespace(saved_namespace) - self.namespace = Namespace() - if is_global: - self.module.namespace = self.global_namespace = self.namespace - self.namespace.merge_namespace(saved_namespace) + conditional.test = self.dispatch(conditional.test) + + # Where the test may affect the body and the else clause, save the + # namespace after processing the test. + + if not conditional.isolate_test: + saved_namespace = self.namespace + self.namespace = Namespace() + if is_module: + self.module.namespace = self.namespace + self.namespace.merge_namespace(saved_namespace) + + # Process the body clause. + conditional.body = self.dispatches(conditional.body) body_namespace = self.namespace + # Use the saved namespace as a template for the else clause. + self.namespace = Namespace() - if is_global: - self.module.namespace = self.global_namespace = self.namespace + if is_module: + self.module.namespace = self.namespace self.namespace.merge_namespace(saved_namespace) + + # Process the else clause. + conditional.else_ = self.dispatches(conditional.else_) else_namespace = self.namespace + # Merge the body and else namespaces. + self.namespace = Namespace() - if is_global: - self.module.namespace = self.global_namespace = self.namespace + if is_module: + self.module.namespace = self.namespace self.namespace.merge_namespace(body_namespace) self.namespace.merge_namespace(else_namespace) @@ -272,21 +297,74 @@ return subprogram def visitTry(self, try_): + is_module = self.namespace is self.module.namespace + try_.body = self.dispatches(try_.body) - try_.handler = self.dispatches(try_.handler) + + # Save the namespace from the body. + + body_namespace = Namespace() + body_namespace.merge_namespace(self.namespace) + + # Process the handler. + + if hasattr(try_, "handler"): + try_.handler = self.dispatches(try_.handler) + + # Save the namespace from the handler. + + handler_namespace = Namespace() + handler_namespace.merge_namespace(self.namespace) + + # Remember the raised exceptions encountered so far. + raises = self.namespace.raises - # Empty the raised exceptions for the else clause. + # Process the else clause. + + if hasattr(try_, "else_"): + + # Restore the body namespace for the else clause. + + self.namespace = body_namespace + if is_module: + self.module.namespace = self.namespace + + # Empty the raised exceptions for the else clause. - self.namespace.raises = [] - try_.else_ = self.dispatches(try_.else_) - self.namespace.raises = raises + self.namespace.raises = [] + try_.else_ = self.dispatches(try_.else_) + self.namespace.raises = raises + + # Merge the namespaces. + + self.namespace = Namespace() + if is_module: + self.module.namespace = self.namespace + self.namespace.merge_namespace(body_namespace) + self.namespace.merge_namespace(handler_namespace) + + # Process the finally clause, if any. try_.finally_ = self.dispatches(try_.finally_) return try_ # Namespace operations. + def visitCheckExc(self, checkexc): + checkexc.expr = self.dispatch(checkexc.expr) + expr_types = self.namespace.types + choice_types = [] + choices = [] + for choice in checkexc.choices: + choices.append(self.dispatch(choice)) + choice_types += self.namespace.types + for expr_type in expr_types: + if expr_type.type.get_class() not in choice_types: + print "CheckExc", expr_type, "should be revoked!" + self._prune_non_accesses(checkexc.expr, expr_type) + return checkexc + def visitLoadAttr(self, loadattr): loadattr.expr = self.dispatch(loadattr.expr) types = [] @@ -300,7 +378,7 @@ # Revoke this type from any name involved. - self._prune_non_accesses(loadattr, attr) + self._prune_non_accesses(loadattr.expr, attr) for attribute, accessor in attributes: if attribute is not None: @@ -315,7 +393,7 @@ # Revoke this type from any name involved. - self._prune_non_accesses(loadattr, attr) + self._prune_non_accesses(loadattr.expr, attr) if not types: print "No attribute found for", loadattr.name, "given", self.namespace.types @@ -325,13 +403,15 @@ self.annotate(loadattr) return loadattr - def _prune_non_accesses(self, loadattr, attr): - if isinstance(loadattr.expr, LoadName): - self.namespace.revoke(loadattr.expr.name, attr) - elif isinstance(loadattr.expr, LoadAttr): - for expr_attr in loadattr.expr.expr.types: + def _prune_non_accesses(self, expr, attr): + if isinstance(expr, LoadName): + self.namespace.revoke(expr.name, attr) + elif isinstance(expr, LoadAttr): + for expr_attr in expr.expr.types: if hasattr(expr_attr.type, "namespace"): - expr_attr.type.namespace.revoke(loadattr.expr.name, attr) + expr_attr.type.namespace.revoke(expr.name, attr) + elif isinstance(expr, LoadExc): + self.namespace.revoke_exception_type(attr) def visitLoadExc(self, loadexc): self.namespace.types = self.namespace.raises[:] @@ -887,6 +967,9 @@ def revoke(self, name, type): self.names[name].remove(type) + def revoke_exception_type(self, type): + self.raises.remove(type) + def merge_namespace(self, namespace, no_return_locals=0): self.merge_items(namespace.names.items()) combine(self.returns, namespace.returns) diff -r f991da4a64a4 -r 5b213d091b9f simplified.py --- a/simplified.py Sun Nov 26 15:45:53 2006 +0100 +++ b/simplified.py Sun Nov 26 21:28:11 2006 +0100 @@ -233,17 +233,6 @@ if getattr(self, "structure", 0): self._pprint(indent + 2, "( ", "structure '%s'" % self.structure.name, stream=stream) - # Statement-related details. - - if hasattr(self, "test"): - self.test.pprint(indent + 2, "? ", stream=stream) - for attr in "code", "body", "else_", "handler", "finally_", "choices": - if hasattr(self, attr) and getattr(self, attr): - self._pprint(indent, "", "%s {" % attr, stream=stream) - for node in getattr(self, attr): - node.pprint(indent + 2, stream=stream) - self._pprint(indent, "", "}", stream=stream) - # Expression-related details. if hasattr(self, "expr"): @@ -265,6 +254,17 @@ if hasattr(self, "dstar") and self.dstar: self.dstar.pprint(indent + 2, "( ", stream=stream) + # Statement-related details. + + if hasattr(self, "test"): + self.test.pprint(indent + 2, "? ", stream=stream) + for attr in "code", "body", "else_", "handler", "finally_", "choices": + if hasattr(self, attr) and getattr(self, attr): + self._pprint(indent, "", "%s {" % attr, stream=stream) + for node in getattr(self, attr): + node.pprint(indent + 2, stream=stream) + self._pprint(indent, "", "}", stream=stream) + # Annotations. if hasattr(self, "accesses"): @@ -291,16 +291,27 @@ class LoadAttr(Node): "Load an object attribute." class LoadRef(Node): "Load a reference, typically a subprogram or a constant." class LoadExc(Node): "Load a handled exception." +class CheckExc(Node): "Check a handled exception." class StoreTemp(Node): "Store a temporary value." class StoreName(Node): "Associate a name with an object." class StoreAttr(Node): "Associate an object's attribute with a value." class ReleaseTemp(Node): "Release a temporary value." -class Conditional(Node): "A conditional node consisting of a test and outcomes." class Try(Node): "A try...except...else...finally grouping node." class Raise(Node): "An exception raising node." class Not(Node): "A negation of an expression." class Invoke(Node): "An invocation." +# Some behaviour is set as the default in conditional nodes but may be +# overridden. + +class Conditional(Node): + + "A conditional node consisting of a test and outcomes." + + def __init__(self, *args, **kw): + self.isolate_test = 0 + Node.__init__(self, *args, **kw) + # Invocations involve some more work to process calculated attributes. class InvokeFunction(Invoke): @@ -392,7 +403,10 @@ def full_name(self): # NOTE: Wrap the result in a call to name(self, ...) where multiple # NOTE: instances per class can occur. - return self.namespace.load("__class__")[0].type._full_name + return self.get_class()._full_name + + def get_class(self): + return self.namespace.load("__class__")[0].type def __repr__(self): return "Instance of type '%s'" % self.full_name() diff -r f991da4a64a4 -r 5b213d091b9f simplify.py --- a/simplify.py Sun Nov 26 15:45:53 2006 +0100 +++ b/simplify.py Sun Nov 26 21:28:11 2006 +0100 @@ -352,13 +352,9 @@ # isinstance(, ) else: - new_spec = self.dispatch(spec) test = Conditional( - test=InvokeFunction( - expr=LoadName(name="isinstance"), - args=[LoadExc(), new_spec], - star=None, - dstar=None) + isolate_test=1, + test=CheckExc(expr=LoadExc(), choices=self._visitTryExcept(spec)) ) test.body = [] @@ -390,6 +386,18 @@ result.handler = results return result + def _visitTryExcept(self, spec): + + "Return a list of nodes for the given exception type 'spec'." + + if isinstance(spec, compiler.ast.Tuple): + nodes = [] + for node in spec.nodes: + nodes += self._visitTryExcept(node) + else: + nodes = [self.dispatch(spec)] + return nodes + comparison_methods = { "==" : "__eq__", "!=" : "__ne__", "<" : "__lt__", "<=" : "__le__", ">=" : "__ge__", ">" : "__gt__", "is" : None, "is not" : None diff -r f991da4a64a4 -r 5b213d091b9f tests/tryexcept.py --- a/tests/tryexcept.py Sun Nov 26 15:45:53 2006 +0100 +++ b/tests/tryexcept.py Sun Nov 26 21:28:11 2006 +0100 @@ -18,5 +18,6 @@ a = f else: a = x + return a f()