1# Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""Activity analysis. 16 17Requires qualified name annotations (see qual_names.py). 18""" 19 20import copy 21import weakref 22 23import gast 24 25from tensorflow.python.autograph.pyct import anno 26from tensorflow.python.autograph.pyct import qual_names 27from tensorflow.python.autograph.pyct import transformer 28from tensorflow.python.autograph.pyct.static_analysis.annos import NodeAnno 29 30 31class Scope(object): 32 """Encloses local symbol definition and usage information. 33 34 This can track for instance whether a symbol is modified in the current scope. 35 Note that scopes do not necessarily align with Python's scopes. For example, 36 the body of an if statement may be considered a separate scope. 37 38 Caution - the AST references held by this object are weak. 39 40 Scope objects are mutable during construction only, and must be frozen using 41 `Scope.finalize()` before use. Furthermore, a scope is consistent only after 42 all its children have been frozen. While analysing code blocks, scopes are 43 being gradually built, from the innermost scope outward. Freezing indicates 44 that the analysis of a code block is complete. Once frozen, mutation is no 45 longer allowed. `is_final` tracks whether the scope is frozen or not. Certain 46 properties, like `referenced`, are only accurate when called on frozen scopes. 47 48 Attributes: 49 parent: Optional[Scope], the parent scope, if any. 50 isolated: bool, whether the scope is a true Python scope (e.g. the scope of 51 a function), or just a surrogate tracking an ordinary code block. Using 52 the terminology of the Python 3 reference documentation, True roughly 53 represents an actual scope, whereas False represents an ordinary code 54 block. 55 function_name: Optional[str], name of the function owning this scope. 56 isolated_names: Set[qual_names.QN], identifiers that are isolated to this 57 scope (even if the scope is not isolated). 58 annotations: Set[qual_names.QN], identifiers used as type annotations 59 in this scope. 60 read: Set[qual_names.QN], identifiers read in this scope. 61 modified: Set[qual_names.QN], identifiers modified in this scope. 62 deleted: Set[qual_names.QN], identifiers deleted in this scope. 63 bound: Set[qual_names.QN], names that are bound to this scope. See 64 https://docs.python.org/3/reference/executionmodel.html#binding-of-names 65 for a precise definition. 66 globals: Set[qual_names.QN], names that are explicitly marked as global in 67 this scope. Note that this doesn't include free read-only vars bound to 68 global symbols. 69 nonlocals: Set[qual_names.QN], names that are explicitly marked as nonlocal 70 in this scope. Note that this doesn't include free read-only vars bound to 71 global symbols. 72 free_vars: Set[qual_names.QN], the free variables in this scope. See 73 https://docs.python.org/3/reference/executionmodel.html for a precise 74 definition. 75 params: WeakValueDictionary[qual_names.QN, ast.Node], function arguments 76 visible in this scope, mapped to the function node that defines them. 77 enclosing_scope: Scope, the innermost isolated scope that is a transitive 78 parent of this scope. May be the scope itself. 79 referenced: Set[qual_names.QN], the totality of the symbols used by this 80 scope and its parents. 81 is_final: bool, whether the scope is frozen or not. 82 83 Note - simple statements may never delete and modify a symbol at the same 84 time. However, compound ones like if statements can. In that latter case, it's 85 undefined whether the symbol is actually modified or deleted upon statement 86 exit. Certain analyses like reaching definitions need to be careful about 87 this. 88 """ 89 90 # Note: this mutable-immutable pattern is used because using a builder would 91 # have taken a lot more boilerplate. 92 93 def __init__(self, parent, isolated=True, function_name=None): 94 """Create a new scope. 95 96 Args: 97 parent: A Scope or None. 98 isolated: Whether the scope is isolated, that is, whether variables 99 modified in this scope should be considered modified in the parent 100 scope. 101 function_name: Name of the function owning this scope. 102 """ 103 self.parent = parent 104 self.isolated = isolated 105 self.function_name = function_name 106 107 self.isolated_names = set() 108 109 self.read = set() 110 self.modified = set() 111 self.deleted = set() 112 113 self.bound = set() 114 self.globals = set() 115 self.nonlocals = set() 116 self.annotations = set() 117 118 self.params = weakref.WeakValueDictionary() 119 120 # Certain fields can only be accessed after the scope and all its parent 121 # scopes have been fully built. This field guards that. 122 self.is_final = False 123 124 @property 125 def enclosing_scope(self): 126 assert self.is_final 127 if self.parent is not None and not self.isolated: 128 return self.parent 129 return self 130 131 @property 132 def referenced(self): 133 if self.parent is not None: 134 return self.read | self.parent.referenced 135 return self.read 136 137 @property 138 def free_vars(self): 139 enclosing_scope = self.enclosing_scope 140 return enclosing_scope.read - enclosing_scope.bound 141 142 def copy_from(self, other): 143 """Recursively copies the contents of this scope from another scope.""" 144 assert not self.is_final 145 if self.parent is not None: 146 assert other.parent is not None 147 self.parent.copy_from(other.parent) 148 self.isolated_names = copy.copy(other.isolated_names) 149 self.modified = copy.copy(other.modified) 150 self.read = copy.copy(other.read) 151 self.deleted = copy.copy(other.deleted) 152 self.bound = copy.copy(other.bound) 153 self.annotations = copy.copy(other.annotations) 154 self.params = copy.copy(other.params) 155 156 @classmethod 157 def copy_of(cls, other): 158 if other.parent is not None: 159 assert other.parent is not None 160 parent = cls.copy_of(other.parent) 161 else: 162 parent = None 163 new_copy = cls(parent) 164 new_copy.copy_from(other) 165 return new_copy 166 167 def merge_from(self, other): 168 """Adds all activity from another scope to this scope.""" 169 assert not self.is_final 170 if self.parent is not None: 171 assert other.parent is not None 172 self.parent.merge_from(other.parent) 173 self.isolated_names.update(other.isolated_names) 174 self.read.update(other.read) 175 self.modified.update(other.modified) 176 self.bound.update(other.bound) 177 self.deleted.update(other.deleted) 178 self.annotations.update(other.annotations) 179 self.params.update(other.params) 180 181 def finalize(self): 182 """Freezes this scope.""" 183 assert not self.is_final 184 # TODO(mdan): freeze read, modified, bound. 185 if self.parent is not None: 186 assert not self.parent.is_final 187 if not self.isolated: 188 self.parent.read.update(self.read - self.isolated_names) 189 self.parent.modified.update(self.modified - self.isolated_names) 190 self.parent.bound.update(self.bound - self.isolated_names) 191 self.parent.globals.update(self.globals) 192 self.parent.nonlocals.update(self.nonlocals) 193 self.parent.annotations.update(self.annotations) 194 else: 195 # TODO(mdan): This is not accurate. 196 self.parent.read.update(self.read - self.bound) 197 self.parent.annotations.update(self.annotations - self.bound) 198 self.is_final = True 199 200 def __repr__(self): 201 return 'Scope{r=%s, w=%s}' % (tuple(self.read), tuple(self.modified)) 202 203 def mark_param(self, name, owner): 204 # Assumption: all AST nodes have the same life span. This lets us use 205 # a weak reference to mark the connection between a symbol node and the 206 # function node whose argument that symbol is. 207 self.params[name] = owner 208 209 210class _Comprehension(object): 211 212 no_root = True 213 214 def __init__(self): 215 # TODO(mdan): Consider using an enum. 216 self.is_list_comp = False 217 self.targets = set() 218 219 220class _FunctionOrClass(object): 221 222 def __init__(self): 223 self.node = None 224 225 226class ActivityAnalyzer(transformer.Base): 227 """Annotates nodes with local scope information. 228 229 See Scope. 230 231 The use of this class requires that qual_names.resolve() has been called on 232 the node. This class will ignore nodes have not been 233 annotated with their qualified names. 234 """ 235 236 def __init__(self, context, parent_scope=None): 237 super(ActivityAnalyzer, self).__init__(context) 238 self.allow_skips = False 239 self.scope = Scope(parent_scope, isolated=True) 240 241 # Note: all these flags crucially rely on the respective nodes are 242 # leaves in the AST, that is, they cannot contain other statements. 243 self._in_aug_assign = False 244 self._in_annotation = False 245 self._track_annotations_only = False 246 247 @property 248 def _in_constructor(self): 249 context = self.state[_FunctionOrClass] 250 if context.level > 2: 251 innermost = context.stack[-1].node 252 parent = context.stack[-2].node 253 return (isinstance(parent, gast.ClassDef) and 254 (isinstance(innermost, gast.FunctionDef) and 255 innermost.name == '__init__')) 256 return False 257 258 def _node_sets_self_attribute(self, node): 259 if anno.hasanno(node, anno.Basic.QN): 260 qn = anno.getanno(node, anno.Basic.QN) 261 # TODO(mdan): The 'self' argument is not guaranteed to be called 'self'. 262 if qn.has_attr and qn.parent.qn == ('self',): 263 return True 264 return False 265 266 def _track_symbol(self, node, composite_writes_alter_parent=False): 267 if self._track_annotations_only and not self._in_annotation: 268 return 269 270 # A QN may be missing when we have an attribute (or subscript) on a function 271 # call. Example: a().b 272 if not anno.hasanno(node, anno.Basic.QN): 273 return 274 qn = anno.getanno(node, anno.Basic.QN) 275 276 # When inside a comprehension, ignore reads to any of the comprehensions's 277 # targets. This includes attributes or slices of those arguments. 278 for l in self.state[_Comprehension]: 279 if qn in l.targets: 280 return 281 if qn.owner_set & set(l.targets): 282 return 283 284 if isinstance(node.ctx, gast.Store): 285 # In comprehensions, modified symbols are the comprehension targets. 286 if self.state[_Comprehension].level > 0: 287 self.state[_Comprehension].targets.add(qn) 288 return 289 290 self.scope.modified.add(qn) 291 self.scope.bound.add(qn) 292 if qn.is_composite and composite_writes_alter_parent: 293 self.scope.modified.add(qn.parent) 294 if self._in_aug_assign: 295 self.scope.read.add(qn) 296 297 elif isinstance(node.ctx, gast.Load): 298 self.scope.read.add(qn) 299 if self._in_annotation: 300 self.scope.annotations.add(qn) 301 302 elif isinstance(node.ctx, gast.Param): 303 self.scope.bound.add(qn) 304 self.scope.mark_param(qn, self.state[_FunctionOrClass].node) 305 306 elif isinstance(node.ctx, gast.Del): 307 # The read matches the Python semantics - attempting to delete an 308 # undefined symbol is illegal. 309 self.scope.read.add(qn) 310 # Targets of del are considered bound: 311 # https://docs.python.org/3/reference/executionmodel.html#binding-of-names 312 self.scope.bound.add(qn) 313 self.scope.deleted.add(qn) 314 315 else: 316 raise ValueError('Unknown context {} for node "{}".'.format( 317 type(node.ctx), qn)) 318 319 def _enter_scope(self, isolated, f_name=None): 320 self.scope = Scope(self.scope, isolated=isolated, function_name=f_name) 321 322 def _exit_scope(self): 323 exited_scope = self.scope 324 exited_scope.finalize() 325 self.scope = exited_scope.parent 326 return exited_scope 327 328 def _exit_and_record_scope(self, node, tag=anno.Static.SCOPE): 329 node_scope = self._exit_scope() 330 anno.setanno(node, tag, node_scope) 331 return node_scope 332 333 def _process_statement(self, node): 334 self._enter_scope(False) 335 node = self.generic_visit(node) 336 self._exit_and_record_scope(node) 337 return node 338 339 def _process_annotation(self, node): 340 self._in_annotation = True 341 node = self.visit(node) 342 self._in_annotation = False 343 return node 344 345 def visit_Import(self, node): 346 return self._process_statement(node) 347 348 def visit_ImportFrom(self, node): 349 return self._process_statement(node) 350 351 def visit_Global(self, node): 352 self._enter_scope(False) 353 for name in node.names: 354 qn = qual_names.QN(name) 355 self.scope.read.add(qn) 356 self.scope.globals.add(qn) 357 self._exit_and_record_scope(node) 358 return node 359 360 def visit_Nonlocal(self, node): 361 self._enter_scope(False) 362 for name in node.names: 363 qn = qual_names.QN(name) 364 self.scope.read.add(qn) 365 self.scope.bound.add(qn) 366 self.scope.nonlocals.add(qn) 367 self._exit_and_record_scope(node) 368 return node 369 370 def visit_Expr(self, node): 371 return self._process_statement(node) 372 373 def visit_Raise(self, node): 374 return self._process_statement(node) 375 376 def visit_Return(self, node): 377 return self._process_statement(node) 378 379 def visit_Assign(self, node): 380 return self._process_statement(node) 381 382 def visit_AnnAssign(self, node): 383 self._enter_scope(False) 384 node.target = self.visit(node.target) 385 if node.value is not None: 386 # Can be None for pure declarations, e.g. `n: int`. This is a new thing 387 # enabled by type annotations, but does not influence static analysis 388 # (declarations are not definitions). 389 node.value = self.visit(node.value) 390 if node.annotation: 391 node.annotation = self._process_annotation(node.annotation) 392 self._exit_and_record_scope(node) 393 return node 394 395 def visit_AugAssign(self, node): 396 # Special rules for AugAssign. Here, the AST only shows the target as 397 # written, when it is in fact also read. 398 self._enter_scope(False) 399 400 self._in_aug_assign = True 401 node.target = self.visit(node.target) 402 self._in_aug_assign = False 403 404 node.op = self.visit(node.op) 405 node.value = self.visit(node.value) 406 self._exit_and_record_scope(node) 407 return node 408 409 def visit_Delete(self, node): 410 return self._process_statement(node) 411 412 def visit_Name(self, node): 413 if node.annotation: 414 node.annotation = self._process_annotation(node.annotation) 415 self._track_symbol(node) 416 return node 417 418 def visit_alias(self, node): 419 node = self.generic_visit(node) 420 421 if node.asname is None: 422 # Only the root name is a real symbol operation. 423 qn = qual_names.QN(node.name.split('.')[0]) 424 else: 425 qn = qual_names.QN(node.asname) 426 427 self.scope.modified.add(qn) 428 self.scope.bound.add(qn) 429 return node 430 431 def visit_Attribute(self, node): 432 node = self.generic_visit(node) 433 if self._in_constructor and self._node_sets_self_attribute(node): 434 self._track_symbol(node, composite_writes_alter_parent=True) 435 else: 436 self._track_symbol(node) 437 return node 438 439 def visit_Subscript(self, node): 440 node = self.generic_visit(node) 441 # Subscript writes (e.g. a[b] = "value") are considered to modify 442 # both the element itself (a[b]) and its parent (a). 443 self._track_symbol(node) 444 return node 445 446 def visit_Print(self, node): 447 self._enter_scope(False) 448 node.values = self.visit_block(node.values) 449 node_scope = self._exit_and_record_scope(node) 450 anno.setanno(node, NodeAnno.ARGS_SCOPE, node_scope) 451 return node 452 453 def visit_Assert(self, node): 454 return self._process_statement(node) 455 456 def visit_Call(self, node): 457 self._enter_scope(False) 458 node.args = self.visit_block(node.args) 459 node.keywords = self.visit_block(node.keywords) 460 # TODO(mdan): Account starargs, kwargs 461 self._exit_and_record_scope(node, tag=NodeAnno.ARGS_SCOPE) 462 463 node.func = self.visit(node.func) 464 return node 465 466 def _process_block_node(self, node, block, scope_name): 467 self._enter_scope(False) 468 block = self.visit_block(block) 469 self._exit_and_record_scope(node, tag=scope_name) 470 return node 471 472 def _process_parallel_blocks(self, parent, children): 473 # Because the scopes are not isolated, processing any child block 474 # modifies the parent state causing the other child blocks to be 475 # processed incorrectly. So we need to checkpoint the parent scope so that 476 # each child sees the same context. 477 before_parent = Scope.copy_of(self.scope) 478 after_children = [] 479 for child, scope_name in children: 480 self.scope.copy_from(before_parent) 481 parent = self._process_block_node(parent, child, scope_name) 482 after_child = Scope.copy_of(self.scope) 483 after_children.append(after_child) 484 for after_child in after_children: 485 self.scope.merge_from(after_child) 486 return parent 487 488 def _process_comprehension(self, 489 node, 490 is_list_comp=False, 491 is_dict_comp=False): 492 with self.state[_Comprehension] as comprehension_: 493 comprehension_.is_list_comp = is_list_comp 494 # Note: it's important to visit the generators first to properly account 495 # for the variables local to these generators. Example: `x` is local to 496 # the expression `z for x in y for z in x`. 497 node.generators = self.visit_block(node.generators) 498 if is_dict_comp: 499 node.key = self.visit(node.key) 500 node.value = self.visit(node.value) 501 else: 502 node.elt = self.visit(node.elt) 503 return node 504 505 def visit_comprehension(self, node): 506 # It is important to visit children in this order so that the reads to 507 # the target name are appropriately ignored. 508 node.iter = self.visit(node.iter) 509 node.target = self.visit(node.target) 510 return self.generic_visit(node) 511 512 def visit_DictComp(self, node): 513 return self._process_comprehension(node, is_dict_comp=True) 514 515 def visit_ListComp(self, node): 516 return self._process_comprehension(node, is_list_comp=True) 517 518 def visit_SetComp(self, node): 519 return self._process_comprehension(node) 520 521 def visit_GeneratorExp(self, node): 522 return self._process_comprehension(node) 523 524 def visit_ClassDef(self, node): 525 with self.state[_FunctionOrClass] as fn: 526 fn.node = node 527 # The ClassDef node itself has a Scope object that tracks the creation 528 # of its name, along with the usage of any decorator accompanying it. 529 self._enter_scope(False) 530 node.decorator_list = self.visit_block(node.decorator_list) 531 self.scope.modified.add(qual_names.QN(node.name)) 532 self.scope.bound.add(qual_names.QN(node.name)) 533 node.bases = self.visit_block(node.bases) 534 node.keywords = self.visit_block(node.keywords) 535 self._exit_and_record_scope(node) 536 537 # A separate Scope tracks the actual class definition. 538 self._enter_scope(True) 539 node = self.generic_visit(node) 540 self._exit_scope() 541 return node 542 543 def _visit_node_list(self, nodes): 544 return [(None if n is None else self.visit(n)) for n in nodes] 545 546 def _visit_arg_annotations(self, node): 547 node.args.kw_defaults = self._visit_node_list(node.args.kw_defaults) 548 node.args.defaults = self._visit_node_list(node.args.defaults) 549 self._track_annotations_only = True 550 node = self._visit_arg_declarations(node) 551 self._track_annotations_only = False 552 return node 553 554 def _visit_arg_declarations(self, node): 555 node.args.posonlyargs = self._visit_node_list(node.args.posonlyargs) 556 node.args.args = self._visit_node_list(node.args.args) 557 if node.args.vararg is not None: 558 node.args.vararg = self.visit(node.args.vararg) 559 node.args.kwonlyargs = self._visit_node_list(node.args.kwonlyargs) 560 if node.args.kwarg is not None: 561 node.args.kwarg = self.visit(node.args.kwarg) 562 return node 563 564 def visit_FunctionDef(self, node): 565 with self.state[_FunctionOrClass] as fn: 566 fn.node = node 567 # The FunctionDef node itself has a Scope object that tracks the creation 568 # of its name, along with the usage of any decorator accompanying it. 569 self._enter_scope(False) 570 node.decorator_list = self.visit_block(node.decorator_list) 571 if node.returns: 572 node.returns = self._process_annotation(node.returns) 573 # Argument annotartions (includeing defaults) affect the defining context. 574 node = self._visit_arg_annotations(node) 575 576 function_name = qual_names.QN(node.name) 577 self.scope.modified.add(function_name) 578 self.scope.bound.add(function_name) 579 self._exit_and_record_scope(node) 580 581 # A separate Scope tracks the actual function definition. 582 self._enter_scope(True, node.name) 583 584 # Keep a separate scope for the arguments node, which is used in the CFG. 585 self._enter_scope(False, node.name) 586 587 # Arg declarations only affect the function itself, and have no effect 588 # in the defining context whatsoever. 589 node = self._visit_arg_declarations(node) 590 591 self._exit_and_record_scope(node.args) 592 593 # Track the body separately. This is for compatibility reasons, it may not 594 # be strictly needed. 595 self._enter_scope(False, node.name) 596 node.body = self.visit_block(node.body) 597 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 598 599 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) 600 return node 601 602 def visit_Lambda(self, node): 603 # Lambda nodes are treated in roughly the same way as FunctionDef nodes. 604 with self.state[_FunctionOrClass] as fn: 605 fn.node = node 606 # The Lambda node itself has a Scope object that tracks the creation 607 # of its name, along with the usage of any decorator accompanying it. 608 self._enter_scope(False) 609 node = self._visit_arg_annotations(node) 610 self._exit_and_record_scope(node) 611 612 # A separate Scope tracks the actual function definition. 613 self._enter_scope(True) 614 615 # Keep a separate scope for the arguments node, which is used in the CFG. 616 self._enter_scope(False) 617 node = self._visit_arg_declarations(node) 618 self._exit_and_record_scope(node.args) 619 620 # Track the body separately. This is for compatibility reasons, it may not 621 # be strictly needed. 622 # TODO(mdan): Do remove it, it's confusing. 623 self._enter_scope(False) 624 node.body = self.visit(node.body) 625 626 # The lambda body can contain nodes of types normally not found as 627 # statements, and may not have the SCOPE annotation needed by the CFG. 628 # So we attach one if necessary. 629 if not anno.hasanno(node.body, anno.Static.SCOPE): 630 anno.setanno(node.body, anno.Static.SCOPE, self.scope) 631 632 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 633 634 lambda_scope = self.scope 635 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) 636 637 # Exception: lambdas are assumed to be used in the place where 638 # they are defined. Therefore, their activity is passed on to the 639 # calling statement. 640 self.scope.read.update(lambda_scope.read - lambda_scope.bound) 641 642 return node 643 644 def visit_With(self, node): 645 self._enter_scope(False) 646 node = self.generic_visit(node) 647 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 648 return node 649 650 def visit_withitem(self, node): 651 return self._process_statement(node) 652 653 def visit_If(self, node): 654 self._enter_scope(False) 655 node.test = self.visit(node.test) 656 node_scope = self._exit_and_record_scope(node.test) 657 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) 658 659 node = self._process_parallel_blocks(node, 660 ((node.body, NodeAnno.BODY_SCOPE), 661 (node.orelse, NodeAnno.ORELSE_SCOPE))) 662 return node 663 664 def visit_For(self, node): 665 self._enter_scope(False) 666 node.target = self.visit(node.target) 667 node.iter = self.visit(node.iter) 668 self._exit_and_record_scope(node.iter) 669 670 self._enter_scope(False) 671 self.visit(node.target) 672 if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): 673 self._process_statement(anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) 674 self._exit_and_record_scope(node, tag=NodeAnno.ITERATE_SCOPE) 675 676 node = self._process_parallel_blocks(node, 677 ((node.body, NodeAnno.BODY_SCOPE), 678 (node.orelse, NodeAnno.ORELSE_SCOPE))) 679 return node 680 681 def visit_While(self, node): 682 self._enter_scope(False) 683 node.test = self.visit(node.test) 684 node_scope = self._exit_and_record_scope(node.test) 685 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) 686 687 node = self._process_parallel_blocks(node, 688 ((node.body, NodeAnno.BODY_SCOPE), 689 (node.orelse, NodeAnno.ORELSE_SCOPE))) 690 return node 691 692 def visit_ExceptHandler(self, node): 693 self._enter_scope(False) 694 # try/except oddity: as expected, it leaks any names you defined inside the 695 # except block, but not the name of the exception variable. 696 if node.name is not None: 697 self.scope.isolated_names.add(anno.getanno(node.name, anno.Basic.QN)) 698 node = self.generic_visit(node) 699 self._exit_scope() 700 return node 701 702 703def resolve(node, context, parent_scope=None): 704 return ActivityAnalyzer(context, parent_scope).visit(node) 705