diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 125671bc2bef..bd2436061974 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -676,3 +676,26 @@ Example: print("red") case _: print("other") + +.. _code-untyped-decorator: + +Error if an untyped decorator makes a typed function effectively untyped [untyped-decorator] +-------------------------------------------------------------------------------------------- + +If enabled with :option:`--disallow-untyped-decorators ` +mypy generates an error if a typed function is wrapped by an untyped decorator +(as this would effectively remove the benefits of typing the function). + +Example: + +.. code-block:: python + + def printing_decorator(func): + def wrapper(*args, **kwds): + print("Calling", func) + return func(*args, **kwds) + return wrapper + # A decorated function. + @printing_decorator # E: Untyped decorator makes function "add_forty_two" untyped [untyped-decorator] + def add_forty_two(value: int) -> int: + return value + 42 diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index a96f5f723a7d..fbfa572b9439 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -302,6 +302,10 @@ def __hash__(self) -> int: sub_code_of=MISC, ) +UNTYPED_DECORATOR: Final = ErrorCode( + "untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General" +) + NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorCode( "narrowed-type-not-subtype", "Warn if a TypeIs function's narrowed type is not a subtype of the original type", diff --git a/mypy/messages.py b/mypy/messages.py index 6329cad687f6..c6378c264757 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2008,7 +2008,11 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: ) def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: - self.fail(f'Untyped decorator makes function "{func_name}" untyped', context) + self.fail( + f'Untyped decorator makes function "{func_name}" untyped', + context, + code=codes.UNTYPED_DECORATOR, + ) def bad_proto_variance( self, actual: int, tvar_name: str, expected: int, context: Context diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index bb5f658ebb50..06c5753db5a7 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -394,6 +394,21 @@ def f() -> None: def g(): pass +[case testErrorCodeUntypedDecorator] +# flags: --disallow-untyped-decorators --warn-unused-ignores +def d(f): return f + +@d # E: Untyped decorator makes function "x" untyped [untyped-decorator] +def x() -> int: return 1 +@d # type: ignore +def y() -> int: return 2 +@d # type: ignore[untyped-decorator] +def best() -> int: return 3 +@d # type: ignore[misc] # E: Unused "type: ignore" comment [unused-ignore] \ + # E: Untyped decorator makes function "z" untyped [untyped-decorator] \ + # N: Error code "untyped-decorator" not covered by "type: ignore" comment +def z() -> int: return 4 + [case testErrorCodeIndexing] from typing import Dict x: Dict[int, int]