diff --git a/CHANGELOG.md b/CHANGELOG.md index 75743a91d3f52..eb11ae8283290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## Next Release +### 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. This will only work with the current (old) parser (`--no-native-parser`). + +```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)). + ### Packaging changes - No longer provide mypyc-accelerated wheels for macOS x86_64 [mypyc-wheels #119](https://github.com/mypyc/mypy_mypyc-wheels/pull/119) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 5dfe483d2cfe2..c574d82ddffae 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -699,3 +699,30 @@ 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. This will only work with the current (old) parser +(``--no-native-parser``). + +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 06e716372f248..d5a053c48e4a3 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 d9e2d5df8f4c1..b61e7319caef9 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -931,6 +931,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) @@ -1142,7 +1145,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 @@ -1271,6 +1280,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) @@ -1298,6 +1314,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), @@ -1310,6 +1333,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), @@ -1337,6 +1367,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], @@ -1348,6 +1385,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 2e40048cbb58b..5b192b9bf4d8b 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -313,6 +313,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 5296d813334e4..f1e1a1cefc541 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1419,3 +1419,31 @@ def process(response1: int,response2: int) -> int: # E: Overloaded function sign def process(response1,response2)-> Union[float,int]: return response1 + response2 + +[case testLegacyTypeComments_no_parallel] +# flags: --enable-error-code type-comment +from typing import AsyncGenerator +def f(): ... +async def g() -> AsyncGenerator[int, None]: + yield 2 + +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] +[typing fixtures/typing-full.pyi]