From d10ec6e2d637873e22c0abf6737bd142e58c043c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 3 Dec 2023 22:27:01 -0800 Subject: [PATCH 1/6] Allow type ignores of PEP 695 constructs This is basically a pre-existing bug and affects other errors that ASTConverter might raise, like merging overloads. It could vaguely be nice to move all the set_file_ignored_lines into fastparse, instead of BuildManager.parse_file. Could also clean up the ignore_errors logic a little bit more. Fixes #16607 --- mypy/build.py | 4 ++-- mypy/fastparse.py | 26 +++++++++++++++----------- mypy/parse.py | 2 +- mypy/test/testparse.py | 14 +++++++++++--- test-data/unit/check-python312.test | 5 +++++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 961198fc2fa4..b3ca8d06916d 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2174,8 +2174,8 @@ def parse_file(self, *, temporary: bool = False) -> None: self.id, self.xpath, source, - self.ignore_all or self.options.ignore_errors, - self.options, + ignore_errors=self.ignore_all or self.options.ignore_errors, + options=self.options, ) else: diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 95d99db84a15..e3915c21337f 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -190,7 +190,7 @@ def parse( source: str | bytes, fnam: str, module: str | None, - errors: Errors | None = None, + errors: Errors, options: Options | None = None, ) -> MypyFile: """Parse a source file, without doing any semantic analysis. @@ -199,16 +199,13 @@ def parse( on failure. Otherwise, use the errors object to report parse errors. """ ignore_errors = (options is not None and options.ignore_errors) or ( - errors is not None and fnam in errors.ignored_files + fnam in errors.ignored_files ) # If errors are ignored, we can drop many function bodies to speed up type checking. strip_function_bodies = ignore_errors and (options is None or not options.preserve_asts) raise_on_error = False if options is None: options = Options() - if errors is None: - errors = Errors(options) - raise_on_error = True errors.set_file(fnam, module, options=options) is_stub_file = fnam.endswith(".pyi") if is_stub_file: @@ -228,11 +225,9 @@ def parse( options=options, is_stub=is_stub_file, errors=errors, - ignore_errors=ignore_errors, strip_function_bodies=strip_function_bodies, + path=fnam, ).visit(ast) - tree.path = fnam - tree.is_stub = is_stub_file except SyntaxError as e: # alias to please mypyc is_py38_or_earlier = sys.version_info < (3, 9) @@ -357,8 +352,8 @@ def __init__( is_stub: bool, errors: Errors, *, - ignore_errors: bool, strip_function_bodies: bool, + path: str, ) -> None: # 'C' for class, 'D' for function signature, 'F' for function, 'L' for lambda self.class_and_function_stack: list[Literal["C", "D", "F", "L"]] = [] @@ -367,8 +362,8 @@ def __init__( self.options = options self.is_stub = is_stub self.errors = errors - self.ignore_errors = ignore_errors self.strip_function_bodies = strip_function_bodies + self.path = path self.type_ignores: dict[int, list[str]] = {} @@ -380,6 +375,10 @@ def note(self, msg: str, line: int, column: int) -> None: def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None: if blocker or not self.options.ignore_errors: + # Make sure self.errors reflects any type ignores that we have parsed + self.errors.set_file_ignored_lines( + self.path, self.type_ignores, self.options.ignore_errors + ) self.errors.report(line, column, msg.value, blocker=blocker, code=msg.code) def fail_merge_overload(self, node: IfStmt) -> None: @@ -858,8 +857,13 @@ def visit_Module(self, mod: ast3.Module) -> MypyFile: self.type_ignores[ti.lineno] = parsed else: self.fail(message_registry.INVALID_TYPE_IGNORE, ti.lineno, -1, blocker=False) + body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True)) - return MypyFile(body, self.imports, False, self.type_ignores) + + ret = MypyFile(body, self.imports, False, ignored_lines=self.type_ignores) + ret.is_stub = self.is_stub + ret.path = self.path + return ret # --- stmt --- # FunctionDef(identifier name, arguments args, diff --git a/mypy/parse.py b/mypy/parse.py index 8bf9983967ba..4904d5d4a48e 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -6,7 +6,7 @@ def parse( - source: str | bytes, fnam: str, module: str | None, errors: Errors | None, options: Options + source: str | bytes, fnam: str, module: str | None, errors: Errors, options: Options ) -> MypyFile: """Parse a source file, without doing any semantic analysis. diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 0140eb072821..eae3403b1409 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -8,7 +8,7 @@ from mypy import defaults from mypy.config_parser import parse_mypy_comments -from mypy.errors import CompileError +from mypy.errors import CompileError, Errors from mypy.options import Options from mypy.parse import parse from mypy.test.data import DataDrivenTestCase, DataSuite @@ -51,7 +51,11 @@ def test_parser(testcase: DataDrivenTestCase) -> None: try: n = parse( - bytes(source, "ascii"), fnam="main", module="__main__", errors=None, options=options + bytes(source, "ascii"), + fnam="main", + module="__main__", + errors=Errors(options), + options=options, ) a = n.str_with_options(options).split("\n") except CompileError as e: @@ -82,7 +86,11 @@ def test_parse_error(testcase: DataDrivenTestCase) -> None: skip() # Compile temporary file. The test file contains non-ASCII characters. parse( - bytes("\n".join(testcase.input), "utf-8"), INPUT_FILE_NAME, "__main__", None, options + bytes("\n".join(testcase.input), "utf-8"), + INPUT_FILE_NAME, + "__main__", + errors=Errors(options), + options=options, ) raise AssertionError("No errors reported") except CompileError as e: diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index cb89eb34880c..285563c19991 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -11,6 +11,11 @@ def g(x: MyList[int]) -> MyList[int]: # E: Variable "__main__.MyList" is not va # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases return reveal_type(x) # N: Revealed type is "MyList?[builtins.int]" +type MyInt2 = int # type: ignore[valid-type] + +def h(x: MyInt2) -> MyInt2: + return reveal_type(x) # N: Revealed type is "builtins.int" + [case test695Class] class MyGen[T]: # E: PEP 695 generics are not yet supported def __init__(self, x: T) -> None: # E: Name "T" is not defined From 81f6c618e88674ae83b6add58edf38ebf1102ab1 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 3 Dec 2023 22:50:42 -0800 Subject: [PATCH 2/6] . --- misc/dump-ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/dump-ast.py b/misc/dump-ast.py index 6f70bbc8c9ed..7fdf905bae0b 100755 --- a/misc/dump-ast.py +++ b/misc/dump-ast.py @@ -9,7 +9,7 @@ import sys from mypy import defaults -from mypy.errors import CompileError +from mypy.errors import CompileError, Errors from mypy.options import Options from mypy.parse import parse @@ -19,7 +19,7 @@ def dump(fname: str, python_version: tuple[int, int], quiet: bool = False) -> No options.python_version = python_version with open(fname, "rb") as f: s = f.read() - tree = parse(s, fname, None, errors=None, options=options) + tree = parse(s, fname, None, errors=Errors(options), options=options) if not quiet: print(tree) From e09797d700e139e509290919cce73b2af3903ab3 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 3 Dec 2023 22:52:55 -0800 Subject: [PATCH 3/6] . --- test-data/unit/check-errorcodes.test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 28487a456156..1dd058730f28 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -975,11 +975,13 @@ def f(d: D, s: str) -> None: [typing fixtures/typing-typeddict.pyi] [case testRecommendErrorCode] -# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever"` [syntax] +# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever"` [syntax] \ + # N: Error code "syntax" not covered by "type: ignore" comment 1 + "asdf" [case testRecommendErrorCode2] -# type: ignore[whatever, other] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever, other"` [syntax] +# type: ignore[whatever, other] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever, other"` [syntax] \ + # N: Error code "syntax" not covered by "type: ignore" comment 1 + "asdf" [case testShowErrorCodesInConfig] From 35808e050ae2d2a227c25ef4819acf1123d74c3a Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 3 Dec 2023 22:57:27 -0800 Subject: [PATCH 4/6] . --- mypy/fastparse.py | 5 +---- mypy/parse.py | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index e3915c21337f..cba01eab2e4e 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -203,7 +203,7 @@ def parse( ) # If errors are ignored, we can drop many function bodies to speed up type checking. strip_function_bodies = ignore_errors and (options is None or not options.preserve_asts) - raise_on_error = False + if options is None: options = Options() errors.set_file(fnam, module, options=options) @@ -249,9 +249,6 @@ def parse( ) tree = MypyFile([], [], False, {}) - if raise_on_error and errors.is_errors(): - errors.raise_error() - assert isinstance(tree, MypyFile) return tree diff --git a/mypy/parse.py b/mypy/parse.py index 4904d5d4a48e..8993905b90fd 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -19,4 +19,7 @@ def parse( source = options.transform_source(source) import mypy.fastparse - return mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options) + tree = mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options) + if errors.is_errors(): + errors.raise_error() + return tree From 0da87d6177c2a3c7333f9c407c68abd4bd1d8e8c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 3 Dec 2023 23:27:46 -0800 Subject: [PATCH 5/6] . --- mypy/parse.py | 4 ++-- mypy/test/testparse.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/parse.py b/mypy/parse.py index 8993905b90fd..e0bc1e842e56 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -6,7 +6,7 @@ def parse( - source: str | bytes, fnam: str, module: str | None, errors: Errors, options: Options + source: str | bytes, fnam: str, module: str | None, errors: Errors, options: Options, raise_on_error: bool = False ) -> MypyFile: """Parse a source file, without doing any semantic analysis. @@ -20,6 +20,6 @@ def parse( import mypy.fastparse tree = mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options) - if errors.is_errors(): + if raise_on_error and errors.is_errors(): errors.raise_error() return tree diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index eae3403b1409..e33fa7e53ff0 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -56,6 +56,7 @@ def test_parser(testcase: DataDrivenTestCase) -> None: module="__main__", errors=Errors(options), options=options, + raise_on_error=True, ) a = n.str_with_options(options).split("\n") except CompileError as e: @@ -91,6 +92,7 @@ def test_parse_error(testcase: DataDrivenTestCase) -> None: "__main__", errors=Errors(options), options=options, + raise_on_error=True, ) raise AssertionError("No errors reported") except CompileError as e: From f8db8e3d9285a3d8860ebbcd3859f8893c176282 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:28:20 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/parse.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/parse.py b/mypy/parse.py index e0bc1e842e56..ee61760c0ac0 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -6,7 +6,12 @@ def parse( - source: str | bytes, fnam: str, module: str | None, errors: Errors, options: Options, raise_on_error: bool = False + source: str | bytes, + fnam: str, + module: str | None, + errors: Errors, + options: Options, + raise_on_error: bool = False, ) -> MypyFile: """Parse a source file, without doing any semantic analysis.