Repository: hylang/hy

Issue:
Return value not generated when a function contains `yield from`
When a coroutine function uses `yield from`, the compiler no longer generates a return value for the final expression in the function. For example:

``` hy
(defn yield-from-and-return []
  (print "whatever")
  (yield-from (some-coroutine))
  42)

(defn without-yield-from []
  (print "whatever")
  42)
```

expands to:

``` python
def yield_from_and_return():
    print('whatever')
    yield from some_coroutine()
    42

def without_yield_from():
    print('whatever')
    return 42
```



Discussion:
The proximate cause appears to be this code in `compiler.py`:

``` python
        if body.expr:
            if body.contains_yield:
                body += body.expr_as_stmt()
            else:
                body += ast.Return(value=body.expr,
                                   lineno=body.expr.lineno,
                                   col_offset=body.expr.col_offset)
```

It looks like it should be enough to weaken the condition so that the `ast.Return` is always generated in Python 3 (actually, Python 3.3+ I think?)

Sounds right to me, that was the idea I had in my head for this!

On Thu, Dec 31, 2015 at 4:22 AM, timmartin notifications@github.com wrote:

> The proximate cause appears to be this code in compiler.py:
> 
> ```
>     if body.expr:
>         if body.contains_yield:
>             body += body.expr_as_stmt()
>         else:
>             body += ast.Return(value=body.expr,
>                                lineno=body.expr.lineno,
>                                col_offset=body.expr.col_offset)
> ```
> 
> It looks like it should be enough to weaken the condition so that the
> ast.Return is always generated in Python 3 (actually, Python 3.3+ I
> think?)
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/hylang/hy/issues/1010#issuecomment-168154637.

## 

:wq


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -2309,7 +2309,10 @@ def compile_function_def(self, expression):
             return ret
 
         if body.expr:
-            if body.contains_yield:
+            if body.contains_yield and not PY33:
+                # Prior to PEP 380 (introduced in Python 3.3)
+                # generators may not have a value in a return
+                # statement.
                 body += body.expr_as_stmt()
             else:
                 body += ast.Return(value=body.expr,


Test patch:
diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py
--- a/tests/compilers/test_compiler.py
+++ b/tests/compilers/test_compiler.py
@@ -26,6 +26,8 @@
 from hy.models.expression import HyExpression
 from hy.models.list import HyList
 from hy.models.symbol import HySymbol
+from hy.models.integer import HyInteger
+from hy._compat import PY33
 
 if sys.version_info[0] <= 2 and sys.version_info[1] <= 6:
     import unittest2 as unittest
@@ -98,3 +100,37 @@ def test_compiler_bare_names(self):
         expr = ret.expr
         self.assertIsInstance(expr, ast.Name)
         self.assertEqual(expr.id, "c")
+
+    def test_compiler_yield_return(self):
+        """
+        Check that the compiler correctly generates return statements for
+        a generator function. In Python versions prior to 3.3, the return
+        statement in a generator can't take a value, so the final expression
+        should not generate a return statement. From 3.3 onwards a return
+        value should be generated.
+        """
+        ret = self.c.compile_function_def(
+            self._make_expression(HySymbol("fn"),
+                                  HyList(),
+                                  HyExpression([HySymbol("yield"),
+                                                HyInteger(2)]),
+                                  HyExpression([HySymbol("+"),
+                                                HyInteger(1),
+                                                HyInteger(1)])))
+
+        self.assertEqual(len(ret.stmts), 1)
+        stmt = ret.stmts[0]
+        self.assertIsInstance(stmt, ast.FunctionDef)
+        body = stmt.body
+        self.assertEquals(len(body), 2)
+        self.assertIsInstance(body[0], ast.Expr)
+        self.assertIsInstance(body[0].value, ast.Yield)
+
+        if PY33:
+            # From 3.3+, the final statement becomes a return value
+            self.assertIsInstance(body[1], ast.Return)
+            self.assertIsInstance(body[1].value, ast.BinOp)
+        else:
+            # In earlier versions, the expression is not returned
+            self.assertIsInstance(body[1], ast.Expr)
+            self.assertIsInstance(body[1].value, ast.BinOp)