Skip to content

Commit f68f463

Browse files
ilevkivskyiJukkaL
authored andcommitted
Fix file reloading in dmypy with --export-types (#16359)
Fixes #15794 Unfortunately, this requires to pass `--export-types` to `dmypy run` if one wants to inspect a file that was previously kicked out of the build.
1 parent 5624f40 commit f68f463

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

mypy/dmypy_server.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,15 +393,21 @@ def cmd_recheck(
393393
t1 = time.time()
394394
manager = self.fine_grained_manager.manager
395395
manager.log(f"fine-grained increment: cmd_recheck: {t1 - t0:.3f}s")
396-
self.options.export_types = export_types
396+
old_export_types = self.options.export_types
397+
self.options.export_types = self.options.export_types or export_types
397398
if not self.following_imports():
398-
messages = self.fine_grained_increment(sources, remove, update)
399+
messages = self.fine_grained_increment(
400+
sources, remove, update, explicit_export_types=export_types
401+
)
399402
else:
400403
assert remove is None and update is None
401-
messages = self.fine_grained_increment_follow_imports(sources)
404+
messages = self.fine_grained_increment_follow_imports(
405+
sources, explicit_export_types=export_types
406+
)
402407
res = self.increment_output(messages, sources, is_tty, terminal_width)
403408
self.flush_caches()
404409
self.update_stats(res)
410+
self.options.export_types = old_export_types
405411
return res
406412

407413
def check(
@@ -412,17 +418,21 @@ def check(
412418
If is_tty is True format the output nicely with colors and summary line
413419
(unless disabled in self.options). Also pass the terminal_width to formatter.
414420
"""
415-
self.options.export_types = export_types
421+
old_export_types = self.options.export_types
422+
self.options.export_types = self.options.export_types or export_types
416423
if not self.fine_grained_manager:
417424
res = self.initialize_fine_grained(sources, is_tty, terminal_width)
418425
else:
419426
if not self.following_imports():
420-
messages = self.fine_grained_increment(sources)
427+
messages = self.fine_grained_increment(sources, explicit_export_types=export_types)
421428
else:
422-
messages = self.fine_grained_increment_follow_imports(sources)
429+
messages = self.fine_grained_increment_follow_imports(
430+
sources, explicit_export_types=export_types
431+
)
423432
res = self.increment_output(messages, sources, is_tty, terminal_width)
424433
self.flush_caches()
425434
self.update_stats(res)
435+
self.options.export_types = old_export_types
426436
return res
427437

428438
def flush_caches(self) -> None:
@@ -535,6 +545,7 @@ def fine_grained_increment(
535545
sources: list[BuildSource],
536546
remove: list[str] | None = None,
537547
update: list[str] | None = None,
548+
explicit_export_types: bool = False,
538549
) -> list[str]:
539550
"""Perform a fine-grained type checking increment.
540551
@@ -545,6 +556,8 @@ def fine_grained_increment(
545556
sources: sources passed on the command line
546557
remove: paths of files that have been removed
547558
update: paths of files that have been changed or created
559+
explicit_export_types: --export-type was passed in a check command
560+
(as opposite to being set in dmypy start)
548561
"""
549562
assert self.fine_grained_manager is not None
550563
manager = self.fine_grained_manager.manager
@@ -559,6 +572,10 @@ def fine_grained_increment(
559572
# Use the remove/update lists to update fswatcher.
560573
# This avoids calling stat() for unchanged files.
561574
changed, removed = self.update_changed(sources, remove or [], update or [])
575+
if explicit_export_types:
576+
# If --export-types is given, we need to force full re-checking of all
577+
# explicitly passed files, since we need to visit each expression.
578+
add_all_sources_to_changed(sources, changed)
562579
changed += self.find_added_suppressed(
563580
self.fine_grained_manager.graph, set(), manager.search_paths
564581
)
@@ -577,7 +594,9 @@ def fine_grained_increment(
577594
self.previous_sources = sources
578595
return messages
579596

580-
def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> list[str]:
597+
def fine_grained_increment_follow_imports(
598+
self, sources: list[BuildSource], explicit_export_types: bool = False
599+
) -> list[str]:
581600
"""Like fine_grained_increment, but follow imports."""
582601
t0 = time.time()
583602

@@ -603,6 +622,9 @@ def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> l
603622
changed, new_files = self.find_reachable_changed_modules(
604623
sources, graph, seen, changed_paths
605624
)
625+
if explicit_export_types:
626+
# Same as in fine_grained_increment().
627+
add_all_sources_to_changed(sources, changed)
606628
sources.extend(new_files)
607629

608630
# Process changes directly reachable from roots.
@@ -1011,6 +1033,22 @@ def find_all_sources_in_build(
10111033
return result
10121034

10131035

1036+
def add_all_sources_to_changed(sources: list[BuildSource], changed: list[tuple[str, str]]) -> None:
1037+
"""Add all (explicit) sources to the list changed files in place.
1038+
1039+
Use this when re-processing of unchanged files is needed (e.g. for
1040+
the purpose of exporting types for inspections).
1041+
"""
1042+
changed_set = set(changed)
1043+
changed.extend(
1044+
[
1045+
(bs.module, bs.path)
1046+
for bs in sources
1047+
if bs.path and (bs.module, bs.path) not in changed_set
1048+
]
1049+
)
1050+
1051+
10141052
def fix_module_deps(graph: mypy.build.Graph) -> None:
10151053
"""After an incremental update, update module dependencies to reflect the new state.
10161054

mypy/test/testfinegrained.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo
149149
options.use_fine_grained_cache = self.use_cache and not build_cache
150150
options.cache_fine_grained = self.use_cache
151151
options.local_partial_types = True
152+
options.export_types = "inspect" in testcase.file
152153
# Treat empty bodies safely for these test cases.
153154
options.allow_empty_bodies = not testcase.name.endswith("_no_empty")
154155
if re.search("flags:.*--follow-imports", source) is None:
@@ -163,7 +164,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo
163164
return options
164165

165166
def run_check(self, server: Server, sources: list[BuildSource]) -> list[str]:
166-
response = server.check(sources, export_types=True, is_tty=False, terminal_width=-1)
167+
response = server.check(sources, export_types=False, is_tty=False, terminal_width=-1)
167168
out = response["out"] or response["err"]
168169
assert isinstance(out, str)
169170
return out.splitlines()

test-data/unit/daemon.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,33 @@ def bar() -> None:
360360
x = foo('abc') # type: str
361361
foo(arg='xyz')
362362

363+
[case testDaemonInspectCheck]
364+
$ dmypy start
365+
Daemon started
366+
$ dmypy check foo.py
367+
Success: no issues found in 1 source file
368+
$ dmypy check foo.py --export-types
369+
Success: no issues found in 1 source file
370+
$ dmypy inspect foo.py:1:1
371+
"int"
372+
[file foo.py]
373+
x = 1
374+
375+
[case testDaemonInspectRun]
376+
$ dmypy run test1.py
377+
Daemon started
378+
Success: no issues found in 1 source file
379+
$ dmypy run test2.py
380+
Success: no issues found in 1 source file
381+
$ dmypy run test1.py --export-types
382+
Success: no issues found in 1 source file
383+
$ dmypy inspect test1.py:1:1
384+
"int"
385+
[file test1.py]
386+
a: int
387+
[file test2.py]
388+
a: str
389+
363390
[case testDaemonGetType]
364391
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary --python-version 3.8
365392
Daemon started

0 commit comments

Comments
 (0)