diff --git a/CHANGELOG.md b/CHANGELOG.md index e5260104f3fe..ee646c02750c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,14 @@ may necessitate changing the location of a `# type: ignore` comment. Contributed by Shantanu Jain (PR [18392](https://github.com/python/mypy/pull/18392), PR [18397](https://github.com/python/mypy/pull/18397)). +### `mypy: ignore` + +`mypy: ignore` comments are now honored by mypy exactly as though they said `type: ignore`. +This allows one to suppress type errors from mypy in particular, without suppressing other type checkers, +which can be desirable if mypy has a bug and other type checkers one runs on the same code do not. + +Contributed by Wyatt S Carpenter (PR [17875](https://github.com/python/mypy/pull/17875)). + ## Mypy 1.14 We’ve just uploaded mypy 1.14 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 7165955e67d3..eb7097f60fad 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -148,14 +148,6 @@ error: The second line is now fine, since the ignore comment causes the name ``frobnicate`` to get an implicit ``Any`` type. -.. note:: - - You can use the form ``# type: ignore[]`` to only ignore - specific errors on the line. This way you are less likely to - silence unexpected errors that are not safe to ignore, and this - will also document what the purpose of the comment is. See - :ref:`error-codes` for more information. - .. note:: The ``# type: ignore`` comment will only assign the implicit ``Any`` @@ -163,6 +155,24 @@ The second line is now fine, since the ignore comment causes the name if we did have a stub available for ``frobnicate`` then mypy would ignore the ``# type: ignore`` comment and typecheck the stub as usual. +You can use the form ``# type: ignore[]`` to only ignore +specific errors on the line. This way you are less likely to +silence unexpected errors that are not safe to ignore, and this +will also document what the purpose of the comment is. See +:ref:`error-codes` for more information. + +Instead of ``# type: ignore``, you may also use the mypy-specific +``# mypy: ignore``, to tell only mypy — and not other type checkers — to +ignore that line. This is mostly useful if mypy has a bug that produces +known-spurious error messages. You may also use the ``# mypy: ignore[]`` +form to only ignore specific errors. Indeed, it is often wisest to use +``# mypy: ignore[]``, as this minimizes your chances of accidentally +suppressing other errors that you don't really want to ignore! + +(``# mypy: ignore`` comments are per-line and should not be confused with +:ref:`inline-config` comments, which are per-file configuration directives +that just happen to be formatted a similar way.) + Another option is to explicitly annotate values with type ``Any`` -- mypy will let you perform arbitrary operations on ``Any`` values. Sometimes there is no more precise type you can use for a diff --git a/mypy/fastparse.py b/mypy/fastparse.py index cd7aab86daa0..ffc5c464741f 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -1,7 +1,9 @@ from __future__ import annotations +import io import re import sys +import tokenize import warnings from collections.abc import Sequence from typing import Any, Callable, Final, Literal, Optional, TypeVar, Union, cast, overload @@ -137,13 +139,57 @@ def ast3_parse( source: str | bytes, filename: str, mode: str, feature_version: int = PY_MINOR_VERSION ) -> AST: - return ast3.parse( - source, - filename, - mode, - type_comments=True, # This works the magic - feature_version=feature_version, + """This function is just a convenience wrapper around ast.parse, with default flags useful to Mypy. + It also handles `# mypy: ignore` comments, by turning them into `# type: ignore` comments. + """ + # Hack to support "mypy: ignore" comments until the builtin compile function changes to allow us to detect it otherwise: + # (Note: completely distinct from https://mypy.readthedocs.io/en/stable/inline_config.html ; see also, util.get_mypy_comments in this codebase.) + + # We make the substitution in comments, and to find those comments we use Python's `tokenize`. + # https://docs.python.org/3/library/tokenize.html has a big red **Warning:** + # Note that the functions in this module are only designed to parse syntactically valid Python + # code (code that does not raise when parsed using ast.parse()). The behavior of the functions + # in this module is **undefined** when providing invalid Python code and it can change at any point. + # So, we cannot rely on roundtrip behavior in tokenize iff ast.parse would throw when given `source`. + # The simplest way to deal with that is just to call ast.parse twice, once before and once after! + def p() -> AST: + return ast3.parse( + source, filename, mode, type_comments=True, feature_version=feature_version + ) + + p() # Call to assure syntactic validity (will throw an exception otherwise, exiting this function). + if isinstance(source, str): + tokens = tokenize.generate_tokens(io.StringIO(source).readline) + else: + tokens = tokenize.tokenize(io.BytesIO(source).readline) + # We do a workaround for a roundtripping error (https://github.com/python/cpython/issues/125008) + # that was introduced in 3.12.3 (https://github.com/python/cpython/blob/v3.12.3/Lib/tokenize.py#L205) + # and fixed in 3.12.8 (https://github.com/python/cpython/blob/v3.12.8/Lib/tokenize.py#L205). + # Luckily, it was caught before the first official (non-rc) release of python 3.13. + is_defective_version = (3, 12, 3) <= sys.version_info[:3] <= (3, 12, 7) + # This is a gnarly list comprehension, but that's basically just how untokenize is supposed to work. + source = tokenize.untokenize( + ( + t, + ( + re.sub(r"#\s*mypy:\s*ignore(?![-_])", "# type: ignore", s) + if t == tokenize.COMMENT + else ( + s[:-1] + if is_defective_version + # Technically redundant hasattr check as all the defective versions are versions that have this attribute, but we'd like to appease the typechecker here: + and hasattr(tokenize, "FSTRING_MIDDLE") + and t == tokenize.FSTRING_MIDDLE + and s.startswith("\\") + and s.endswith("{") + else s + ) + ), + *_, # Including this bit improves whitespace roundtripping (possibly always making it perfect). + ) + for t, s, *_ in tokens ) + return p() # `source` has changed, so this result is different than the first call. NamedExpr = ast3.NamedExpr diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test index fa451f373e70..878c06d6efb6 100644 --- a/test-data/unit/check-ignore.test +++ b/test-data/unit/check-ignore.test @@ -3,6 +3,11 @@ x = 1 x() # type: ignore x() # E: "int" not callable +[case testMypyIgnoreTypeError] +x = 1 +x() # mypy: ignore +x() # E: "int" not callable + [case testIgnoreUndefinedName] x = 1 y # type: ignore