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 b2ec89ff81b83..43b31fa451ec9 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_SOFT_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_SOFT_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,13 @@ 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_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) return self.set_line(s, n) @@ -1293,6 +1309,13 @@ 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_SOFT_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 +1328,13 @@ 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_SOFT_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 +1362,13 @@ 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_SOFT_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 +1380,13 @@ 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_SOFT_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..28f08da028e4d 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -311,6 +311,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage: TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( 'Syntax error in type comment "{}"', codes.SYNTAX ) +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..bccda61127300 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1335,3 +1335,28 @@ 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 +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 # 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 # E: Using type comments with mypy is (soft-)deprecated, use inline annotations instead [type-comment] + ... + +[builtins fixtures/tuple.pyi]