Skip to content

[2.7] bpo-10544: Deprecate "yield" in comprehensions and generator expressions in Py3k mode. (GH-4579) #4676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,8 @@ def make_bad_fd():
file.close()
unlink(TESTFN)

def check_syntax_error(testcase, statement, lineno=None, offset=None):
with testcase.assertRaises(SyntaxError) as cm:
def check_syntax_error(testcase, statement, errtext='', lineno=None, offset=None):
with testcase.assertRaisesRegexp(SyntaxError, errtext) as cm:
compile(statement, '<test string>', 'exec')
err = cm.exception
if lineno is not None:
Expand Down
12 changes: 3 additions & 9 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,13 +1524,7 @@ def printsolution(self, x):
[None]



An obscene abuse of a yield expression within a generator expression:

>>> list((yield 21) for i in range(4))
[21, None, 21, None, 21, None, 21, None]

And a more sane, but still weird usage:
Yield is allowed only in the outermost iterable in generator expression:

>>> def f(): list(i for i in [(yield 26)])
>>> type(f())
Expand Down Expand Up @@ -1571,7 +1565,7 @@ def printsolution(self, x):
>>> def f(): return lambda x=(yield): 1
Traceback (most recent call last):
...
SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.coroutine[22]>, line 1)
SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.coroutine[21]>, line 1)

>>> def f(): x = yield = y
Traceback (most recent call last):
Expand Down Expand Up @@ -1784,7 +1778,7 @@ def printsolution(self, x):
>>> type(f())
<type 'generator'>

>>> def f(): x=(i for i in (yield) if (yield))
>>> def f(): x=(i for i in (yield) if i)
>>> type(f())
<type 'generator'>

Expand Down
40 changes: 40 additions & 0 deletions Lib/test/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,46 @@ def g2(): return 1
def testYield(self):
check_syntax_error(self, "class foo:yield 1")

def test_yield_in_comprehensions(self):
# Check yield in comprehensions
def g(): [x for x in [(yield 1)]]

def check(code, warntext):
with check_py3k_warnings((warntext, DeprecationWarning)):
compile(code, '<test string>', 'exec')
if sys.py3kwarning:
import warnings
with warnings.catch_warnings():
warnings.filterwarnings('error', category=DeprecationWarning)
with self.assertRaises(SyntaxError) as cm:
compile(code, '<test string>', 'exec')
self.assertIn(warntext, str(cm.exception))

check("def g(): [(yield x) for x in ()]",
"'yield' inside list comprehension")
check("def g(): [x for x in () if not (yield x)]",
"'yield' inside list comprehension")
check("def g(): [y for x in () for y in [(yield x)]]",
"'yield' inside list comprehension")
check("def g(): {(yield x) for x in ()}",
"'yield' inside set comprehension")
check("def g(): {(yield x): x for x in ()}",
"'yield' inside dict comprehension")
check("def g(): {x: (yield x) for x in ()}",
"'yield' inside dict comprehension")
check("def g(): ((yield x) for x in ())",
"'yield' inside generator expression")
with check_py3k_warnings(("'yield' inside list comprehension",
DeprecationWarning)):
check_syntax_error(self, "class C: [(yield x) for x in ()]")
check("class C: ((yield x) for x in ())",
"'yield' inside generator expression")
with check_py3k_warnings(("'yield' inside list comprehension",
DeprecationWarning)):
check_syntax_error(self, "[(yield x) for x in ()]")
check("((yield x) for x in ())",
"'yield' inside generator expression")

def testRaise(self):
# 'raise' test [',' test]
try: raise RuntimeError, 'just testing'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Yield expressions are now deprecated in comprehensions and generator
expressions when checking Python 3 compatibility. They are still
permitted in the definition of the outermost iterable, as that is
evaluated directly in the enclosing scope.
88 changes: 71 additions & 17 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,14 @@ PyTypeObject PySTEntry_Type = {
};

static int symtable_analyze(struct symtable *st);
static int symtable_warn(struct symtable *st, char *msg, int lineno);
static int symtable_warn(struct symtable *st,
PyObject *warn, const char *msg, int lineno);
static int symtable_enter_block(struct symtable *st, identifier name,
_Py_block_ty block, void *ast, int lineno);
static int symtable_exit_block(struct symtable *st, void *ast);
static int symtable_visit_stmt(struct symtable *st, stmt_ty s);
static int symtable_visit_expr(struct symtable *st, expr_ty s);
static int symtable_visit_listcomp(struct symtable *st, expr_ty e);
static int symtable_visit_genexp(struct symtable *st, expr_ty s);
static int symtable_visit_setcomp(struct symtable *st, expr_ty e);
static int symtable_visit_dictcomp(struct symtable *st, expr_ty e);
Expand Down Expand Up @@ -796,14 +798,18 @@ symtable_analyze(struct symtable *st)


