8000 Improve message when function is missing return type (#6773) · python/mypy@9c74efd · GitHub
[go: up one dir, main page]

Skip to content

Commit 9c74efd

Browse files
allisonkingilevkivskyi
authored andcommitted
Improve message when function is missing return type (#6773)
Fixes #5952 This partially relies on argument names like 'self' or 'cls', but this is OK, since it is only used to decide the error message wording.
1 parent 1ea1122 commit 9c74efd

File tree

8 files changed

+58
-31
lines changed

8 files changed

+58
-31
lines changed

mypy/checker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from mypy.scope import Scope
6767
from mypy.typeops import tuple_fallback
6868
from mypy import state
69+
from mypy.traverser import has_return_statement
6970

7071
MYPY = False
7172
if MYPY:
@@ -987,7 +988,13 @@ def is_unannotated_any(t: Type) -> bool:
987988
check_incomplete_defs = self.options.disallow_incomplete_defs and has_explicit_annotation
988989
if show_untyped and (self.options.disallow_untyped_defs or check_incomplete_defs):
989990
if fdef.type is None and self.options.disallow_untyped_defs:
990-
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)
991+
if (not fdef.arguments or (len(fdef.arguments) == 1 and
992+
(fdef.arg_names[0] == 'self' or fdef.arg_names[0] == 'cls'))):
993+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
994+
if not has_return_statement(fdef):
995+
self.note('Use "-> None" if function does not return a value', fdef)
996+
else:
997+
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)
991998
elif isinstance(fdef.type, CallableType):
992999
ret_type = fdef.type.ret_type
9931000
if is_unannotated_any(ret_type):

mypy/stubgen.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
from mypy.find_sources import create_source_list, InvalidSourceList
8787
from mypy.build import build
8888
from mypy.errors import CompileError
89+
from mypy.traverser import has_return_statement
8990

9091
MYPY = False
9192
if MYPY:
@@ -853,26 +854,6 @@ def find_self_initializers(fdef: FuncBase) -> List[Tuple[str, Expression]]:
853854
return traverser.results
854855

855856

856-
class ReturnSeeker(mypy.traverser.TraverserVisitor):
857-
def __init__(self) -> None:
858-
self.found = False
859-
860-
def visit_return_stmt(self, o: ReturnStmt) -> None:
861-
if o.expr is None or isinstance(o.expr, NameExpr) and o.expr.name == 'None':
862-
return
863-
self.found = True
864-
865-
866-
def has_return_statement(fdef: FuncBase) -> bool:
867-
"""Find if a function has a non-trivial return statement.
868-
869-
Plain 'return' and 'return None' don't count.
870-
"""
871-
seeker = ReturnSeeker()
872-
fdef.accept(seeker)
873-
return seeker.found
874-
875-
876857
def get_qualified_name(o: Expression) -> str:
877858
if isinstance(o, NameExpr):
878859
return o.name

mypy/traverser.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
from mypy.visitor import NodeVisitor
44
from mypy.nodes import (
5-
Block, MypyFile, FuncItem, CallExpr, ClassDef, Decorator, FuncDef,
5+
Block, MypyFile, FuncBase, FuncItem, CallExpr, ClassDef, Decorator, FuncDef,
66
ExpressionStmt, AssignmentStmt, OperatorAssignmentStmt, WhileStmt,
77
ForStmt, ReturnStmt, AssertStmt, DelStmt, IfStmt, RaiseStmt,
8-
TryStmt, WithStmt, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealExpr,
8+
TryStmt, WithStmt, NameExpr, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealExpr,
99
UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr,
1010
GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension,
1111
ConditionalExpr, TypeApplication, ExecStmt, Import, ImportFrom,
1212
LambdaExpr, ComparisonExpr, OverloadedFuncDef, YieldFromExpr,
13-
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr, REVEAL_TYPE
13+
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr, REVEAL_TYPE,
14+
Statement
1415
)
1516

1617

