diff --git a/mypy/checker.py b/mypy/checker.py index e83473492f01..7b239d6d07d7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1634,32 +1634,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): @@ -1672,8 +1683,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) @@ -6791,7 +6823,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: diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 0c7464246990..d7ffc482aa05 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -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"' diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index c822c7c44f41..bb3cf4ea87c0 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -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] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index d6e3366401dd..f7e3c1bdd174 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -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] @@ -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] @@ -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 diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index bb64bb44d282..b2499e8482b9 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -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 @@ -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] @@ -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] @@ -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 @@ -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] @@ -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] @@ -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 @@ -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 @@ -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() @@ -2299,13 +2317,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] diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index dd3f793fd02b..31f4c1d4b61c 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -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") diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 2db4451adc9a..85d3f3df26ec 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -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