Skip to content

Commit 976d105

Browse files
authored
Fix couple crashes in dmypy (#18098)
Fixes #18019 Fixes #17775 These two are essentially variations of the same thing. Instead of adding e.g. `types` to `SENSITIVE_INTERNAL_MODULES` (which would be fragile and re-introduce same crashes whenever we add a new "core" module) I add _all stdlib modules_. The only scenario when stdlib changes is when a version of mypy changes, and in this case the daemon will be (or should be) restarted anyway. While adding tests for these I noticed a discrepancy in `--follow-imports=normal` in the daemon: the files explicitly added on the command line should be always treated as changed, since otherwise we will not detect errors if a file was removed from command line in an intermediate run. Finally the tests also discovered a spurious error when cache is disabled (via `/dev/null`).
1 parent 1de4871 commit 976d105

File tree

6 files changed

+75
-21
lines changed

6 files changed

+75
-21
lines changed

mypy/build.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,10 @@ def generate_deps_for_cache(manager: BuildManager, graph: Graph) -> dict[str, di
10501050
def write_plugins_snapshot(manager: BuildManager) -> None:
10511051
"""Write snapshot of versions and hashes of currently active plugins."""
10521052
snapshot = json_dumps(manager.plugins_snapshot)
1053-
if not manager.metastore.write(PLUGIN_SNAPSHOT_FILE, snapshot):
1053+
if (
1054+
not manager.metastore.write(PLUGIN_SNAPSHOT_FILE, snapshot)
1055+
and manager.options.cache_dir != os.devnull
1056+
):
10541057
manager.errors.set_file(_cache_dir_prefix(manager.options), None, manager.options)
10551058
manager.errors.report(0, 0, "Error writing plugins snapshot", blocker=True)
10561059

mypy/dmypy_server.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ def fine_grained_increment_follow_imports(
625625
changed, new_files = self.find_reachable_changed_modules(
626626
sources, graph, seen, changed_paths
627627
)
628+
# Same as in fine_grained_increment().
629+
self.add_explicitly_new(sources, changed)
628630
if explicit_export_types:
629631
# Same as in fine_grained_increment().
630632
add_all_sources_to_changed(sources, changed)
@@ -888,6 +890,22 @@ def _find_changed(
888890
assert path
889891
removed.append((source.module, path))
890892

893+
self.add_explicitly_new(sources, changed)
894+
895+
# Find anything that has had its module path change because of added or removed __init__s
896+
last = {s.path: s.module for s in self.previous_sources}
897+
for s in sources:
898+
assert s.path
899+
if s.path in last and last[s.path] != s.module:
900+
# Mark it as removed from its old name and changed at its new name
901+
removed.append((last[s.path], s.path))
902+
changed.append((s.module, s.path))
903+
904+
return changed, removed
905+
906+
def add_explicitly_new(
907+
self, sources: list[BuildSource], changed: list[tuple[str, str]]
908+
) -> None:
891909
# Always add modules that were (re-)added, since they may be detected as not changed by
892910
# fswatcher (if they were actually not changed), but they may still need to be checked
893911
# in case they had errors before they were deleted from sources on previous runs.
@@ -903,17 +921,6 @@ def _find_changed(
903921
]
904922
)
905923

906-
# Find anything that has had its module path change because of added or removed __init__s
907-
last = {s.path: s.module for s in self.previous_sources}
908-
for s in sources:
909-
assert s.path
910-
if s.path in last and last[s.path] != s.module:
911-
# Mark it as removed from its old name and changed at its new name
912-
removed.append((last[s.path], s.path))
913-
changed.append((s.module, s.path))
914-
915-
return changed, removed
916-
917924
def cmd_inspect(
918925
self,
919926
show: str,

mypy/metastore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class MetadataStore:
2727

2828
@abstractmethod
2929
def getmtime(self, name: str) -> float:
30-
"""Read the mtime of a metadata entry..
30+
"""Read the mtime of a metadata entry.
3131
3232
Raises FileNotFound if the entry does not exist.
3333
"""

mypy/server/update.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,7 @@
146146
TypeInfo,
147147
)
148148
from mypy.options import Options
149-
from mypy.semanal_main import (
150-
core_modules,
151-
semantic_analysis_for_scc,
152-
semantic_analysis_for_targets,
153-
)
149+
from mypy.semanal_main import semantic_analysis_for_scc, semantic_analysis_for_targets
154150
from mypy.server.astdiff import (
155151
SymbolSnapshot,
156152
compare_symbol_table_snapshots,
@@ -162,11 +158,12 @@
162158
from mypy.server.target import trigger_to_target
163159
from mypy.server.trigger import WILDCARD_TAG, make_trigger
164160
from mypy.typestate import type_state
165-
from mypy.util import module_prefix, split_target
161+
from mypy.util import is_stdlib_file, module_prefix, split_target
166162

167163
MAX_ITER: Final = 1000
168164

169-
SENSITIVE_INTERNAL_MODULES = tuple(core_modules) + ("mypy_extensions", "typing_extensions")
165+
# These are modules beyond stdlib that have some special meaning for mypy.
166+
SENSITIVE_INTERNAL_MODULES = ("mypy_extensions", "typing_extensions")
170167

171168

172169
class FineGrainedBuildManager:
@@ -406,7 +403,10 @@ def update_module(
406403
# builtins and friends could potentially get triggered because
407404
# of protocol stuff, but nothing good could possibly come from
408405
# actually updating them.
409-
if module in SENSITIVE_INTERNAL_MODULES:
406+
if (
407+
is_stdlib_file(self.manager.options.abs_custom_typeshed_dir, path)
408+
or module in SENSITIVE_INTERNAL_MODULES
409+
):
410410
return [], (module, path), None
411411

412412
manager = self.manager

mypy/util.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,18 @@ def is_typeshed_file(typeshed_dir: str | None, file: str) -> bool:
870870
return False
871871

872872

873+
def is_stdlib_file(typeshed_dir: str | None, file: str) -> bool:
874+
if "stdlib" not in file:
875+
# Fast path
876+
return False
877+
typeshed_dir = typeshed_dir if typeshed_dir is not None else TYPESHED_DIR
878+
stdlib_dir = os.path.join(typeshed_dir, "stdlib")
879+
try:
880+
return os.path.commonpath((stdlib_dir, os.path.abspath(file))) == stdlib_dir
881+
except ValueError: # Different drives on Windows
882+
return False
883+
884+
873885
def is_stub_package_file(file: str) -> bool:
874886
# Use hacky heuristics to check whether file is part of a PEP 561 stub package.
875887
if not file.endswith(".pyi"):

test-data/unit/daemon.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,38 @@ mypy-daemon: error: Missing target module, package, files, or command.
263263
$ dmypy stop
264264
Daemon stopped
265265

266+
[case testDaemonRunTwoFilesFullTypeshed]
267+
$ dmypy run x.py
268+
Daemon started
269+
Success: no issues found in 1 source file
270+
$ dmypy run y.py
271+
Success: no issues found in 1 source file
272+
$ dmypy run x.py
273+
Success: no issues found in 1 source file
274+
[file x.py]
275+
[file y.py]
276+
277+
[case testDaemonCheckTwoFilesFullTypeshed]
278+
$ dmypy start
279+
Daemon started
280+
$ dmypy check foo.py
281+
foo.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
282+
Found 1 error in 1 file (checked 1 source file)
283+
== Return code: 1
284+
$ dmypy check bar.py
285+
Success: no issues found in 1 source file
286+
$ dmypy check foo.py
287+
foo.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
288+
Found 1 error in 1 file (checked 1 source file)
289+
== Return code: 1
290+
[file foo.py]
291+
from bar import add
292+
x: str = add("a", "b")
293+
x_error: int = add("a", "b")
294+
[file bar.py]
295+
def add(a, b) -> str:
296+
return a + b
297+
266298
[case testDaemonWarningSuccessExitCode-posix]
267299
$ dmypy run -- foo.py --follow-imports=error --python-version=3.11
268300
Daemon started

0 commit comments

Comments
 (0)