def test_copy(self): node_1 = ast.Name() anno.setanno(node_1, 'foo', 3) node_2 = ast.Name() anno.copyanno(node_1, node_2, 'foo') anno.copyanno(node_1, node_2, 'bar') self.assertTrue(anno.hasanno(node_2, 'foo')) self.assertFalse(anno.hasanno(node_2, 'bar'))
def test_duplicate(self): node = ast.If(test=ast.Num(1), body=[ast.Expr(ast.Name('bar', ast.Load()))], orelse=[]) anno.setanno(node, 'spam', 1) anno.setanno(node, 'ham', 1) anno.setanno(node.body[0], 'ham', 1) anno.dup(node, {'spam': 'eggs'}) self.assertTrue(anno.hasanno(node, 'spam')) self.assertTrue(anno.hasanno(node, 'ham')) self.assertTrue(anno.hasanno(node, 'eggs')) self.assertFalse(anno.hasanno(node.body[0], 'eggs'))
def visit_Attribute(self, node): node = self.generic_visit(node) if anno.hasanno(node.value, anno.Basic.QN): anno.setanno( node, anno.Basic.QN, QN(anno.getanno(node.value, anno.Basic.QN), attr=node.attr)) return node
def _node_sets_self_attribute(self, node): if anno.hasanno(node, anno.Basic.QN): qn = anno.getanno(node, anno.Basic.QN) # TODO(mdanatg): The 'self' argument is not guaranteed to be called 'self'. if qn.has_attr and qn.parent.qn == ('self', ): return True return False
def _track_symbol(self, node, composite_writes_alter_parent=False): # A QN may be missing when we have an attribute (or subscript) on a function # call. Example: a().b if not anno.hasanno(node, anno.Basic.QN): return qn = anno.getanno(node, anno.Basic.QN) # When inside a lambda, ignore any of the lambda's arguments. # This includes attributes or slices of those arguments. for l in self.state[_Lambda]: if qn in l.args: return if qn.owner_set & set(l.args): return # When inside a comprehension, ignore any of the comprehensions's targets. # This includes attributes or slices of those arguments. # This is not true in Python2, which leaks symbols. if six.PY3: for l in self.state[_Comprehension]: if qn in l.targets: return if qn.owner_set & set(l.targets): return if isinstance(node.ctx, gast.Store): # In comprehensions, modified symbols are the comprehension targets. if six.PY3 and self.state[_Comprehension].level > 0: # Like a lambda's args, they are tracked separately in Python3. self.state[_Comprehension].targets.add(qn) else: self.scope.mark_modified(qn) if qn.is_composite and composite_writes_alter_parent: self.scope.mark_modified(qn.parent) if self._in_aug_assign: self.scope.mark_read(qn) elif isinstance(node.ctx, gast.Load): self.scope.mark_read(qn) elif isinstance(node.ctx, gast.Param): if self._in_function_def_args: # In function defs have the meaning of defining a variable. self.scope.mark_modified(qn) self.scope.mark_param(qn, self.enclosing_entities[-1]) elif self.state[_Lambda].level: # In lambdas, they are tracked separately. self.state[_Lambda].args.add(qn) else: # TODO(mdanatg): Is this case possible at all? raise NotImplementedError( 'Param "{}" outside a function arguments or lambda.'. format(qn)) elif isinstance(node.ctx, gast.Del): # The read matches the Python semantics - attempting to delete an # undefined symbol is illegal. self.scope.mark_read(qn) self.scope.mark_deleted(qn) else: raise ValueError('Unknown context {} for node "{}".'.format( type(node.ctx), qn))
def test_copy_clean_preserves_annotations(self): node = parsing.parse_str( textwrap.dedent(""" def f(a): return a + 1 """)) anno.setanno(node.body[0], 'foo', 'bar') anno.setanno(node.body[0], 'baz', 1) new_node = ast_util.copy_clean(node, preserve_annos={'foo'}) self.assertEqual(anno.getanno(new_node.body[0], 'foo'), 'bar') self.assertFalse(anno.hasanno(new_node.body[0], 'baz'))
def test_basic(self): node = ast.Name() self.assertEqual(anno.keys(node), set()) self.assertFalse(anno.hasanno(node, 'foo')) with self.assertRaises(AttributeError): anno.getanno(node, 'foo') anno.setanno(node, 'foo', 3) self.assertEqual(anno.keys(node), {'foo'}) self.assertTrue(anno.hasanno(node, 'foo')) self.assertEqual(anno.getanno(node, 'foo'), 3) self.assertEqual(anno.getanno(node, 'bar', default=7), 7) anno.delanno(node, 'foo') self.assertEqual(anno.keys(node), set()) self.assertFalse(anno.hasanno(node, 'foo')) with self.assertRaises(AttributeError): anno.getanno(node, 'foo') self.assertIsNone(anno.getanno(node, 'foo', default=None))
def visit_Subscript(self, node): # TODO(mdanatg): This may no longer apply if we overload getitem. node = self.generic_visit(node) s = node.slice if not isinstance(s, gast.Index): # TODO(mdanatg): Support range and multi-dimensional indices. # Continuing silently because some demos use these. return node if isinstance(s.value, gast.Num): subscript = QN(NumberLiteral(s.value.n)) elif isinstance(s.value, gast.Str): subscript = QN(StringLiteral(s.value.s)) else: # The index may be an expression, case in which a name doesn't make sense. if anno.hasanno(node.slice.value, anno.Basic.QN): subscript = anno.getanno(node.slice.value, anno.Basic.QN) else: return node if anno.hasanno(node.value, anno.Basic.QN): anno.setanno( node, anno.Basic.QN, QN(anno.getanno(node.value, anno.Basic.QN), subscript=subscript)) return node
def visit(self, node): if not isinstance(node, gast.AST): # This is not that uncommon a mistake: various node bodies are lists, for # example, posing a land mine for transformers that need to recursively # call `visit`. The error needs to be raised before the exception handler # below is installed, because said handler will mess up if `node` is not, # in fact, a node. msg = ('invalid value for "node": expected "ast.AST", got "{}"; to' ' visit lists of nodes, use "visit_block" instead').format( type(node)) raise ValueError(msg) did_enter_function = False processing_expr_node = False if isinstance(node, (gast.FunctionDef, gast.ClassDef, gast.Lambda)): did_enter_function = True elif isinstance(node, gast.Expr): processing_expr_node = True if did_enter_function: self._enclosing_entities.append(node) if processing_expr_node: entry_expr_value = node.value if not anno.hasanno(node, anno.Basic.SKIP_PROCESSING): result = super(Base, self).visit(node) # Adjust for consistency: replacing the value of an Expr with # an Assign node removes the need for the Expr node. if processing_expr_node: if isinstance(result, gast.Expr) and result.value != entry_expr_value: # When the replacement is a list, it is assumed that the list came # from a template that contained a number of statements, which # themselves are standalone and don't require an enclosing Expr. if isinstance(result.value, (list, tuple, gast.Assign, gast.AugAssign)): result = result.value # On exception, the local scope integrity is not guaranteed. if did_enter_function: self._enclosing_entities.pop() return result
def visit_Attribute(self, node): if anno.hasanno(node, anno.Basic.QN): return self._process(node) # Attributes of dynamic objects will not have a QN. return self.generic_visit(node)