Skip to content

Commit cdddc50

Browse files
authored
Fix dmypy run on Windows (#15429)
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`.
1 parent c63e873 commit cdddc50

File tree

4 files changed

+49
-2
lines changed

4 files changed

+49
-2
lines changed

mypy/dmypy_server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def cmd_run(
330330
header=argparse.SUPPRESS,
331331
)
332332
# Signal that we need to restart if the options have changed
333-
if self.options_snapshot != options.snapshot():
333+
if not options.compare_stable(self.options_snapshot):
334334
return {"restart": "configuration changed"}
335335
if __version__ != version:
336336
return {"restart": "mypy version changed"}

mypy/errorcodes.py

+8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ def __init__(
3737
def __str__(self) -> str:
3838
return f"<ErrorCode {self.code}>"
3939

40+
def __eq__(self, other: object) -> bool:
41+
if not isinstance(other, ErrorCode):
42+
return False
43+
return self.code == other.code
44+
45+
def __hash__(self) -> int:
46+
return hash((self.code,))
47+
4048

4149
ATTR_DEFINED: Final = ErrorCode("attr-defined", "Check that attribute exists", "General")
4250
NAME_DEFINED: Final = ErrorCode("name-defined", "Check that name is defined", "General")

mypy/options.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def use_or_syntax(self) -> bool:
373373
def new_semantic_analyzer(self) -> bool:
374374
return True
375375

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

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

414415
return new_options
415416

417+
def compare_stable(self, other_snapshot: dict[str, object]) -> bool:
418+
"""Compare options in a way that is stable for snapshot() -> apply_changes() roundtrip.
419+
420+
This is needed because apply_changes() has non-trivial effects for some flags, so
421+
Options().apply_changes(options.snapshot()) may result in a (slightly) different object.
422+
"""
423+
return (
424+
Options().apply_changes(self.snapshot()).snapshot()
425+
== Options().apply_changes(other_snapshot).snapshot()
426+
)
427+
416428
def build_per_module_cache(self) -> None:
417429
self._per_module_cache = {}
418430

test-data/unit/daemon.test

+27
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ Daemon stopped
2828
[file foo.py]
2929
def f(): pass
3030

31+
[case testDaemonRunIgnoreMissingImports]
32+
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports
33+
Daemon started
34+
Success: no issues found in 1 source file
35+
$ dmypy stop
36+
Daemon stopped
37+
[file foo.py]
38+
def f(): pass
39+
40+
[case testDaemonRunErrorCodes]
41+
$ dmypy run -- foo.py --follow-imports=error --disable-error-code=type-abstract
42+
Daemon started
43+
Success: no issues found in 1 source file
44+
$ dmypy stop
45+
Daemon stopped
46+
[file foo.py]
47+
def f(): pass
48+
49+
[case testDaemonRunCombinedOptions]
50+
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports --disable-error-code=type-abstract
51+
Daemon started
52+
Success: no issues found in 1 source file
53+
$ dmypy stop
54+
Daemon stopped
55+
[file foo.py]
56+
def f(): pass
57+
3158
[case testDaemonIgnoreConfigFiles]
3259
$ dmypy start -- --follow-imports=error
3360
Daemon started

0 commit comments

Comments
 (0)