From 88dd26a568056ca656e4ef38a979653d1c74e61b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 18 Jan 2026 18:51:25 +0100 Subject: [PATCH 1/3] Deprecate type comments --- mypy/fastparse.py | 31 ++++++++++++++++++++++++++++++- mypy/message_registry.py | 3 +++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index b2ec89ff81b83..16778ea5f1c13 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -927,6 +927,9 @@ def do_func_def( arg_types = [None] * len(args) return_type = None elif n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, lineno, n.col_offset, blocker=False + ) try: func_type_ast = ast3_parse(n.type_comment, "", "func_type") assert isinstance(func_type_ast, FunctionType) @@ -1138,7 +1141,13 @@ def make_argument( arg_type = None if annotation is not None: arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation) - else: + elif type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, + arg.lineno, + arg.col_offset, + blocker=False, + ) arg_type = self.translate_type_comment(arg, type_comment) if argument_elide_name(arg.arg): pos_only = True @@ -1266,6 +1275,10 @@ def visit_Delete(self, n: ast3.Delete) -> DelStmt: def visit_Assign(self, n: ast3.Assign) -> AssignmentStmt: lvalues = self.translate_expr_list(n.targets) rvalue = self.visit(n.value) + if n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + ) typ = self.translate_type_comment(n, n.type_comment) s = AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=False) return self.set_line(s, n) @@ -1293,6 +1306,10 @@ def visit_AugAssign(self, n: ast3.AugAssign) -> OperatorAssignmentStmt: # For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment) def visit_For(self, n: ast3.For) -> ForStmt: + if n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + ) target_type = self.translate_type_comment(n, n.type_comment) node = ForStmt( self.visit(n.target), @@ -1305,6 +1322,10 @@ def visit_For(self, n: ast3.For) -> ForStmt: # AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment) def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt: + if n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + ) target_type = self.translate_type_comment(n, n.type_comment) node = ForStmt( self.visit(n.target), @@ -1332,6 +1353,10 @@ def visit_If(self, n: ast3.If) -> IfStmt: # With(withitem* items, stmt* body, string? type_comment) def visit_With(self, n: ast3.With) -> WithStmt: + if n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + ) target_type = self.translate_type_comment(n, n.type_comment) node = WithStmt( [self.visit(i.context_expr) for i in n.items], @@ -1343,6 +1368,10 @@ def visit_With(self, n: ast3.With) -> WithStmt: # AsyncWith(withitem* items, stmt* body, string? type_comment) def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt: + if n.type_comment is not None: + self.fail( + message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + ) target_type = self.translate_type_comment(n, n.type_comment) s = WithStmt( [self.visit(i.context_expr) for i in n.items], diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 9de31514b6bd1..632dc9053437a 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -311,6 +311,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( 'Syntax error in type comment "{}"', codes.SYNTAX ) +TYPE_COMMENT_DEPRECATED: Final = ErrorMessage( + "Using type comments with mypy is deprecated, use inline annotations instead", codes.MISC +) ELLIPSIS_WITH_OTHER_TYPEPARAMS: Final = ErrorMessage( "Ellipses cannot accompany other parameter types in function type signature", codes.SYNTAX ) From f33c9223da33d49535e336149c553e134f621554 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:16:20 +0100 Subject: [PATCH 2/3] Add optional error code --- CHANGELOG.md | 20 +++++++++++++++++++ docs/source/error_code_list2.rst | 26 +++++++++++++++++++++++++ mypy/errorcodes.py | 6 ++++++ mypy/fastparse.py | 29 +++++++++++++++++++++------- mypy/message_registry.py | 5 +++-- test-data/unit/check-errorcodes.test | 22 +++++++++++++++++++++ 6 files changed, 99 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f74d29ff3a1c..cd2730115c31f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,26 @@ necessary. Contributed by Marc Mueller (PR [20410](https://github.com/python/mypy/pull/20410)) and (PR [20405](https://github.com/python/mypy/pull/20405)). +### Added optional error code `type-comment` + +A new disabled by default error code `type-comment` was added. If enabled with +`--enable-error-code type-comment`, mypy will generate errors if legacy type comments instead of +type annotations are used. + +```py +a = 2 # type: int +a: int = 2 + +def func(a, b): + # type: (int, str) -> bool + ... + +def func(a: int, b: str) -> bool: + ... +``` + +Contributed by Marc Mueller (PR [20616](https://github.com/python/mypy/pull/20616)). + ## Mypy 1.19 We’ve just uploaded mypy 1.19.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 36cf05d4f3f85..00d9e41746856 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -699,3 +699,29 @@ Example: @printing_decorator # E: Untyped decorator makes function "add_forty_two" untyped [untyped-decorator] def add_forty_two(value: int) -> int: return value + 42 + +.. _code-type-comment: + +Check that no legacy type comments are used [type-comment] +---------------------------------------------------------- + +If enabled with :option:`--enable-error-code type-comment `, +mypy generates an error if legacy type comments are used. Tools like +[com2ann](https://github.com/ilevkivskyi/com2ann) can help with translating type comments to +type annotations. + +More information about type comments are available in the +[typing specification](https://typing.python.org/en/latest/spec/historical.html#type-comments). + +Example: + +.. code-block:: python + + o = 2 # type: int + + for x, y in points: # type: float, float + ... + + def func(a, b): + # type: (str, int) -> bool + ... diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 5c28e8332a76c..8168255699b9d 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -280,6 +280,12 @@ def __hash__(self) -> int: "Error when a string is used where a TypeForm is expected but a string annotation cannot be recognized", "General", ) +TYPE_COMMENT: Final = ErrorCode( + "type-comment", + "Error when legacy type comments are used instead of type annotations", + "General", + default_enabled=False, +) # Syntax errors are often blocking. SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 16778ea5f1c13..43b31fa451ec9 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -928,7 +928,7 @@ def do_func_def( return_type = None elif n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, lineno, n.col_offset, blocker=False ) try: func_type_ast = ast3_parse(n.type_comment, "", "func_type") @@ -1143,7 +1143,7 @@ def make_argument( arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation) elif type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, arg.lineno, arg.col_offset, blocker=False, @@ -1277,7 +1277,10 @@ def visit_Assign(self, n: ast3.Assign) -> AssignmentStmt: rvalue = self.visit(n.value) if n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, + n.lineno, + n.col_offset, + blocker=False, ) typ = self.translate_type_comment(n, n.type_comment) s = AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=False) @@ -1308,7 +1311,10 @@ def visit_AugAssign(self, n: ast3.AugAssign) -> OperatorAssignmentStmt: def visit_For(self, n: ast3.For) -> ForStmt: if n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, + n.lineno, + n.col_offset, + blocker=False, ) target_type = self.translate_type_comment(n, n.type_comment) node = ForStmt( @@ -1324,7 +1330,10 @@ def visit_For(self, n: ast3.For) -> ForStmt: def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt: if n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, + n.lineno, + n.col_offset, + blocker=False, ) target_type = self.translate_type_comment(n, n.type_comment) node = ForStmt( @@ -1355,7 +1364,10 @@ def visit_If(self, n: ast3.If) -> IfStmt: def visit_With(self, n: ast3.With) -> WithStmt: if n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, + n.lineno, + n.col_offset, + blocker=False, ) target_type = self.translate_type_comment(n, n.type_comment) node = WithStmt( @@ -1370,7 +1382,10 @@ def visit_With(self, n: ast3.With) -> WithStmt: def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt: if n.type_comment is not None: self.fail( - message_registry.TYPE_COMMENT_DEPRECATED, n.lineno, n.col_offset, blocker=False + message_registry.TYPE_COMMENT_SOFT_DEPRECATED, + n.lineno, + n.col_offset, + blocker=False, ) target_type = self.translate_type_comment(n, n.type_comment) s = WithStmt( diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 632dc9053437a..28f08da028e4d 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -311,8 +311,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( 'Syntax error in type comment "{}"', codes.SYNTAX ) -TYPE_COMMENT_DEPRECATED: Final = ErrorMessage( - "Using type comments with mypy is deprecated, use inline annotations instead", codes.MISC +TYPE_COMMENT_SOFT_DEPRECATED: Final = ErrorMessage( + "Using type comments with mypy is (soft-)deprecated, use inline annotations instead", + codes.TYPE_COMMENT, ) ELLIPSIS_WITH_OTHER_TYPEPARAMS: Final = ErrorMessage( "Ellipses cannot accompany other parameter types in function type signature", codes.SYNTAX diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index ba2f23fd72940..29008626b8b35 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1335,3 +1335,25 @@ def process(response1: int,response2: int) -> int: # E: Overloaded function sign def process(response1,response2)-> Union[float,int]: return response1 + response2 + +[case testLegacyTypeComments] +# flags: --enable-error-code type-comment +a = 2 # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + +for b in (): # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + ... + +with f() as foo: # type: int # type: ignore[name-defined] # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + ... + +def func(d, e): # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + # type: (str, int) -> bool + ... + +async def func2(): # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + # type: () -> None + + async for c in g(): # type: int # type: ignore[name-defined] # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + ... + +[builtins fixtures/tuple.pyi] From a56494a2a75c4974917dcca4ababa6bbc2083c95 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:46:13 +0100 Subject: [PATCH 3/3] Fix test case --- test-data/unit/check-errorcodes.test | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 29008626b8b35..bccda61127300 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1338,12 +1338,15 @@ def process(response1,response2)-> Union[float,int]: [case testLegacyTypeComments] # flags: --enable-error-code type-comment +def f(): ... +async def g(): ... + a = 2 # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] for b in (): # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] ... -with f() as foo: # type: int # type: ignore[name-defined] # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] +with f() as foo: # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] ... def func(d, e): # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] @@ -1353,7 +1356,7 @@ def func(d, e): # E: Using type comments with mypy is (soft-)deprecated, use in async def func2(): # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] # type: () -> None - async for c in g(): # type: int # type: ignore[name-defined] # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + async for c in g(): # type: int # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] ... [builtins fixtures/tuple.pyi]