Repository: python/mypy

Issue:
Invalid definition of Tuple in return reports on a confusing line number
Here's an example:
```
class TestTuple:
    @staticmethod
    def test(a: str) -> (bool, str):
        return len(a) < 2, a[:2]
```
And here's the output (I'd expect it to say Line #3):
```
test_invalid_tuple.py:2: error: Invalid tuple literal type
```


Discussion:
This may be because mypy's parser considers decorator a part of the function definition and assigns the function type the first line of the function definition.
Yes, I assumed that that was the case but for someone like me that's new to typing in Python and using mypy to verify it, it was confusing. Now that I now that you're supposed to do `Tuple(bool, str)` and that `(bool, str)` is invalid I would know how to fix it. But it took me a few minutes of thinking "there's not even a tuple on that line" before I realized what I was supposed to do.

On a related note, it is also confusing that you can't using the normal tuple syntax for typing.
> mypy's parser considers decorator a part of the function definition and assigns the function type the first line of the function definition.

Yes, that's why, and I agree it would be better to pin-point errors to the specific line where they occur.

> it is also confusing that you can't using the normal tuple syntax for typing.

Presuming you're referring to the idea of writing `(int, str)` instead of `Tuple[int, str]`, that ship has sailed -- there are some edge cases here that wouldn't work well (unfortunately I don't have time to rehash the discussion, it's probably in one of the mega-threads we had in 2014/2015 about PEP 484.
I'm going to take a stab at this issue.

Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -343,7 +343,8 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
                 return_type = AnyType()
         else:
             arg_types = [a.type_annotation for a in args]
-            return_type = TypeConverter(self.errors, line=n.lineno).visit(n.returns)
+            return_type = TypeConverter(self.errors, line=n.returns.lineno
+                                        if n.returns else n.lineno).visit(n.returns)
 
         for arg, arg_type in zip(args, arg_types):
             self.set_type_optional(arg_type, arg.initializer)
@@ -410,7 +411,7 @@ def make_argument(arg: ast3.arg, default: Optional[ast3.expr], kind: int) -> Arg
                     self.fail(messages.DUPLICATE_TYPE_SIGNATURES, arg.lineno, arg.col_offset)
                 arg_type = None
                 if arg.annotation is not None:
-                    arg_type = TypeConverter(self.errors, line=line).visit(arg.annotation)
+                    arg_type = TypeConverter(self.errors, line=arg.lineno).visit(arg.annotation)
                 elif arg.type_comment is not None:
                     arg_type = parse_type_comment(arg.type_comment, arg.lineno, self.errors)
             return Argument(Var(arg.arg), arg_type, self.visit(default), kind)


Test patch:
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -2044,3 +2044,26 @@ bad = make_list()  # E: Need type annotation for variable
 def foo(__b: int, x: int, y: int) -> int: pass
 foo(x=2, y=2)  # E: Missing positional argument
 foo(y=2)  # E: Missing positional arguments
+
+[case testReturnTypeLineNumberWithDecorator]
+def dec(f): pass
+
+@dec
+def test(a: str) -> (str,): # E: Invalid tuple literal type
+    return None
+
+[case testReturnTypeLineNumberNewLine]
+def fn(a: str
+       ) -> badtype: # E: Name 'badtype' is not defined
+    pass
+
+[case testArgumentTypeLineNumberWithDecorator]
+def dec(f): pass
+
+@dec
+def some_method(self: badtype): pass # E: Name 'badtype' is not defined
+
+[case TestArgumentTypeLineNumberNewline]
+def fn(
+        a: badtype) -> None: # E: Name 'badtype' is not defined
+    pass
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -87,8 +87,8 @@ from typing import overload
 class A: pass
 @overload
 def f(): pass
-@overload # E: "A" expects no type arguments, but 1 given
-def f(x: A[int]) -> None: pass
+@overload
+def f(x: A[int]) -> None: pass # E: "A" expects no type arguments, but 1 given
 def f(*args): pass
 [out]