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