Skip to content

Commit

Permalink
Fix dmypy run on Windows (#15429)
Browse files Browse the repository at this point in the history
Fixes #10709 

Two changes here:
* Add equality methods to `ErrorCode` because they may appear in options
snapshots
* Use stable comparison for option snapshots, since
`Options.apply_changes()` does some unrelated things with flags

Both are only relevant on Windows where options may roundtrip as:
`options -> snapshot -> pickle -> base64 -> unpickle -> apply_changes`.
  • Loading branch information
ilevkivskyi authored Jun 14, 2023
1 parent c63e873 commit cdddc50
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 2 deletions.
2 changes: 1 addition & 1 deletion mypy/dmypy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def cmd_run(
header=argparse.SUPPRESS,
)
# Signal that we need to restart if the options have changed
if self.options_snapshot != options.snapshot():
if not options.compare_stable(self.options_snapshot):
return {"restart": "configuration changed"}
if __version__ != version:
return {"restart": "mypy version changed"}
Expand Down
8 changes: 8 additions & 0 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def __init__(
def __str__(self) -> str:
return f"<ErrorCode {self.code}>"

def __eq__(self, other: object) -> bool:
if not isinstance(other, ErrorCode):
return False
return self.code == other.code

def __hash__(self) -> int:
return hash((self.code,))


ATTR_DEFINED: Final = ErrorCode("attr-defined", "Check that attribute exists", "General")
NAME_DEFINED: Final = ErrorCode("name-defined", "Check that name is defined", "General")
Expand Down
14 changes: 13 additions & 1 deletion mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def use_or_syntax(self) -> bool:
def new_semantic_analyzer(self) -> bool:
return True

def snapshot(self) -> object:
def snapshot(self) -> dict[str, object]:
"""Produce a comparable snapshot of this Option"""
# Under mypyc, we don't have a __dict__, so we need to do worse things.
d = dict(getattr(self, "__dict__", ()))
Expand All @@ -388,6 +388,7 @@ def __repr__(self) -> str:
return f"Options({pprint.pformat(self.snapshot())})"

def apply_changes(self, changes: dict[str, object]) -> Options:
# Note: effects of this method *must* be idempotent.
new_options = Options()
# Under mypyc, we don't have a __dict__, so we need to do worse things.
replace_object_state(new_options, self, copy_dict=True)
Expand All @@ -413,6 +414,17 @@ def apply_changes(self, changes: dict[str, object]) -> Options:

return new_options

def compare_stable(self, other_snapshot: dict[str, object]) -> bool:
"""Compare options in a way that is stable for snapshot() -> apply_changes() roundtrip.
This is needed because apply_changes() has non-trivial effects for some flags, so
Options().apply_changes(options.snapshot()) may result in a (slightly) different object.
"""
return (
Options().apply_changes(self.snapshot()).snapshot()
== Options().apply_changes(other_snapshot).snapshot()
)

def build_per_module_cache(self) -> None:
self._per_module_cache = {}

Expand Down
27 changes: 27 additions & 0 deletions test-data/unit/daemon.test
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,33 @@ Daemon stopped
[file foo.py]
def f(): pass

[case testDaemonRunIgnoreMissingImports]
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass

[case testDaemonRunErrorCodes]
$ dmypy run -- foo.py --follow-imports=error --disable-error-code=type-abstract
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass

[case testDaemonRunCombinedOptions]
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports --disable-error-code=type-abstract
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass

[case testDaemonIgnoreConfigFiles]
$ dmypy start -- --follow-imports=error
Daemon started
Expand Down

0 comments on commit cdddc50

Please sign in to comment.