From 08a898f85d69c060241dc7f07f003e6e6c4acd50 Mon Sep 17 00:00:00 2001
From: Tim Peters <tim.peters@gmail.com>
Date: Thu, 28 Jun 2001 01:52:22 +0000
Subject: [PATCH] Another "if 0:" hack, this time to complain about otherwise
 invisible "return expr" instances in generators (which latter may be
 generators due to otherwise invisible "yield" stmts hiding in "if 0" blocks).
 This was fun the first time, but this has gotten truly ugly now.

---
 Lib/test/test_generators.py | 33 +++++++++++++++++++
 Python/compile.c            | 63 +++++++++++++++++++++++++++++++++++--
 2 files changed, 94 insertions(+), 2 deletions(-)

diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 3dd468b1cc2..7b78b2bed0e 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -651,6 +651,17 @@ But this is fine:
 >>> list(f())
 [12, 666]
 
+>>> def f():
+...    yield
+Traceback (most recent call last):
+SyntaxError: invalid syntax
+
+>>> def f():
+...    if 0:
+...        yield
+Traceback (most recent call last):
+SyntaxError: invalid syntax
+
 >>> def f():
 ...     if 0:
 ...         yield 1
@@ -704,6 +715,28 @@ But this is fine:
 ...                 yield 2
 >>> type(f())
 <type 'None'>
+
+>>> def f():
+...     if 0:
+...         return
+...     if 0:
+...         yield 2
+>>> type(f())
+<type 'generator'>
+
+
+>>> def f():
+...     if 0:
+...         lambda x:  x        # shouldn't trigger here
+...         return              # or here
+...         def f(i):
+...             return 2*i      # or here
+...         if 0:
+...             return 3        # but *this* sucks (line 8)
+...     if 0:
+...         yield 2             # because it's a generator
+Traceback (most recent call last):
+SyntaxError: 'return' with argument inside generator (<string>, line 8)
 """
 
 __test__ = {"tut":      tutorial_tests,
diff --git a/Python/compile.c b/Python/compile.c
index e82c34c6132..92322fcbd5a 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -2876,6 +2876,45 @@ is_constant_false(struct compiling *c, node *n)
 	return 0;
 }
 
+
+/* Look under n for a return stmt with an expression.
+ * This hack is used to find illegal returns under "if 0:" blocks in
+ * functions already known to be generators (as determined by the symtable
+ * pass).
+ * Return the offending return node if found, else NULL.
+ */
+static node *
+look_for_offending_return(node *n)
+{
+	int i;
+
+	for (i = 0; i < NCH(n); ++i) {
+		node *kid = CHILD(n, i);
+
+		switch (TYPE(kid)) {
+			case classdef:
+			case funcdef:
+			case lambdef:
+				/* Stuff in nested functions & classes doesn't
+				   affect the code block we started in. */
+				return NULL;
+
+			case return_stmt:
+				if (NCH(kid) > 1)
+					return kid;
+				break;
+
+			default: {
+				node *bad = look_for_offending_return(kid);
+				if (bad != NULL)
+					return bad;
+			}
+		}
+	}
+
+	return NULL;
+}			
+
 static void
 com_if_stmt(struct compiling *c, node *n)
 {
@@ -2886,8 +2925,24 @@ com_if_stmt(struct compiling *c, node *n)
 	for (i = 0; i+3 < NCH(n); i+=4) {
 		int a = 0;
 		node *ch = CHILD(n, i+1);
-		if (is_constant_false(c, ch))
+		if (is_constant_false(c, ch)) {
+			/* We're going to skip this block.  However, if this
+			   is a generator, we have to check the dead code
+			   anyway to make sure there aren't any return stmts
+			   with expressions, in the same scope. */
+			if (c->c_flags & CO_GENERATOR) {
+				node *p = look_for_offending_return(n);
+				if (p != NULL) {
+					int savelineno = c->c_lineno;
+					c->c_lineno = p->n_lineno;
+					com_error(c, PyExc_SyntaxError,
+			  	   		"'return' with argument "
+			  	   		"inside generator");
+			  	   	c->c_lineno = savelineno;
+				}
+			}
 			continue;
+		}
 		if (i > 0)
 			com_addoparg(c, SET_LINENO, ch->n_lineno);
 		com_node(c, ch);
@@ -4840,7 +4895,10 @@ symtable_add_def_o(struct symtable *st, PyObject *dict,
 
 #define symtable_add_use(ST, NAME) symtable_add_def((ST), (NAME), USE)
 
-/* Look for a yield stmt under n.  Return 1 if found, else 0. */
+/* Look for a yield stmt under n.  Return 1 if found, else 0.
+   This hack is used to look inside "if 0:" blocks (which are normally
+   ignored) in case those are the only places a yield occurs (so that this
+   function is a generator). */
 static int
 look_for_yield(node *n)
 {
@@ -4853,6 +4911,7 @@ look_for_yield(node *n)
 
 		case classdef:
 		case funcdef:
+		case lambdef:
 			/* Stuff in nested functions and classes can't make
 			   the parent a generator. */
 			return 0;
-- 
2.30.9