Skip to content

Improve error messages for missing annotations (return type + arguments) #19125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
766d4e7
syntax error messages capitalization
charulatalodha May 19, 2025
c48241d
Capitalise only first non space word in syntax err msg
May 19, 2025
afc2385
Merge branch 'master' into master
charulatalodha May 19, 2025
250f0f2
Update fastparse.py
amansomething May 19, 2025
a4dcdf5
Merge pull request #1 from amansomething/patch-1
charulatalodha May 19, 2025
a791f96
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 19, 2025
45d0000
Fix Test suite py39-windows-64, py39-ubuntu, mypyc-compiled
May 19, 2025
1a53c8f
Merge branch 'master' into master
charulatalodha May 20, 2025
40e3eb5
Merge branch 'python:master' into master
charulatalodha May 21, 2025
0eb4a99
update check_for_missing_annotations to give note for None return typ…
May 21, 2025
284fe23
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2025
7b89fd9
fix err code for ARGUMENT_TYPE_EXPECTED NO_UNTYPED_DEF
May 21, 2025
a14d4a2
Merge branch 'master' of https://github.com/charulatalodha/mypy
May 21, 2025
54eed1a
update FUNCTION_TYPE_EXPECTED err handling, update tests
May 21, 2025
547b546
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2025
e3bba27
fix logic for return type annotation vs type annotation for when fdef…
May 21, 2025
e0d5071
Merge branch 'master' of https://github.com/charulatalodha/mypy
May 21, 2025
d5cefb8
fix test
May 21, 2025
e21aa69
Merge branch 'master' into master
charulatalodha May 27, 2025
86710b0
Merge branch 'master' into master
charulatalodha May 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 51 additions & 20 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1633,32 +1633,43 @@ def is_reverse_op_method(self, method_name: str) -> bool:

def check_for_missing_annotations(self, fdef: FuncItem) -> None:
# Check for functions with unspecified/not fully specified types.
def is_unannotated_any(t: Type) -> bool:
def is_unannotated_any(t: Type | None) -> bool:
if not isinstance(t, ProperType):
return False
return isinstance(t, AnyType) and t.type_of_any == TypeOfAny.unannotated

unannotated_args = [
a.variable.name
for a in fdef.arguments
if not (a.variable.is_cls or a.variable.is_self)
and is_unannotated_any(a.variable.type)
]

has_explicit_annotation = isinstance(fdef.type, CallableType) and any(
not is_unannotated_any(t) for t in fdef.type.arg_types + [fdef.type.ret_type]
unannotated_args + [fdef.type.ret_type]
)

show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub
check_incomplete_defs = self.options.disallow_incomplete_defs and has_explicit_annotation
if show_untyped and (self.options.disallow_untyped_defs or check_incomplete_defs):
if fdef.type is None and self.options.disallow_untyped_defs:
if not fdef.arguments or (
len(fdef.arguments) == 1
and (fdef.arg_names[0] == "self" or fdef.arg_names[0] == "cls")
):
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
if not has_return_statement(fdef) and not fdef.is_generator:
self.note(
'Use "-> None" if function does not return a value',
def handle_function_args_type_annotation() -> None:
if isinstance(fdef.type, CallableType):
if unannotated_args:
if len(unannotated_args) < 5:
self.fail(
message_registry.ARGUMENT_TYPE_EXPECTED.format(
", ".join(f'"{arg}"' for arg in unannotated_args)
),
fdef,
code=codes.NO_UNTYPED_DEF,
)
else:
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)
else:
self.fail(
message_registry.ARGUMENT_TYPE_EXPECTED.format(
str(", ".join(f'"{arg}"' for arg in unannotated_args[:5]) + "...")
),
fdef,
)

def handle_return_type_annotation() -> None:
if fdef.type is None and self.options.disallow_untyped_defs:
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
elif isinstance(fdef.type, CallableType):
ret_type = get_proper_type(fdef.type.ret_type)
if is_unannotated_any(ret_type):
Expand All @@ -1671,8 +1682,29 @@ def is_unannotated_any(t: Type) -> bool:
elif fdef.is_coroutine and isinstance(ret_type, Instance):
if is_unannotated_any(self.get_coroutine_return_type(ret_type)):
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
if any(is_unannotated_any(t) for t in fdef.type.arg_types):
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef)

show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub
check_incomplete_defs = self.options.disallow_incomplete_defs and has_explicit_annotation

if show_untyped and (self.options.disallow_untyped_defs or check_incomplete_defs):
if fdef.type is None and self.options.disallow_untyped_defs:
if not fdef.arguments or (
len(fdef.arguments) == 1
and (fdef.arguments[0].variable.is_self or fdef.arguments[0].variable.is_cls)
):
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
else:
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)

