Repository: python/mypy

Issue:
Better message for invalid types like List(int)
The error message "invalid type comment or annotation" isn't very helpful if a user writes `List(int)` instead of `List[int]` (or `Optional(int)`).  This seems to be a common error.

Example:

```py
from typing import List

def f():
    # type: () -> List(int)   # invalid type comment or annotation
    return [1]
```

A better message could be something like this:

```
program.py:3: error: Syntax error in type annotation
program.py:3:note: Suggestion: Use List[...] instead of List(...)
```


Discussion:
Another example that's not immediately obvious is missing parens around input eg

```
def f(x):
    # type: List[int] -> Tuple[List[int], Optional[str]]
    ...
```
throws message `error: syntax error in type comment`,
but would be nicer if it was more like
```
error: syntax error in type comment
Suggestion: Expected: `(` but found `L` at column 13
```
or 
```
error: syntax error in type comment
Suggestion: Wrap input in parens
```
or something else to highlight the issue
Here's one that took a while to figure out:
```py
def update_state(conf,               # type: SourceConf
                 new_state,          # type: SourceState
                 tid,                # type: int
                 vid,                # type: int
                 update_ts=None,     # type: Optional[float],
                 ):         # type: (...) -> SourceConf
```
This gave `error: Invalid tuple literal type` on the `def` line.

**spoiler alert**

.

.

.

.

.

.

.

.

.

.

.

.

.

There's an extra comma after `Optional[float]`.
@gvanrossum Well, https://github.com/python/mypy/issues/3871 should help a bit in your case. I'd fix it but I'm currently occupied with my PEP.
It looks like #4323 fixed only _part_ of the problems mentioned here, I am reopening this, just not to lose track.

Resolving patch:
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -389,6 +389,8 @@ def visit_tuple_type(self, t: TupleType) -> Type:
         # generate errors elsewhere, and Tuple[t1, t2, ...] must be used instead.
         if t.implicit and not self.allow_tuple_literal:
             self.fail('Invalid tuple literal type', t)
+            if len(t.items) == 1:
+                self.note_func('Suggestion: Is there a spurious trailing comma?', t)
             return AnyType(TypeOfAny.from_error)
         star_count = sum(1 for item in t.items if isinstance(item, StarType))
         if star_count > 1:


Test patch:
diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test
--- a/test-data/unit/check-fastparse.test
+++ b/test-data/unit/check-fastparse.test
@@ -423,3 +423,15 @@ def call() -> str: pass
 [case testNoCrashOnImportFromStarPython2]
 # flags: --py2
 from . import * # E: No parent module -- cannot perform relative import
+
+[case testSpuriousTrailingComma_python2]
+from typing import Optional
+
+def update_state(tid,                # type: int
+                 vid,                # type: int
+                 update_ts=None,     # type: Optional[float],
+                 ):         # type: (...) -> str
+    pass
+[out]
+main:3: error: Invalid tuple literal type
+main:3: note: Suggestion: Is there a spurious trailing comma?
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
@@ -2212,7 +2212,7 @@ foo(y=2)  # E: Missing positional arguments
 def dec(f): pass
 
 @dec
-def test(a: str) -> (str,): # E: Invalid tuple literal type
+def test(a: str) -> (str,): # E: Invalid tuple literal type # N: Suggestion: Is there a spurious trailing comma?
     return None
 
 [case testReturnTypeLineNumberNewLine]