-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type ignore comments erroneously marked as unused by dmypy #15043
base: master
Are you sure you want to change the base?
Conversation
I ran into this issue testing a pre-release of 1.3 and it has created enough confusion on the team that I'm not sure we can roll it out until this is fixed. I would love to see this merged before release. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
710c001
to
0b84c46
Compare
This comment has been minimized.
This comment has been minimized.
0b84c46
to
fd8b35a
Compare
85b5144
to
cca70fc
Compare
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
b69fe5d
to
84271b0
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
244c384
to
84271b0
Compare
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
The quotes shouldn't matter as this is a stub file, which isn't evaluated. The mutually recursive definition of Iterable and Iterator is indeed annoying to deal with, but mypy should be able to handle it. |
This comment was marked as outdated.
This comment was marked as outdated.
I'd like to try to help with this issue, and have some time next week. From what I understand, the PR fixes the underlying issue, but causes some apparently unrelated tests to fail in a surprising way. (The odd behaviour is limited to tests and doesn't happen if we run mypy for real.) I'm assuming this is a problem with the test machinery, and in particular with test fixtures that attempt to import My plan next week is to understand better what's going on by stepping through the test run in a debugger, but if anyone has any inkling as to what might be going on I'd be very grateful for any pointers. @chadrik or @JelleZijlstra I don't suppose you have any idea? |
I've created a simple test case to show what is breaking. This test passes (but it shouldn't).
This test is simulating the addition of a comment to an otherwise empty file. We are using the I'll continue to try to understand why this is... |
Good news - I think I've got somewhere. This WIP commit gets the number of failing tests down to only 3. 🥳 BackgroundI noticed while debugging that there were some additional errors in the error list following the first check run. These aren't surfaced anywhere obvious, I happened to find these by putting a breakpoint in the final statement of Most of the errors were along these lines:
These errors aren't present when I run it on master. How I fixed itI'm not sure if it's the correct thing to do, but I found adding @chadrik @JelleZijlstra Do you have any concerns with this as a fix? If so, please advise what would be a better thing to do. There were a couple of other test cases where instead the fix seemed to be just to add the appropriate Remaining failuresThe last three are a bit different - I'll continue to look at these tomorrow. Further contextThe reason for the more visible |
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
This shows two cases where errors disappear from the second run of dmypy. The first shows a way that "unused type ignore" errors can disappear. The case is a little complicated, but I can't yet work out how to make it smaller. The second case shows how "module X has not attribute Y" errors can disappear.
032999e
to
1f68fc6
Compare
I've now boiled this down the the minimal changes to fix the issue (though there are a lot of tests), and I think that this is finally ready for review! |
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
This which fixes issue python#9655 wherein some types of error would be lost when a file was re-processed by dmypy. This also fixes another error where sometimes files would not be re-processed by dmypy if the only error in the file was either "unused type ignore" or "ignore without code".
This catches a regression caused by the previous attempt to fix python#9655 where "type: ignore" comments are erroneously marked as unused in re-runs of dmypy. Ref: python#14835
This change shows our branch has a regression in the attrs plugin when re-running dmypy. The error produced when running this test is: Expected: foo.py:3: error: Cannot find implementation or library stub for module named "a_module_which_does_not_exist" [import-not-found] foo.py:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports == Return code: 1 Actual: foo.py:3: error: Cannot find implementation or library stub for module named "a_module_which_does_not_exist" [import-not-found] foo.py:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports foo.py:8: error: Unused "type: ignore" comment [unused-ignore] (diff) == Return code: 1
Modules are removed from the fine-grained build if they have not been "seen". This change ensures that we don't remove ancestor modules if their descendants have been seen.
1f68fc6
to
22777be
Compare
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heroic effort @meshy!
Just left a couple of tiny comments, feel free to disregard. I'll leave approval to a maintainer!
@@ -715,6 +716,29 @@ def refresh_file(module: str, path: str) -> list[str]: | |||
|
|||
return messages | |||
|
|||
def _seen_and_ancestors(self, seen: set[str]) -> set[str]: | |||
"""Return the set of seen modules along with any ancestors not already in the set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great docstring, thanks.
This is used to stop us from deleting ancestor modules from the graph | ||
when their descendants have been seen. | ||
""" | ||
seen_paths = seen.copy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No big deal, but we could do this without any conditionals like this, if you prefer:
seen_and_ancestors: set[str] = set()
for module in seen:
module_parts = module.split(".")
for i in range(len(module_parts)):
seen_and_ancestors.add(".".join(module_parts[:i + 1]))
return seen_and_ancestors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed that in some cases with many sub-modules that my way would be faster so my preference would be to keep it as-is, but I'm not strongly opposed to changing this if there's a strong will to do so?
seen_paths = seen.copy() | ||
for module_path in seen: | ||
while module_path := module_path.rpartition(".")[0]: | ||
if module_path in seen_paths: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might be able to drop this conditional and just always add the module_path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like it'd be quicker to skip this if we have a module with many submodules. I haven't measured this though.
Testing out installing this branch of your repo locally, I found the repeat dmypy runs on a module I installed in edit mode (
<my_home_folder>/Desktop/Python/Ports/NEAT-Template-Python/src/neat/neat.py:539:9:539:27: error: Property "enabled" defined in "Connection" is read-only [misc]
No content returned
Daemon crashed! Full Code Repository: |
@CoolCat467 That's great info, thank you very much. Have you tested that crash on Mypy's I'll see if I can reproduce those disappearing errors. If you're able to produce a minimal example of any of these failures I'd really appreciate it! |
@CoolCat467 I've reproduced a crash on the I don't think that the crash I got is related to these changes, though as I said, I didn't get the same one as you. I've boiled it down to a minimal example, and added details of the crash in #14645 (comment) |
@CoolCat467 I'm not able to reproduce your issue with the disappearing messages using the script in that repo. Can I please ask for detailed instructions on how to reproduce it? |
I have now updated Edit: I think the important detail was the fact I am telling dmypy absolute paths, because my use case of dmypy is as a code editor extension (idlemypyextension), where it might be quite important to tell the mypy daemon exactly which file we want it to check, because potentially the user changes projects without restarting the daemon and they might happen to have files named exactly the same way or whatnot. |
@CoolCat467 Thank you for the clarification. I've got that working now, and can reproduce the dropped errors (on this branch and master). I'll try to turn this into a minimal test case. Curiously, I'm now not seeing the crash on the third command, but I'll come back to that later. |
The best minimal example I have so far:
[build-system]
requires = ["setuptools >= 64"]
build-backend = "setuptools.build_meta"
[project]
name = "dmypy_example"
version = "0.0.1"
dependencies = []
a: str = 1 Commands: # Create a new Python 3.12 virtualenv, then:
pip install --editable .
dmypy run --export-types $(pwd)/src/foo.py
dmypy run --export-types $(pwd)/src/foo.py Results: $ pip install --editable .
...
$ dmypy run --export-types $(pwd)/src/foo.py
Daemon started
src/foo.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
Found 1 error in 1 file (checked 1 source file)
$ dmypy run --export-types $(pwd)/src/foo.py
Success: no issues found in 1 source file Notes:
My conclusion: It's disappointing that this branch doesn't fix this error, but I don't think that it's caused by this branch, so perhaps we should consider this a new bug for handling separately? |
Yea, I think it would make sense to make a new issue for this in particular.
I think this might be due to the project structure pip expects, but not completely sure Also side note, thank you for showing the use of |
As far as I know,
I'm glad you found it useful! 🙂
Thanks! I've opened a new issue so we can track it there. #16768 |
The problem this solved has been addressed in another way, so we don't need to do this re-analysis any more in order to fix the bug we've been chasing. This change is a partial revert of "Fix disappearing errors when re-running dmypy check" from a few commits back.
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Hi all! It's been a while since I've had a look at this (I got a bit overwhelmed with it, TBH). I notice that this was considered for release in 1.9, but wasn't merged because "while the PR itself is apparently ok, it doesn't actually fix the issue completely that its trying to address". Am I right in saying that the issue discovered in this comment is the blocker? Before I go about investigating that, are there any other blockers that we're aware of? |
There is currently a misbehaviour where "type: ignore" comments are erroneously marked as unused in re-runs of dmypy. There are also cases where errors disappear on the re-run.
As far as I can tell, this only happens in modules which contain an import that we don't know how to type (such as a module which does not exist), and a submodule which is unused.
There was a lot of commenting and investigation on this PR, but I hope that the committed tests and fixes illustrate and address the issue.
Related to #9655