if not has_return_statement(fdef) and not fdef.is_generator:
self.note(
'Use "-> None" if function does not return a value',
fdef,
code=codes.NO_UNTYPED_DEF,
)
elif isinstance(fdef.type, CallableType):
handle_return_type_annotation()
handle_function_args_type_annotation()

def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
self_type = fill_typevars_with_any(fdef.info)
Expand Down Expand Up @@ -6787,7 +6819,6 @@ def refine_away_none_in_comparison(
if_map, else_map = {}, {}

if not non_optional_types or (len(non_optional_types) != len(chain_indices)):

# Narrow e.g. `Optional[A] == "x"` or `Optional[A] is "x"` to `A` (which may be
# convenient but is strictly not type-safe):
for i in narrowable_operand_indices:
Expand Down
3 changes: 2 additions & 1 deletion mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
"Function is missing a return type annotation", codes.NO_UNTYPED_DEF
)
ARGUMENT_TYPE_EXPECTED: Final = ErrorMessage(
"Function is missing a type annotation for one or more arguments", codes.NO_UNTYPED_DEF
"Function is missing a type annotation for one or more arguments: {}",
code=codes.NO_UNTYPED_DEF,
)
KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE: Final = ErrorMessage(
'Keyword argument only valid with "str" key type in call to "dict"'
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-columns.test
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ if int():
def f(x: int): # E:5: Function is missing a return type annotation
pass

def g(x): # E:5: Function is missing a type annotation
def g(x): # E:5: Function is missing a type annotation # N:5: Use "-> None" if function does not return a value
pass

[case testColumnNameIsNotDefined]
Expand Down
11 changes: 9 additions & 2 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -362,13 +362,13 @@ a.x # E: Item "B" of "Union[A, B]" has no attribute "x" [union-attr]
[case testErrorCodeFunctionHasNoAnnotation]
# flags: --disallow-untyped-defs

def f(x): # E: Function is missing a type annotation [no-untyped-def]
def f(x): # E: Function is missing a type annotation [no-untyped-def] # N: Use "-> None" if function does not return a value
pass

def g(x: int): # E: Function is missing a return type annotation [no-untyped-def]
pass

def h(x) -> None: # E: Function is missing a type annotation for one or more arguments [no-untyped-def]
def h(x) -> None: # E: Function is missing a type annotation for one or more arguments: "x" [no-untyped-def]
pass

def gen(): # E: Function is missing a return type annotation [no-untyped-def]
Expand All @@ -382,6 +382,7 @@ async def asyncf(): # E: Function is missing a return type annotation [no-unty

async def asyncf2(x: int): # E: Function is missing a return type annotation [no-untyped-def]
return 0

[typing fixtures/typing-async.pyi]
[builtins fixtures/tuple.pyi]

Expand Down Expand Up @@ -685,6 +686,12 @@ g(p) # type: ignore[arg-type]
def f(): # type: ignore[no-untyped-def]
pass

[case testErrorCodeNoneReturnNoteIgnoreMultipleArgs]
# flags: --disallow-untyped-defs

def f(a, b:int, c): # type: ignore[no-untyped-def]
pass

[case testErrorCodeVarianceNoteIgnore]
from typing import List
def f(x: List[object]) -> None: pass
Expand Down
52 changes: 35 additions & 17 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,36 @@
def f(x): pass
[out]
main:2: error: Function is missing a type annotation
main:2: note: Use "-> None" if function does not return a value

[case testUnannotatedArgumentSingle]
# flags: --disallow-untyped-defs
def f(x) -> int: return 1
[out]
main:2: error: Function is missing a type annotation for one or more arguments: "x"

[case testUnannotatedArgument]
[case testUnannotatedArgumentLessThan5]
# flags: --disallow-untyped-defs
def f(x) -> int: pass
def f(x, y, z, a:int, b) -> int: return 1
[out]
main:2: error: Function is missing a type annotation for one or more arguments
main:2: error: Function is missing a type annotation for one or more arguments: "x", "y", "z", "b"

[case testNoArgumentFunction]
[case testUnannotatedArgument5orMore]
# flags: --disallow-untyped-defs
def f() -> int: pass
def f(x, y, z, a:int, b, c, d, e, f:int) -> int: return 1
[out]
main:2: error: Function is missing a type annotation for one or more arguments: "x", "y", "z", "b", "c"...

[case testNoArgumentValidReturnType]
# flags: --disallow-untyped-defs
def f() -> int: return 1
[out]

[case testNoArgumentMissingReturnTypeFunction]
# flags: --disallow-untyped-defs
def f(): return 5
[out]
main:2: error: Function is missing a return type annotation

[case testUnannotatedReturn]
# flags: --disallow-untyped-defs
Expand Down Expand Up @@ -44,7 +63,7 @@ main:2: note: Use "-> None" if function does not return a value
# flags: --disallow-untyped-defs
def f(self): pass
[out]
main:2: error: Function is missing a return type annotation
main:2: error: Function is missing a type annotation
main:2: note: Use "-> None" if function does not return a value

[case testUnannotatedReturnWithNontrivialReturn]
Expand All @@ -55,15 +74,14 @@ main:2: error: Function is missing a return type annotation

[case testUntypedAsyncDef]
# flags: --disallow-untyped-defs
async def f(): # E: Function is missing a return type annotation \
# N: Use "-> None" if function does not return a value
async def f(): # E: Function is missing a return type annotation # N: Use "-> None" if function does not return a value
pass
[builtins fixtures/async_await.pyi]
[typing fixtures/typing-medium.pyi]

[case testAsyncUnannotatedArgument]
# flags: --disallow-untyped-defs
async def f(x) -> None: # E: Function is missing a type annotation for one or more arguments
async def f(x) -> None: # E: Function is missing a type annotation for one or more arguments: "x"
pass
[builtins fixtures/async_await.pyi]
[typing fixtures/typing-async.pyi]
Expand All @@ -84,7 +102,7 @@ async def g(x: int) -> Any:
def get_tasks(self):
return 'whatever'
[out]
main:2: error: Function is missing a return type annotation
main:2: error: Function is missing a type annotation

[case testDisallowUntypedDefsUntypedDecorator]
# flags: --disallow-untyped-decorators
Expand Down Expand Up @@ -659,7 +677,7 @@ import standard, incomplete
def incomplete(x) -> int:
return 0
[file incomplete.py]
def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments
def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments: "x"
return 0
[file mypy.ini]
\[mypy]
Expand All @@ -676,7 +694,7 @@ import standard, incomplete
def incomplete(x) -> int:
return 0
[file incomplete.py]
def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments
def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments: "x"
return 0
[file pyproject.toml]
\[tool.mypy]
Expand Down Expand Up @@ -1351,7 +1369,7 @@ def g(m: Movie) -> Movie:

def f(i: int): # E: Function is missing a return type annotation
pass
def g(i) -> None: # E: Function is missing a type annotation for one or more arguments
def g(i) -> None: # E: Function is missing a type annotation for one or more arguments: "i"
pass
def h(i: int) -> int: # no error
return i
Expand All @@ -1378,7 +1396,7 @@ def f(i: int, s):

[out]
main:3: error: Function is missing a return type annotation
main:3: error: Function is missing a type annotation for one or more arguments
main:3: error: Function is missing a type annotation for one or more arguments: "s"

[case testDisallowIncompleteDefsAttrsNoAnnotations]
# flags: --disallow-incomplete-defs
Expand All @@ -1405,7 +1423,7 @@ class Annotated:
import attrs

@attrs.define
class PartiallyAnnotated: # E: Function is missing a type annotation for one or more arguments
class PartiallyAnnotated: # E: Function is missing a type annotation for one or more arguments: "baz"
bar: int = attrs.field()
baz = attrs.field()

Expand Down Expand Up @@ -2301,13 +2319,13 @@ ignore_errors = True
# flags: --config-file tmp/mypy.ini
import x, y, z
[file x.py]
def f(a): ... # E: Function is missing a type annotation
def f(a): ... # E: Function is missing a type annotation # N: Use "-> None" if function does not return a value
def g(a: int) -> int: return f(a)
[file y.py]
def f(a): pass
def g(a: int) -> int: return f(a)
[file z.py]
def f(a): pass # E: Function is missing a type annotation
def f(a): pass # E: Function is missing a type annotation # N: Use "-> None" if function does not return a value
def g(a: int) -> int: return f(a) # E: Call to untyped function "f" in typed context
[file mypy.ini]
\[mypy]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python38.test
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def g(x: int): ...

[case testPEP570ArgTypesMissing]
# flags: --disallow-untyped-defs
def f(arg, /) -> None: ... # E: Function is missing a type annotation for one or more arguments
def f(arg, /) -> None: ... # E: Function is missing a type annotation for one or more arguments: "arg"

[case testPEP570ArgTypesBadDefault]
def f(arg: int = "ERROR", /) -> None: ... # E: Incompatible default for argument "arg" (default has type "str", argument has type "int")
Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/cmdline.test
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ def f(a): pass
def f(a): pass
[out]
z.py:1: error: Function is missing a type annotation
z.py:1: note: Use "-> None" if function does not return a value
x.py:1: error: Function is missing a type annotation
x.py:1: note: Use "-> None" if function does not return a value

[case testConfigErrorNoSection]
# cmd: mypy -c pass
Expand Down