Skip to content

Commit

Permalink
Reword the error message related to void functions (#15876)
Browse files Browse the repository at this point in the history
Fixes #3226.

Aims to provide better assistance to users who may be confused when
their void functions technically return None.

Co-authored-by: Ilya Priven <ilya.konstantinov@gmail.com>
Co-authored-by: hauntsaninja <hauntsaninja@gmail.com>
  • Loading branch information
3 people authored Sep 2, 2023
1 parent 6884aa2 commit 1655b0c
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 61 deletions.
2 changes: 1 addition & 1 deletion docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ returns ``None``:
# OK: we don't do anything with the return value
f()
# Error: "f" does not return a value [func-returns-value]
# Error: "f" does not return a value (it only ever returns None) [func-returns-value]
if f():
print("not false")
Expand Down
15 changes: 4 additions & 11 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,18 +1021,11 @@ def duplicate_argument_value(self, callee: CallableType, index: int, context: Co

def does_not_return_value(self, callee_type: Type | None, context: Context) -> None:
"""Report an error about use of an unusable type."""
name: str | None = None
callee_type = get_proper_type(callee_type)
if isinstance(callee_type, FunctionLike):
name = callable_name(callee_type)
if name is not None:
self.fail(
f"{capitalize(name)} does not return a value",
context,
code=codes.FUNC_RETURNS_VALUE,
)
else:
self.fail("Function does not return a value", context, code=codes.FUNC_RETURNS_VALUE)
callee_name = callable_name(callee_type) if isinstance(callee_type, FunctionLike) else None
name = callee_name or "Function"
message = f"{name} does not return a value (it only ever returns None)"
self.fail(message, context, code=codes.FUNC_RETURNS_VALUE)

def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None:
"""Report an error about using an deleted type as an rvalue."""
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -553,15 +553,15 @@ from typing import Callable

def f() -> None: pass

x = f() # E: "f" does not return a value [func-returns-value]
x = f() # E: "f" does not return a value (it only ever returns None) [func-returns-value]

class A:
def g(self) -> None: pass

y = A().g() # E: "g" of "A" does not return a value [func-returns-value]
y = A().g() # E: "g" of "A" does not return a value (it only ever returns None) [func-returns-value]

c: Callable[[], None]
z = c() # E: Function does not return a value [func-returns-value]
z = c() # E: Function does not return a value (it only ever returns None) [func-returns-value]

[case testErrorCodeInstantiateAbstract]
from abc import abstractmethod
Expand Down
66 changes: 33 additions & 33 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1079,15 +1079,15 @@ class A:
a: A
o: object
if int():
a = f() # E: "f" does not return a value
a = f() # E: "f" does not return a value (it only ever returns None)
if int():
o = a() # E: Function does not return a value
o = a() # E: Function does not return a value (it only ever returns None)
if int():
o = A().g(a) # E: "g" of "A" does not return a value
o = A().g(a) # E: "g" of "A" does not return a value (it only ever returns None)
if int():
o = A.g(a, a) # E: "g" of "A" does not return a value
A().g(f()) # E: "f" does not return a value
x: A = f() # E: "f" does not return a value
o = A.g(a, a) # E: "g" of "A" does not return a value (it only ever returns None)
A().g(f()) # E: "f" does not return a value (it only ever returns None)
x: A = f() # E: "f" does not return a value (it only ever returns None)
f()
A().g(a)
[builtins fixtures/tuple.pyi]
Expand All @@ -1096,15 +1096,15 @@ A().g(a)
import typing
def f() -> None: pass

if f(): # E: "f" does not return a value
if f(): # E: "f" does not return a value (it only ever returns None)
pass
elif f(): # E: "f" does not return a value
elif f(): # E: "f" does not return a value (it only ever returns None)
pass
while f(): # E: "f" does not return a value
while f(): # E: "f" does not return a value (it only ever returns None)
pass
def g() -> object:
return f() # E: "f" does not return a value
raise f() # E: "f" does not return a value
return f() # E: "f" does not return a value (it only ever returns None)
raise f() # E: "f" does not return a value (it only ever returns None)
[builtins fixtures/exception.pyi]

[case testNoneReturnTypeWithExpressions]
Expand All @@ -1115,13 +1115,13 @@ class A:
def __add__(self, x: 'A') -> 'A': pass

a: A
[f()] # E: "f" does not return a value
f() + a # E: "f" does not return a value
a + f() # E: "f" does not return a value
f() == a # E: "f" does not return a value
a != f() # E: "f" does not return a value
[f()] # E: "f" does not return a value (it only ever returns None)
f() + a # E: "f" does not return a value (it only ever returns None)
a + f() # E: "f" does not return a value (it only ever returns None)
f() == a # E: "f" does not return a value (it only ever returns None)
a != f() # E: "f" does not return a value (it only ever returns None)
cast(A, f())
f().foo # E: "f" does not return a value
f().foo # E: "f" does not return a value (it only ever returns None)
[builtins fixtures/list.pyi]

[case testNoneReturnTypeWithExpressions2]
Expand All @@ -1134,14 +1134,14 @@ class A:

a: A
b: bool
f() in a # E: "f" does not return a value # E: Unsupported right operand type for in ("A")
a < f() # E: "f" does not return a value
f() <= a # E: "f" does not return a value
a in f() # E: "f" does not return a value
-f() # E: "f" does not return a value
not f() # E: "f" does not return a value
f() and b # E: "f" does not return a value
b or f() # E: "f" does not return a value
f() in a # E: "f" does not return a value (it only ever returns None) # E: Unsupported right operand type for in ("A")
a < f() # E: "f" does not return a value (it only ever returns None)
f() <= a # E: "f" does not return a value (it only ever returns None)
a in f() # E: "f" does not return a value (it only ever returns None)
-f() # E: "f" does not return a value (it only ever returns None)
not f() # E: "f" does not return a value (it only ever returns None)
f() and b # E: "f" does not return a value (it only ever returns None)
b or f() # E: "f" does not return a value (it only ever returns None)
[builtins fixtures/bool.pyi]


Expand Down Expand Up @@ -1441,7 +1441,7 @@ if int():
[case testConditionalExpressionWithEmptyCondition]
import typing
def f() -> None: pass
x = 1 if f() else 2 # E: "f" does not return a value
x = 1 if f() else 2 # E: "f" does not return a value (it only ever returns None)

[case testConditionalExpressionWithSubtyping]
import typing
Expand Down Expand Up @@ -1504,7 +1504,7 @@ from typing import List, Union
x = []
y = ""
x.append(y) if bool() else x.append(y)
z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not return a value
z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not return a value (it only ever returns None)
[builtins fixtures/list.pyi]

-- Special cases
Expand Down Expand Up @@ -1604,7 +1604,7 @@ def f(x: int) -> None:
[builtins fixtures/for.pyi]
[out]
main:1: error: The return type of a generator function should be "Generator" or one of its supertypes
main:2: error: "f" does not return a value
main:2: error: "f" does not return a value (it only ever returns None)
main:2: error: Argument 1 to "f" has incompatible type "str"; expected "int"

[case testYieldExpressionWithNone]
Expand All @@ -1624,7 +1624,7 @@ from typing import Iterator
def f() -> Iterator[int]:
yield 5
def g() -> Iterator[int]:
a = yield from f() # E: Function does not return a value
a = yield from f() # E: Function does not return a value (it only ever returns None)

[case testYieldFromGeneratorHasValue]
from typing import Iterator, Generator
Expand All @@ -1639,12 +1639,12 @@ def g() -> Iterator[int]:
[case testYieldFromTupleExpression]
from typing import Generator
def g() -> Generator[int, None, None]:
x = yield from () # E: Function does not return a value
x = yield from (0, 1, 2) # E: Function does not return a value
x = yield from () # E: Function does not return a value (it only ever returns None)
x = yield from (0, 1, 2) # E: Function does not return a value (it only ever returns None)
x = yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "object", expected type "int") \
# E: Function does not return a value
# E: Function does not return a value (it only ever returns None)
x = yield from ("ERROR",) # E: Incompatible types in "yield from" (actual type "str", expected type "int") \
# E: Function does not return a value
# E: Function does not return a value (it only ever returns None)
[builtins fixtures/tuple.pyi]

-- dict(...)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ if int():
if int():
f = o # E: Incompatible types in assignment (expression has type "object", variable has type "Callable[[], None]")
if int():
f = f() # E: Function does not return a value
f = f() # E: Function does not return a value (it only ever returns None)

if int():
f = f
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class B: pass
[case testLvarInitializedToVoid]
import typing
def f() -> None:
a = g() # E: "g" does not return a value
#b, c = g() # "g" does not return a value TODO
a = g() # E: "g" does not return a value (it only ever returns None)
#b, c = g() # "g" does not return a value (it only ever returns None) TODO

def g() -> None: pass
[out]
Expand Down Expand Up @@ -1180,7 +1180,7 @@ for e, f in [[]]: # E: Need type annotation for "e" \
[case testForStatementInferenceWithVoid]
def f() -> None: pass

for x in f(): # E: "f" does not return a value
for x in f(): # E: "f" does not return a value (it only ever returns None)
pass
[builtins fixtures/for.pyi]

Expand Down Expand Up @@ -2118,7 +2118,7 @@ arr = []
arr.append(arr.append(1))
[builtins fixtures/list.pyi]
[out]
main:3: error: "append" of "list" does not return a value
main:3: error: "append" of "list" does not return a value (it only ever returns None)

-- Multipass
-- ---------
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,9 @@ def f() -> None:
def g(x: Optional[int]) -> int:
pass

x = f() # E: "f" does not return a value
f() + 1 # E: "f" does not return a value
g(f()) # E: "f" does not return a value
x = f() # E: "f" does not return a value (it only ever returns None)
f() + 1 # E: "f" does not return a value (it only ever returns None)
g(f()) # E: "f" does not return a value (it only ever returns None)

[case testEmptyReturn]
def f() -> None:
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ class C(B): pass
import typing
def f() -> None: pass

(None, f()) # E: "f" does not return a value
(f(), None) # E: "f" does not return a value
(None, f()) # E: "f" does not return a value (it only ever returns None)
(f(), None) # E: "f" does not return a value (it only ever returns None)
[builtins fixtures/tuple.pyi]


Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-varargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ c: C

f(c) # E: Argument 1 to "f" has incompatible type "C"; expected "A"
f(a, b, c) # E: Argument 3 to "f" has incompatible type "C"; expected "A"
f(g()) # E: "g" does not return a value
f(a, g()) # E: "g" does not return a value
f(g()) # E: "g" does not return a value (it only ever returns None)
f(a, g()) # E: "g" does not return a value (it only ever returns None)
f()
f(a)
f(b)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/pythoneval-asyncio.test
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ try:
finally:
loop.close()
[out]
_program.py:11: error: Function does not return a value
_program.py:11: error: Function does not return a value (it only ever returns None)

[case testErrorReturnIsNotTheSameType]
from typing import Any
Expand Down

0 comments on commit 1655b0c

Please sign in to comment.