@@ -285,3 +286,23 @@ def visit_exec_stmt(self, o: ExecStmt) -> None:
285286
o.globals.accept(self)
286287
if o.locals:
287288
o.locals.accept(self)
289+
290+
291+
class ReturnSeeker(TraverserVisitor):
292+
def __init__(self) -> None:
293+
self.found = False
294+
295+
def visit_return_stmt(self, o: ReturnStmt) -> None:
296+
if (o.expr is None or isinstance(o.expr, NameExpr) and o.expr.name == 'None'):
297+
return
298+
self.found = True
299+
300+
301+
def has_return_statement(fdef: FuncBase) -> bool:
302+
"""Find if a function has a non-trivial return statement.
303+
304+
Plain 'return' and 'return None' don't count.
305+
"""
306+
seeker = ReturnSeeker()
307+
fdef.accept(seeker)
308+
return seeker.found

test-data/unit/check-38.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class C: ... # E: Name 'C' already defined on line 4
1010
def d(f): ... # type: ignore
1111
@d
1212

13-
def f(): ... # E: Function is missing a type annotation
13+
def f(): ... # E: Function is missing a return type annotation \
14+
# N: Use "-> None" if function does not return a value
1415

1516
[case testIgnoreDecoratedFunction1]
1617
# flags: --disallow-untyped-defs --warn-unused-ignores

test-data/unit/check-async-await.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,8 @@ async def f() -> AsyncGenerator[Any, Any]:
694694
async def h() -> Any:
695695
yield 0
696696

697-
async def g(): # E: Function is missing a type annotation
697+
async def g(): # E: Function is missing a return type annotation \
698+
# N: Use "-> None" if function does not return a value
698699
yield 0
699700
[builtins fixtures/async_await.pyi]
700701
[typing fixtures/typing-full.pyi]

test-data/unit/check-flags.test

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,27 @@ lambda x: x
3737
def f():
3838
1 + "str"
3939
[out]
40-
main:2: error: Function is missing a type annotation
40+
main:2: error: Function is missing a return type annotation
41+
main:2: note: Use "-> None" if function does not return a value
42+
43+
[case testUnannotatedReturnWithOnlySelfArgument]
44+
# flags: --disallow-untyped-defs
45+
def f(self): pass
46+
[out]
47+
main:2: error: Function is missing a return type annotation
48+
main:2: note: Use "-> None" if function does not return a value
49+
50+
[case testUnannotatedReturnWithNontrivialReturn]
51+
# flags: --disallow-untyped-defs
52+
def f(): return 1
53+
[out]
54+
main:2: error: Function is missing a return type annotation
4155

4256
[case testUntypedAsyncDef]
4357
# flags: --disallow-untyped-defs
44-
async def f(): # E: Function is missing a type annotation
45-
pass
58+
async def f(): # E: Function is missing a return type annotation \
59+
# N: Use "-> None" if function does not return a value
60+
pass
4661
[builtins fixtures/async_await.pyi]
4762
[typing fixtures/typing-full.pyi]
4863

test-data/unit/cmdline.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ disallow_any_generics = True
10761076
def get_tasks(self):
10771077
return 'whatever'
10781078
[out]
1079-
a.py:1: error: Function is missing a type annotation
1079+
a.py:1: error: Function is missing a return type annotation
10801080

10811081
[case testMissingFile]
10821082
# cmd: mypy nope.py

test-data/unit/daemon.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ $ dmypy run -- foo.py --follow-imports=error
3535
Restarting: configuration changed
3636
Daemon stopped
3737
Daemon started
38-
foo.py:1: error: Function is missing a type annotation
38+
foo.py:1: error: Function is missing a return type annotation
39+
foo.py:1: note: Use "-> None" if function does not return a value
3940
== Return code: 1
4041
$ {python} -c "print('def f() -> None: pass')" >foo.py
4142
$ dmypy run -- foo.py --follow-imports=error

0 commit comments

Comments
 (0)
0