Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions docs/source/error_code_list2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 --enable-error-code>`,
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``).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this work with the new parser, for the kinds of type comments the new parser supports? The new parser supports function and variable type comments, but not for loop or with statement type comments. For loop and with statements type comments are likely just a tiny fraction of all type comments.

If we can't make this work at all with the native parser, can you generate an error message suggesting the use of --no-native-parser? Also add a test case that uses the native parser.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this work with the new parser, for the kinds of type comments the new parser supports?

Unfortunately not AFAICT. ast_serialize doesn't differentiate between annotations and type comments. After deserialization all mypy knows is that the e.g. parameter, has a certain type, not where it came from.

If we can't make this work at all with the native parser, can you generate an error message suggesting the use of --no-native-parser?

Not sure that makes sense. I believe the error codes are a module level setting, so we would need to emit that for each one we parse. Sure we could do that, e.g. in the function linked below, but the UX might be terrible.

mypy/mypy/parse.py

Lines 92 to 97 in 358a3b5

# Report parse errors, this replicates the logic in parse().
all_errors = raw_data.raw_errors + state.errors
errors.set_file(fnam, module, options=options)
for error in all_errors:
# Note we never raise in this function, so it should not be called in coordinator.
report_parse_error(error, errors)

--

Tbh I'm not sure anymore if this error code is really helpful at all. I'm inclined to just drop it and close the PR at this point. What do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are planning to switch to the native parser soon, having something that only works with the legacy parser doesn't seem worth it. However, I think we should make this work with the native parser as well, and then this would be a useful feature. I haven't looked into this in detail, but I'd expect that changes to ast_serialize would be fairly minor. It's possible to make non-backward-compatible changes to the AST structure (as long as we also support the old AST structure when used with an older mypy version).


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
...
6 changes: 6 additions & 0 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
46 changes: 45 additions & 1 deletion mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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>", "func_type")
assert isinstance(func_type_ast, FunctionType)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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],
Expand All @@ -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],
Expand Down
4 changes: 4 additions & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
28 changes: 28 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading