Repository: python/mypy

Issue:
Internal error - AttributeError: 'AssignmentStmt' object has no attribute 'expr'
Mypy fails on on this example:

```
import itertools

def chunk(iterable, chunk_size):
    key = lambda (i, elem): i  # type: ignore
```

(note that type: ignore doens't help)

Stacktrace:

```
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.5/bin/mypy", line 6, in <module>
    main(__file__)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/main.py", line 53, in main
    type_check_only(sources, bin_dir, options)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/main.py", line 96, in type_check_only
    python_path=options.python_path)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/build.py", line 203, in build
    result = manager.process(initial_states)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/build.py", line 386, in process
    next.process()
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/build.py", line 883, in process
    self.type_checker().visit_file(self.tree, self.tree.path)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 385, in visit_file
    self.accept(d)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 394, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/nodes.py", line 382, in accept
    return visitor.visit_func_def(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 509, in visit_func_def
    self.check_func_item(defn, name=defn.name())
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 567, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 663, in check_func_def
    self.accept_in_frame(item.body)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 409, in accept_in_frame
    answer = self.accept(node, type_context)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 394, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/nodes.py", line 526, in accept
    return visitor.visit_block(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 1058, in visit_block
    self.accept(s)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 394, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/nodes.py", line 564, in accept
    return visitor.visit_assignment_stmt(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 1067, in visit_assignment_stmt
    self.check_assignment(s.lvalues[-1], s.rvalue, s.type is None)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 1112, in check_assignment
    self.infer_variable_type(inferred, lvalue, self.accept(rvalue),
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 394, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/nodes.py", line 1193, in accept
    return visitor.visit_func_expr(self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 1984, in visit_func_expr
    return self.expr_checker.visit_func_expr(e)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checkexpr.py", line 1200, in visit_func_expr
    ret_type = e.expr().accept(self.chk)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/nodes.py", line 1190, in expr
    return ret.expr
AttributeError: 'AssignmentStmt' object has no attribute 'expr'
```



Discussion:
So this seems a bug in parsing lambda args. Note that `lambda (x, y): ...` is not the same as `lambda x, y: ...` -- the former is a tuple argument of length 2.

I think the issue here is that
- the `FuncExpr` code is assuming that the body of a lambda is always one statement, a return statement:

```
    def expr(self) -> Node:
        """Return the expression (the body) of the lambda."""
        ret = cast(ReturnStmt, self.body.body[0])
        return ret.expr
```
- The way we handled #934, in PR #929, made that not true when we have a Python 2 tuple-parameter to desugar -- the body can start instead with an AssignmentStmt. See in particular here: https://github.com/jukkal/mypy/blob/master/mypy/nodes.py#L291

Probably the immediate fix is to complicate `FuncExpr.expr` to handle this case -- maybe just change `body[0]` to `body[-1]`? That'll still feel a little awkward and potentially fragile. But it's probably OK in practice because we're not likely to add more desugaring like #929 -- we believe we're done parsing all syntax.


Resolving patch:
diff --git a/mypy/errors.py b/mypy/errors.py
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -196,7 +196,7 @@ def raise_error(self) -> None:
 
         Render the messages suitable for displaying.
         """
-        raise CompileError(self.messages())
+        raise CompileError(self.messages(), use_stdout=True)
 
     def messages(self) -> List[str]:
         """Return a string list that represents the error messages.
@@ -338,10 +338,12 @@ class CompileError(Exception):
     """
 
     messages = None  # type: List[str]
+    use_stdout = False
 
-    def __init__(self, messages: List[str]) -> None:
+    def __init__(self, messages: List[str], use_stdout: bool = False) -> None:
         super().__init__('\n'.join(messages))
         self.messages = messages
+        self.use_stdout = use_stdout
 
 
 def remove_path_prefix(path: str, prefix: str) -> str:
diff --git a/mypy/main.py b/mypy/main.py
--- a/mypy/main.py
+++ b/mypy/main.py
@@ -51,8 +51,9 @@ def main(script_path: str) -> None:
         else:
             raise RuntimeError('unsupported target %d' % options.target)
     except CompileError as e:
+        f = sys.stdout if e.use_stdout else sys.stderr
         for m in e.messages:
-            sys.stderr.write(m + '\n')
+            f.write(m + '\n')
         sys.exit(1)
 
 
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -1180,7 +1180,7 @@ def name(self) -> str:
 
     def expr(self) -> Node:
         """Return the expression (the body) of the lambda."""
-        ret = cast(ReturnStmt, self.body.body[0])
+        ret = cast(ReturnStmt, self.body.body[-1])
         return ret.expr
 
     def accept(self, visitor: NodeVisitor[T]) -> T:


Test patch:
diff --git a/mypy/test/data/check-python2.test b/mypy/test/data/check-python2.test
--- a/mypy/test/data/check-python2.test
+++ b/mypy/test/data/check-python2.test
@@ -172,3 +172,7 @@ def f(x, y, z): # type: (..., int) -> None
 [out]
 main: note: In function "f":
 main:1: error: Parse error before ): Ellipses cannot accompany other argument types in function type signature.
+
+[case testLambdaTupleArgInPython2]
+lambda (x, y): x + y
+[out]