Skip to content

Commit

Permalink
Improve Suggestion for empty TupleType syntax error (#9670)
Browse files Browse the repository at this point in the history
* Improve Suggestion for empty TupleType syntax error

The mypy syntax for a function or method that takes zero parameters
is `() -> …`. Some new mypy users will reason by symmetry that the
syntax for a method that returns nothing is likely `(…) -> ()`.
A user who incorrectly annotates a function with `… -> ()` will be
given the suggestion `Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)`.
This suggestion is unlikely to help correct the user's misconception
about how to annotate the return type of a method that does not
explicitly return a value.

This PR adds a case to TupleType syntax error handling that returns a
helpful suggestion in the case where the tuple contains zero items.

Note: The error message casing in TupleType has grown large enough
that it likely warrants relocation to `MessageBuilder`, but as there
is not a `MessageBuilder` in accessible from `TypeAnalyzer` I have
decided to add this single error case the easy way. There is a
preexisting comment about the inaccessibility of `MessageBuilder`
in `RypeAnalyzer`'s `cannot_resolve_type method`.

I have added a test to `check-functions.test` that verifies the new
suggestion is printed when `-> ()` is used as a return type
annotation. I have also tested that a valid return type of
`-> Tuple[()]` remains without error.

* Clarify suggestion message
  • Loading branch information
jonshea authored Oct 31, 2020
1 parent 985a20d commit f6fb60e
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 2 deletions.
5 changes: 4 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,10 @@ 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('Syntax error in type annotation', t, code=codes.SYNTAX)
if len(t.items) == 1:
if len(t.items) == 0:
self.note('Suggestion: Use Tuple[()] instead of () for an empty tuple, or '
'None for a function without a return value', t, code=codes.SYNTAX)
elif len(t.items) == 1:
self.note('Suggestion: Is there a spurious trailing comma?', t, code=codes.SYNTAX)
else:
self.note('Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)', t,
Expand Down
12 changes: 11 additions & 1 deletion test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class B(A):
def f(self, b: str, a: int) -> None: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \
# N: This violates the Liskov substitution principle \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides \
# E: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "str"
# E: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "str"

class C(A):
def f(self, foo: int, bar: str) -> None: pass
Expand Down Expand Up @@ -249,6 +249,16 @@ if int():
class A: pass
[builtins fixtures/tuple.pyi]

[case testReturnEmptyTuple]
from typing import Tuple
def f(x): # type: (int) -> () # E: Syntax error in type annotation \
# N: Suggestion: Use Tuple[()] instead of () for an empty tuple, or None for a function without a return value
pass

def g(x: int) -> Tuple[()]:
pass
[builtins fixtures/tuple.pyi]

[case testFunctionSubtypingWithVoid]
from typing import Callable
f = None # type: Callable[[], None]
Expand Down

0 comments on commit f6fb60e

Please sign in to comment.