static int
symtable_warn(struct symtable *st, char *msg, int lineno)
symtable_warn(struct symtable *st, PyObject *warn, const char *msg, int lineno)
{
if (PyErr_WarnExplicit(PyExc_SyntaxWarning, msg, st->st_filename,
lineno, NULL, NULL) < 0) {
if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
if (lineno < 0) {
lineno = st->st_cur->ste_lineno;
}
if (PyErr_WarnExplicit(warn, msg, st->st_filename, lineno, NULL, NULL) < 0) {
if (PyErr_ExceptionMatches(warn)) {
/* Replace the warning exception with a SyntaxError
to get a more accurate error report */
PyErr_Clear();
PyErr_SetString(PyExc_SyntaxError, msg);
PyErr_SyntaxLocation(st->st_filename,
st->st_cur->ste_lineno);
PyErr_SyntaxLocation(st->st_filename, lineno);
}
return 0;
}
Expand Down Expand Up @@ -1153,7 +1159,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
PyOS_snprintf(buf, sizeof(buf),
GLOBAL_AFTER_USE,
c_name);
if (!symtable_warn(st, buf, s->lineno))
if (!symtable_warn(st, PyExc_SyntaxWarning, buf, s->lineno))
return 0;
}
if (!symtable_add_def(st, name, DEF_GLOBAL))
Expand Down Expand Up @@ -1221,8 +1227,8 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
VISIT_SEQ(st, expr, e->v.Set.elts);
break;
case ListComp_kind:
VISIT(st, expr, e->v.ListComp.elt);
VISIT_SEQ(st, comprehension, e->v.ListComp.generators);
if (!symtable_visit_listcomp(st, e))
return 0;
break;
case GeneratorExp_kind:
if (!symtable_visit_genexp(st, e))
Expand Down Expand Up @@ -1420,12 +1426,11 @@ symtable_visit_alias(struct symtable *st, alias_ty a)
return r;
}
else {
if (st->st_cur->ste_type != ModuleBlock) {
int lineno = st->st_cur->ste_lineno;
if (!symtable_warn(st, IMPORT_STAR_WARNING, lineno)) {
Py_DECREF(store_name);
return 0;
}
if (st->st_cur->ste_type != ModuleBlock &&
!symtable_warn(st, PyExc_SyntaxWarning, IMPORT_STAR_WARNING, -1))
{
Py_DECREF(store_name);
return 0;
}
st->st_cur->ste_unoptimized |= OPT_IMPORT_STAR;
Py_DECREF(store_name);
Expand Down Expand Up @@ -1509,7 +1514,10 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
!symtable_enter_block(st, scope_name, FunctionBlock, (void *)e, 0)) {
return 0;
}
st->st_cur->ste_generator = is_generator;
/* In order to check for yield expressions under '-3', we clear
the generator flag, and restore it at the end */
is_generator |= st->st_cur->ste_generator;
st->st_cur->ste_generator = 0;
/* Outermost iter is received as an argument */
if (!symtable_implicit_arg(st, 0)) {
symtable_exit_block(st, (void *)e);
Expand All @@ -1527,9 +1535,55 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
if (value)
VISIT_IN_BLOCK(st, expr, value, (void*)e);
VISIT_IN_BLOCK(st, expr, elt, (void*)e);
if (Py_Py3kWarningFlag && st->st_cur->ste_generator) {
const char *msg = (
(e->kind == SetComp_kind) ? "'yield' inside set comprehension" :
(e->kind == DictComp_kind) ? "'yield' inside dict comprehension" :
"'yield' inside generator expression");
if (!symtable_warn(st, PyExc_DeprecationWarning, msg, -1)) {
symtable_exit_block(st, (void *)e);
return 0;
}
}
st->st_cur->ste_generator |= is_generator;
return symtable_exit_block(st, (void *)e);
}

static int
symtable_visit_listcomp(struct symtable *st, expr_ty e)
{
asdl_seq *generators = e->v.ListComp.generators;
int i, is_generator;
/* In order to check for yield expressions under '-3', we clear
the generator flag, and restore it at the end */
is_generator = st->st_cur->ste_generator;
st->st_cur->ste_generator = 0;
VISIT(st, expr, e->v.ListComp.elt);
for (i = 0; i < asdl_seq_LEN(generators); i++) {
comprehension_ty lc = (comprehension_ty)asdl_seq_GET(generators, i);
VISIT(st, expr, lc->target);
if (i == 0 && !st->st_cur->ste_generator) {
/* 'yield' in the outermost iterator doesn't cause a warning */
VISIT(st, expr, lc->iter);
is_generator |= st->st_cur->ste_generator;
st->st_cur->ste_generator = 0;
}
else {
VISIT(st, expr, lc->iter);
}
VISIT_SEQ(st, expr, lc->ifs);
}

if (Py_Py3kWarningFlag && st->st_cur->ste_generator) {
const char *msg = "'yield' inside list comprehension";
if (!symtable_warn(st, PyExc_DeprecationWarning, msg, -1)) {
return 0;
}
}
st->st_cur->ste_generator |= is_generator;
return 1;
}

static int
symtable_visit_genexp(struct symtable *st, expr_ty e)
{
Expand Down