From 2eae039ea7759df90bef8ec5360d96a3ade1b32c Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Tue, 23 Sep 2025 00:28:49 +0700 Subject: [PATCH 01/13] Update errorcodes.py: UNTYPED_DECORATOR --- mypy/errorcodes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index a96f5f723a7d..b2670d8e4821 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -273,6 +273,8 @@ def __hash__(self) -> int: # Syntax errors are often blocking. SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") +UNTYPED_DECORATOR: Final = ErrorCode("untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General") + # This is an internal marker code for a whole-file ignore. It is not intended to # be user-visible. FILE: Final = ErrorCode("file", "Internal marker for a whole file being ignored", "General") From 021fc0ab5c9d7e972b52eda35e541304655d9f70 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Tue, 23 Sep 2025 00:31:39 +0700 Subject: [PATCH 02/13] Update messages.py: UNTYPED_DECORATOR --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 6329cad687f6..3ec6e7126ac9 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2008,7 +2008,7 @@ 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 From 6b0f0d83d2145f84bab9d32b22ab05d432bb52c9 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Tue, 23 Sep 2025 01:14:13 +0700 Subject: [PATCH 03/13] black --- mypy/errorcodes.py | 4 +++- mypy/messages.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index b2670d8e4821..7052cf21a388 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -273,7 +273,9 @@ def __hash__(self) -> int: # Syntax errors are often blocking. SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") -UNTYPED_DECORATOR: Final = ErrorCode("untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General") +UNTYPED_DECORATOR: Final = ErrorCode( + "untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General" +) # This is an internal marker code for a whole-file ignore. It is not intended to # be user-visible. diff --git a/mypy/messages.py b/mypy/messages.py index 3ec6e7126ac9..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, code=codes.UNTYPED_DECORATOR) + 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 From a586b8ec9510c4dbe3f0ffaf8b5febc750a9a949 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 24 Sep 2025 03:32:49 +0700 Subject: [PATCH 04/13] docu --- docs/source/error_code_list2.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 125671bc2bef..e93fb6bf7c80 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 [misc] + def add_forty_two(value: int) -> int: + return value + 42 From 3282eb109973e94f16bfa79a77209f9336ca5cb8 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 24 Sep 2025 03:39:56 +0700 Subject: [PATCH 05/13] enable sub_code_of=MISC for backwards compat reason i also remembered how it is error codes are suppose to work exactly, which makea this make sense --- mypy/errorcodes.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 7052cf21a388..aef99d615f29 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -273,10 +273,6 @@ def __hash__(self) -> int: # Syntax errors are often blocking. SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") -UNTYPED_DECORATOR: Final = ErrorCode( - "untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General" -) - # This is an internal marker code for a whole-file ignore. It is not intended to # be user-visible. FILE: Final = ErrorCode("file", "Internal marker for a whole file being ignored", "General") @@ -306,6 +302,13 @@ 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", + sub_code_of=MISC, +) + 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", From 3456a8bc98f8f86fe1edd6364c2c8cf0f94fb9b9 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 24 Sep 2025 03:42:16 +0700 Subject: [PATCH 06/13] Apply suggestion from @wyattscarpenter --- docs/source/error_code_list2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index e93fb6bf7c80..1eca0009a848 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -696,6 +696,6 @@ Example: return func(*args, **kwds) return wrapper # A decorated function. - @printing_decorator # E: Untyped decorator makes function "add_forty_two" untyped [misc] + @printing_decorator # E: Untyped decorator makes function "add_forty_two" untyped [untyped-decorator] def add_forty_two(value: int) -> int: return value + 42 From 19d9d4088eb3985c4d2cbe08648079f1b0ebc911 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 24 Sep 2025 03:47:38 +0700 Subject: [PATCH 07/13] Update error_code_list2.rst --- docs/source/error_code_list2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 1eca0009a848..74b01b00ebbf 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -682,7 +682,7 @@ Example: Error if an untyped decorator makes a typed function effectively untyped [untyped-decorator] -----------------------------------------------------> -If enabled with :option:`--disallow-untyped-decorators` +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). From 8b6296389b286079dcf0082800a52fc3e754029b Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 25 Sep 2025 04:05:22 +0700 Subject: [PATCH 08/13] add tests --- test-data/unit/check-errorcodes.test | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index bb5f658ebb50..eef249b1a8c9 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -394,6 +394,19 @@ 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, use narrower [untyped-decorator] instead of [misc] code [unused-ignore] +def z() -> int: return 4 + [case testErrorCodeIndexing] from typing import Dict x: Dict[int, int] From 78af83660700bc796db0a0cd64e2dbbbef64627f Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 25 Sep 2025 04:37:57 +0700 Subject: [PATCH 09/13] Update docs/source/error_code_list2.rst: fix heading underline Co-authored-by: A5rocks --- docs/source/error_code_list2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 74b01b00ebbf..7acc5b86201a 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -680,7 +680,7 @@ Example: .. _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 From 10ffd2ef98c57da90d8ae2f29c7b03f7667a35be Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 25 Sep 2025 04:49:14 +0700 Subject: [PATCH 10/13] fix the fix --- docs/source/error_code_list2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 7acc5b86201a..bd2436061974 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -680,7 +680,7 @@ Example: .. _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 From 93ed8dd5ef87b69af4193a2e52b0d3ba2c8c175d Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 25 Sep 2025 16:36:34 +0900 Subject: [PATCH 11/13] make untyped-decorator not a sub cose of misc, per suggestion --- mypy/errorcodes.py | 1 - test-data/unit/check-errorcodes.test | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index aef99d615f29..f1727b926dd1 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -306,7 +306,6 @@ def __hash__(self) -> int: "untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General", - sub_code_of=MISC, ) NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorCode( diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index eef249b1a8c9..e0add32ae9b2 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -404,7 +404,7 @@ def x() -> int: return 1 def y() -> int: return 2 @d # type: ignore[untyped-decorator] def best() -> int: return 3 -@d # type: ignore[misc] # E: Unused "type: ignore" comment, use narrower [untyped-decorator] instead of [misc] code [unused-ignore] +@d # E: Untyped decorator makes function "z" untyped [untyped-decorator] def z() -> int: return 4 [case testErrorCodeIndexing] From 263b93f63fef1540aa86640a4af3a85d7095f23a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 07:38:04 +0000 Subject: [PATCH 12/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/errorcodes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index f1727b926dd1..fbfa572b9439 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -303,9 +303,7 @@ def __hash__(self) -> int: ) UNTYPED_DECORATOR: Final = ErrorCode( - "untyped-decorator", - "Error if an untyped decorator makes a typed function untyped", - "General", + "untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General" ) NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorCode( From ed20c35af251846aefee8fd55b9e63ba494d7d90 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 25 Sep 2025 16:43:10 +0900 Subject: [PATCH 13/13] actually this last thing should test sonething different --- test-data/unit/check-errorcodes.test | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index e0add32ae9b2..06c5753db5a7 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -400,11 +400,13 @@ def d(f): return f @d # E: Untyped decorator makes function "x" untyped [untyped-decorator] def x() -> int: return 1 -@d # type: ignore +@d # type: ignore def y() -> int: return 2 -@d # type: ignore[untyped-decorator] +@d # type: ignore[untyped-decorator] def best() -> int: return 3 -@d # E: Untyped decorator makes function "z" untyped [untyped-decorator] +@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]