Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/autograph/pyct/static_analysis/activity.py: 19%
411 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
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.
17Requires qualified name annotations (see qual_names.py).
18"""
20import copy
21import weakref
23import gast
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
31class Scope(object):
32 """Encloses local symbol definition and usage information.
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.
38 Caution - the AST references held by this object are weak.
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.
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.
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 """
90 # Note: this mutable-immutable pattern is used because using a builder would
91 # have taken a lot more boilerplate.
93 def __init__(self, parent, isolated=True, function_name=None):
94 """Create a new scope.
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
107 self.isolated_names = set()
109 self.read = set()
110 self.modified = set()
111 self.deleted = set()
113 self.bound = set()
114 self.globals = set()
115 self.nonlocals = set()
116 self.annotations = set()
118 self.params = weakref.WeakValueDictionary()
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
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
131 @property
132 def referenced(self):
133 if self.parent is not None:
134 return self.read | self.parent.referenced
135 return self.read
137 @property
138 def free_vars(self):
139 enclosing_scope = self.enclosing_scope
140 return enclosing_scope.read - enclosing_scope.bound
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)
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
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)
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
200 def __repr__(self):
201 return 'Scope{r=%s, w=%s}' % (tuple(self.read), tuple(self.modified))
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
210class _Comprehension(object):
212 no_root = True
214 def __init__(self):
215 # TODO(mdan): Consider using an enum.
216 self.is_list_comp = False
217 self.targets = set()
220class _FunctionOrClass(object):
222 def __init__(self):
223 self.node = None
226class ActivityAnalyzer(transformer.Base):
227 """Annotates nodes with local scope information.
229 See Scope.
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 """
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)
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
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
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
266 def _track_symbol(self, node, composite_writes_alter_parent=False):
267 if self._track_annotations_only and not self._in_annotation:
268 return
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)
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
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
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)
297 elif isinstance(node.ctx, gast.Load):
298 self.scope.read.add(qn)
299 if self._in_annotation:
300 self.scope.annotations.add(qn)
302 elif isinstance(node.ctx, gast.Param):
303 self.scope.bound.add(qn)
304 self.scope.mark_param(qn, self.state[_FunctionOrClass].node)
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)
315 else:
316 raise ValueError('Unknown context {} for node "{}".'.format(
317 type(node.ctx), qn))
319 def _enter_scope(self, isolated, f_name=None):
320 self.scope = Scope(self.scope, isolated=isolated, function_name=f_name)
322 def _exit_scope(self):
323 exited_scope = self.scope
324 exited_scope.finalize()
325 self.scope = exited_scope.parent
326 return exited_scope
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
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
339 def _process_annotation(self, node):
340 self._in_annotation = True
341 node = self.visit(node)
342 self._in_annotation = False
343 return node
345 def visit_Import(self, node):
346 return self._process_statement(node)
348 def visit_ImportFrom(self, node):
349 return self._process_statement(node)
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
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
370 def visit_Expr(self, node):
371 return self._process_statement(node)
373 def visit_Raise(self, node):
374 return self._process_statement(node)
376 def visit_Return(self, node):
377 return self._process_statement(node)
379 def visit_Assign(self, node):
380 return self._process_statement(node)
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
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)
400 self._in_aug_assign = True
401 node.target = self.visit(node.target)
402 self._in_aug_assign = False
404 node.op = self.visit(node.op)
405 node.value = self.visit(node.value)
406 self._exit_and_record_scope(node)
407 return node
409 def visit_Delete(self, node):
410 return self._process_statement(node)
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
418 def visit_alias(self, node):
419 node = self.generic_visit(node)
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)
427 self.scope.modified.add(qn)
428 self.scope.bound.add(qn)
429 return node
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
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
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
453 def visit_Assert(self, node):
454 return self._process_statement(node)
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)
463 node.func = self.visit(node.func)
464 return node
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
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
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
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)
512 def visit_DictComp(self, node):
513 return self._process_comprehension(node, is_dict_comp=True)
515 def visit_ListComp(self, node):
516 return self._process_comprehension(node, is_list_comp=True)
518 def visit_SetComp(self, node):
519 return self._process_comprehension(node)
521 def visit_GeneratorExp(self, node):
522 return self._process_comprehension(node)
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)
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
543 def _visit_node_list(self, nodes):
544 return [(None if n is None else self.visit(n)) for n in nodes]
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
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
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)
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)
581 # A separate Scope tracks the actual function definition.
582 self._enter_scope(True, node.name)
584 # Keep a separate scope for the arguments node, which is used in the CFG.
585 self._enter_scope(False, node.name)
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)
591 self._exit_and_record_scope(node.args)
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)
599 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE)
600 return node
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)
612 # A separate Scope tracks the actual function definition.
613 self._enter_scope(True)
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)
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)
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)
632 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE)
634 lambda_scope = self.scope
635 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE)
637 # TODO(bhack:) https://github.com/tensorflow/tensorflow/issues/56089
638 # remove after deprecation
639 # Exception: lambdas are assumed to be used in the place where
640 # they are defined. Therefore, their activity is passed on to the
641 # calling statement.
642 self.scope.read.update(lambda_scope.read - lambda_scope.bound)
644 return node
646 def visit_With(self, node):
647 self._enter_scope(False)
648 node = self.generic_visit(node)
649 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE)
650 return node
652 def visit_withitem(self, node):
653 return self._process_statement(node)
655 def visit_If(self, node):
656 self._enter_scope(False)
657 node.test = self.visit(node.test)
658 node_scope = self._exit_and_record_scope(node.test)
659 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope)
661 node = self._process_parallel_blocks(node,
662 ((node.body, NodeAnno.BODY_SCOPE),
663 (node.orelse, NodeAnno.ORELSE_SCOPE)))
664 return node
666 def visit_For(self, node):
667 self._enter_scope(False)
668 node.target = self.visit(node.target)
669 node.iter = self.visit(node.iter)
670 self._exit_and_record_scope(node.iter)
672 self._enter_scope(False)
673 self.visit(node.target)
674 if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST):
675 self._process_statement(anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST))
676 self._exit_and_record_scope(node, tag=NodeAnno.ITERATE_SCOPE)
678 node = self._process_parallel_blocks(node,
679 ((node.body, NodeAnno.BODY_SCOPE),
680 (node.orelse, NodeAnno.ORELSE_SCOPE)))
681 return node
683 def visit_While(self, node):
684 self._enter_scope(False)
685 node.test = self.visit(node.test)
686 node_scope = self._exit_and_record_scope(node.test)
687 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope)
689 node = self._process_parallel_blocks(node,
690 ((node.body, NodeAnno.BODY_SCOPE),
691 (node.orelse, NodeAnno.ORELSE_SCOPE)))
692 return node
694 def visit_ExceptHandler(self, node):
695 self._enter_scope(False)
696 # try/except oddity: as expected, it leaks any names you defined inside the
697 # except block, but not the name of the exception variable.
698 if node.name is not None:
699 self.scope.isolated_names.add(anno.getanno(node.name, anno.Basic.QN))
700 node = self.generic_visit(node)
701 self._exit_scope()
702 return node
705def resolve(node, context, parent_scope=None):
706 return ActivityAnalyzer(context, parent_scope).visit(node)