From b4569fa411ebce01b11d5da47585a5c25083505c Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sat, 4 Mar 2023 17:38:50 +0000 Subject: [PATCH 1/8] Add test to demonstrate issue #9655 This test currently fails with this error: AssertionError: Command 3 (dmypy check -- bar.py) did not give expected output --- Captured stderr call --- Expected: bar.py:2: error: Unused "type: ignore" comment (diff) == Return code: 1 (diff) Actual: (empty) It demonstrates a bug that when an module is removed using `FineGrainedBuildManager.update` because it is not "seen" by `fine_grained_increment_follow_imports`, then "unused type: ignore" warnings disappear from subsequent checks. Ref: https://github.com/python/mypy/issues/9655 --- test-data/unit/daemon.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 7586c8763d33..6753bc414ccc 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -522,3 +522,20 @@ class A: x: int class B: x: int + +[case testUnusedTypeIgnorePreservedAfterUpdate] +-- Regression test for https://github.com/python/mypy/issues/9655 +$ dmypy start -- --warn-unused-ignores --no-error-summary +Daemon started +$ dmypy check -- bar.py +bar.py:2: error: Unused "type: ignore" comment +== Return code: 1 +$ dmypy check -- bar.py +bar.py:2: error: Unused "type: ignore" comment +== Return code: 1 + +[file foo/__init__.py] +[file foo/empty.py] +[file bar.py] +from foo.empty import * +a = 1 # type: ignore From 1831cbd5e642892361252e731f1c242b20bc62dd Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sun, 12 Mar 2023 22:23:51 +0000 Subject: [PATCH 2/8] Prove ignores without error codes also disappear This test fails with the error: AssertionError: Command 3 (dmypy check -- bar.py) did not give expected output --- Captured stderr call --- Expected: bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] (diff) == Return code: 1 (diff) Actual: (empty) This test illustrates that '"type: ignore" comment without error code' errors currently disappear in the same way that 'Unused "type: ignore"' errors do as described in #9655. Ref: https://github.com/python/mypy/issues/9655 --- test-data/unit/daemon.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 6753bc414ccc..dcc7ec86309f 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -539,3 +539,20 @@ bar.py:2: error: Unused "type: ignore" comment [file bar.py] from foo.empty import * a = 1 # type: ignore + +[case testTypeIgnoreWithoutCodePreservedAfterUpdate] +-- Regression test for https://github.com/python/mypy/issues/9655 +$ dmypy start -- --enable-error-code ignore-without-code --no-error-summary +Daemon started +$ dmypy check -- bar.py +bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] +== Return code: 1 +$ dmypy check -- bar.py +bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] +== Return code: 1 + +[file foo/__init__.py] +[file foo/empty.py] +[file bar.py] +from foo.empty import * +a = 1 # type: ignore From 6094fc46d12904eaec42c3e622645551b549e7e5 Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sun, 12 Mar 2023 23:25:29 +0000 Subject: [PATCH 3/8] Prove possibly-undefined errors disappear This test fails with the error: AssertionError: Command 3 (dmypy check -- bar.py) did not give expected output --- Captured stderr call --- Expected: bar.py:4: error: Name "a" may be undefined [possibly-undefined] (diff) == Return code: 1 (diff) Actual: (empty) This test illustrates that possibly-undefined errors currently disappear in the same way that 'Unused "type: ignore"' errors do as described in #9655. Ref: https://github.com/python/mypy/issues/9655 --- test-data/unit/daemon.test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index dcc7ec86309f..d4ec103db881 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -556,3 +556,22 @@ bar.py:2: error: "type: ignore" comment without error code [ignore-without-code [file bar.py] from foo.empty import * a = 1 # type: ignore + +[case testPossiblyUndefinedVarsPreservedAfterUpdate] +-- Regression test for https://github.com/python/mypy/issues/9655 +$ dmypy start -- --enable-error-code possibly-undefined --no-error-summary +Daemon started +$ dmypy check -- bar.py +bar.py:4: error: Name "a" may be undefined [possibly-undefined] +== Return code: 1 +$ dmypy check -- bar.py +bar.py:4: error: Name "a" may be undefined [possibly-undefined] +== Return code: 1 + +[file foo/__init__.py] +[file foo/empty.py] +[file bar.py] +from foo.empty import * +if False: + a = 1 +a From eac851bb6560df1bd2953aee46bd4a4c856e830a Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Mon, 13 Mar 2023 00:13:07 +0000 Subject: [PATCH 4/8] Ensure errors persist after dmypy update Before this change, if a module was removed with `update` in the `FineGrainedBuildManager`, then some errors would not re-appear. This change ensures that three types of error are maintained when running `reprocess_nodes`: - possibly undefined vars - unused ignores - ignores without codes By adding targets to ErrorInfos, this also fixes an issue where unused ignore and ignores without codes errors didn't get re-processed when they were the only issue in the file. Fixes #9655 --- mypy/errors.py | 4 ++++ mypy/server/update.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/mypy/errors.py b/mypy/errors.py index 2c2c1e5ca227..03eb4a6adf21 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -668,6 +668,8 @@ def generate_unused_ignore_errors(self, file: str) -> None: False, False, False, + origin=(self.file, [line]), + target=self.target_module, ) self._add_error_info(file, info) @@ -720,6 +722,8 @@ def generate_ignore_without_code_errors( False, False, False, + origin=(self.file, [line]), + target=self.target_module, ) self._add_error_info(file, info) diff --git a/mypy/server/update.py b/mypy/server/update.py index 00b823c99dfd..d87e44b97863 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -1027,6 +1027,10 @@ def key(node: FineGrainedDeferredNode) -> int: if graph[module_id].type_checker().check_second_pass(): more = True + graph[module_id].detect_possibly_undefined_vars() + graph[module_id].generate_unused_ignore_notes() + graph[module_id].generate_ignore_without_code_notes() + if manager.options.export_types: manager.all_types.update(graph[module_id].type_map()) From 5bfb6b57c4fa169e6c5b73c8c402db7893a3663a Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Fri, 31 Mar 2023 23:28:56 +0100 Subject: [PATCH 5/8] Improve test names There aren't any updates in these tests, we just re-run dmypy. --- test-data/unit/daemon.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index d4ec103db881..6dd416b1a1a0 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -523,7 +523,7 @@ class A: class B: x: int -[case testUnusedTypeIgnorePreservedAfterUpdate] +[case testUnusedTypeIgnorePreservedOnRerun] -- Regression test for https://github.com/python/mypy/issues/9655 $ dmypy start -- --warn-unused-ignores --no-error-summary Daemon started @@ -540,7 +540,7 @@ bar.py:2: error: Unused "type: ignore" comment from foo.empty import * a = 1 # type: ignore -[case testTypeIgnoreWithoutCodePreservedAfterUpdate] +[case testTypeIgnoreWithoutCodePreservedOnRerun] -- Regression test for https://github.com/python/mypy/issues/9655 $ dmypy start -- --enable-error-code ignore-without-code --no-error-summary Daemon started From 3d8d2b7275dd9ce6e72b7ee5262ce07f1af59550 Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Fri, 31 Mar 2023 23:30:08 +0100 Subject: [PATCH 6/8] Add new failing tests These tests show how some errors disappear on a re-run of dmypy after a file is altered. --- test-data/unit/daemon.test | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 6dd416b1a1a0..34d1b2b9218d 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -557,6 +557,42 @@ bar.py:2: error: "type: ignore" comment without error code [ignore-without-code from foo.empty import * a = 1 # type: ignore +[case testUnusedTypeIgnorePreservedAfterChange] +-- Regression test for https://github.com/python/mypy/issues/9655 +$ dmypy start -- --warn-unused-ignores --no-error-summary +Daemon started +$ dmypy check -- bar.py +bar.py:2: error: Unused "type: ignore" comment +== Return code: 1 +$ echo '' >> bar.py +$ dmypy check -- bar.py +bar.py:2: error: Unused "type: ignore" comment +== Return code: 1 + +[file foo/__init__.py] +[file foo/empty.py] +[file bar.py] +from foo.empty import * +a = 1 # type: ignore + +[case testTypeIgnoreWithoutCodePreservedAfterChange] +-- Regression test for https://github.com/python/mypy/issues/9655 +$ dmypy start -- --enable-error-code ignore-without-code --no-error-summary +Daemon started +$ dmypy check -- bar.py +bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] +== Return code: 1 +$ echo '' >> bar.py +$ dmypy check -- bar.py +bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] +== Return code: 1 + +[file foo/__init__.py] +[file foo/empty.py] +[file bar.py] +from foo.empty import * +a = 1 # type: ignore + [case testPossiblyUndefinedVarsPreservedAfterUpdate] -- Regression test for https://github.com/python/mypy/issues/9655 $ dmypy start -- --enable-error-code possibly-undefined --no-error-summary From 520f8e81f1d24d96d1349d7d0aa20daee50c585c Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sat, 8 Apr 2023 22:36:56 +0100 Subject: [PATCH 7/8] Ensure errors persist after file change Before this change, when a module was changed, some errors would not re-appear. This change ensures that unused-ignore and ignore-without-code errors are maintained when a module is re-analyzed after it changes. --- mypy/server/update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/server/update.py b/mypy/server/update.py index d87e44b97863..e909840d2757 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -667,6 +667,8 @@ def restore(ids: list[str]) -> None: state.type_check_first_pass() state.type_check_second_pass() state.detect_possibly_undefined_vars() + state.generate_unused_ignore_notes() + state.generate_ignore_without_code_notes() t2 = time.time() state.finish_passes() t3 = time.time() From 6c14e7db851a956142c80287c8992cd8cb314e4f Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sun, 9 Apr 2023 18:45:17 +0100 Subject: [PATCH 8/8] Fix no-op file modification on Windows Before this change, tests failed because the way we modified a file with echoing didn't work the same way on windows. This change uses Python to print a newline to the end of the file, which doesn't run afoul of the same issues with newlines. Co-authored-by: AlexWaygood --- test-data/unit/daemon.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 34d1b2b9218d..869f60b4e1fd 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -564,7 +564,7 @@ Daemon started $ dmypy check -- bar.py bar.py:2: error: Unused "type: ignore" comment == Return code: 1 -$ echo '' >> bar.py +$ {python} -c "print('\n')" >> bar.py $ dmypy check -- bar.py bar.py:2: error: Unused "type: ignore" comment == Return code: 1 @@ -582,7 +582,7 @@ Daemon started $ dmypy check -- bar.py bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] == Return code: 1 -$ echo '' >> bar.py +$ {python} -c "print('\n')" >> bar.py $ dmypy check -- bar.py bar.py:2: error: "type: ignore" comment without error code [ignore-without-code] == Return code: 1