From 305b7d3192971ad881430719fd063521067a0de3 Mon Sep 17 00:00:00 2001 From: cpopa Date: Tue, 4 Mar 2014 09:29:46 +0200 Subject: Drop yield_from API, add a new YieldFrom node instead. --- ChangeLog | 4 +--- as_string.py | 15 ++++++++++----- node_classes.py | 7 +++---- nodes.py | 4 ++-- rebuilder.py | 20 ++++++++++---------- scoped_nodes.py | 5 +++-- test/unittest_python3.py | 36 ++++++++++++++++++++++++++---------- 7 files changed, 55 insertions(+), 36 deletions(-) diff --git a/ChangeLog b/ChangeLog index f065913..6881c7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,9 +15,7 @@ Change log for the astroid package (used to be astng) * Don't crash when inferring nodes from `with` clauses with multiple context managers. Closes #18. - * Add `yield_from` property to `Yield` nodes, in order - to differentiate between the classic `yield` and `yield from`. - + * Add a new YieldFrom node. 2013-10-18 -- 1.0.1 * fix py3k/windows installation issue (issue #4) diff --git a/as_string.py b/as_string.py index 1863b93..ace1c4e 100644 --- a/as_string.py +++ b/as_string.py @@ -429,11 +429,7 @@ class AsStringVisitor(object): def visit_yield(self, node): """yield an ast.Yield node as string""" yi_val = node.value and (" " + node.value.accept(self)) or "" - if node.yield_from: - yield_ = 'yield from' - else: - yield_ = 'yield' - expr = yield_ + yi_val + expr = 'yield' + yi_val if node.parent.is_statement: return expr else: @@ -471,6 +467,15 @@ class AsStringVisitor3k(AsStringVisitor): """return Starred node as string""" return "*" + node.value.accept(self) + def visit_yieldfrom(self, node): + """ Return an astroid.YieldFrom node as string. """ + yi_val = node.value and (" " + node.value.accept(self)) or "" + expr = 'yield from' + yi_val + if node.parent.is_statement: + return expr + else: + return "(%s)" % (expr,) + def _import_string(names): """return a list of (name, asname) formatted as a string""" diff --git a/node_classes.py b/node_classes.py index 3873127..01dc8d9 100644 --- a/node_classes.py +++ b/node_classes.py @@ -886,11 +886,10 @@ class With(BlockRangeMixIn, AssignTypeMixin, Statement): class Yield(NodeNG): """class representing a Yield node""" _astroid_fields = ('value',) - value = _from = None + value = None - @property - def yield_from(self): - return self._from +class YieldFrom(Yield): + """ Class representing a YieldFrom node. """ # constants ############################################################## diff --git a/nodes.py b/nodes.py index 9fdc67e..263ab47 100644 --- a/nodes.py +++ b/nodes.py @@ -43,7 +43,7 @@ from astroid.node_classes import Arguments, AssAttr, Assert, Assign, \ Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \ From, Getattr, Global, If, IfExp, Import, Index, Keyword, \ List, Name, Nonlocal, Pass, Print, Raise, Return, Set, Slice, Starred, Subscript, \ - TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \ + TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, YieldFrom, \ const_factory from astroid.scoped_nodes import Module, GenExpr, Lambda, DictComp, \ ListComp, SetComp, Function, Class @@ -68,6 +68,6 @@ ALL_NODE_CLASSES = ( TryExcept, TryFinally, Tuple, UnaryOp, While, With, - Yield, + Yield, YieldFrom ) diff --git a/rebuilder.py b/rebuilder.py index 24ce802..f6fae7b 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -122,6 +122,14 @@ def _infer_metaclass(node): elif isinstance(node, Attribute): return node.attr +def _create_yield_node(node, parent, rebuilder, factory): + newnode = factory() + _lineno_parent(node, newnode, parent) + if node.value is not None: + newnode.value = rebuilder.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + class TreeRebuilder(object): """Rebuilds the _ast tree to become an Astroid tree""" @@ -837,13 +845,7 @@ class TreeRebuilder(object): def visit_yield(self, node, parent): """visit a Yield node by returning a fresh instance of it""" - newnode = new.Yield() - _lineno_parent(node, newnode, parent) - if node.value is not None: - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - + return _create_yield_node(node, parent, self, new.Yield) class TreeRebuilder3k(TreeRebuilder): """extend and overwrite TreeRebuilder for python3k""" @@ -954,9 +956,7 @@ class TreeRebuilder3k(TreeRebuilder): return newnode def visit_yieldfrom(self, node, parent): - newnode = self.visit_yield(node, parent) - newnode._from = True - return newnode + return _create_yield_node(node, parent, self, new.YieldFrom) def visit_class(self, node, parent): newnode = super(TreeRebuilder3k, self).visit_class(node, parent) diff --git a/scoped_nodes.py b/scoped_nodes.py index a7f6ee8..d579913 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -32,7 +32,7 @@ from logilab.common.decorators import cached from astroid.exceptions import NotFoundError, \ AstroidBuildingException, InferenceError from astroid.node_classes import Const, DelName, DelAttr, \ - Dict, From, List, Pass, Raise, Return, Tuple, Yield, \ + Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ LookupMixIn, const_factory as cf, unpack_infer from astroid.bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ @@ -620,7 +620,8 @@ class Function(Statement, Lambda): """return true if this is a generator function""" # XXX should be flagged, not computed try: - return self.nodes_of_class(Yield, skip_klass=(Function, Lambda)).next() + return self.nodes_of_class((Yield, YieldFrom), + skip_klass=(Function, Lambda)).next() except StopIteration: return False diff --git a/test/unittest_python3.py b/test/unittest_python3.py index e04be83..1390df0 100644 --- a/test/unittest_python3.py +++ b/test/unittest_python3.py @@ -46,22 +46,38 @@ class Python3TC(TestCase): body = dedent(""" def func(): yield from iter([1, 2]) - yield 1 """) astroid = self.builder.string_build(body) func = astroid.body[0] self.assertIsInstance(func, Function) - yield_from = func.body[0] - yield_ = func.body[1] - self.assertIsInstance(yield_from, Discard) - self.assertIsInstance(yield_from.value, Yield) - self.assertTrue(yield_from.value.yield_from) - self.assertEqual(yield_from.as_string(), + yieldfrom_stmt = func.body[0] + + self.assertIsInstance(yieldfrom_stmt, Discard) + self.assertIsInstance(yieldfrom_stmt.value, YieldFrom) + self.assertEqual(yieldfrom_stmt.as_string(), 'yield from iter([1, 2])') - self.assertIsInstance(yield_, Discard) - self.assertIsInstance(yield_.value, Yield) - self.assertFalse(yield_.value.yield_from) + @require_version('3.3') + def test_yield_from_is_generator(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertIsInstance(func, Function) + self.assertTrue(func.is_generator()) + + @require_version('3.3') + def test_yield_from_as_string(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + value = yield from other() + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertEqual(func.as_string(), body.strip()) # metaclass tests -- cgit v1.2.1