Skip to content

Commit 99957d7

Browse files
committed
Do not cache lookup results that may become invalid in future
1 parent bd1f51a commit 99957d7

File tree

1 file changed

+33
-21
lines changed

1 file changed

+33
-21
lines changed

mypy/modulefinder.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,21 @@ def find_module(self, id: str, *, fast_path: bool = False) -> ModuleSearchResult
320320
use_typeshed = self._typeshed_has_version(id)
321321
elif top_level in self.stdlib_py_versions:
322322
use_typeshed = self._typeshed_has_version(top_level)
323-
self.results[id] = self._find_module(id, use_typeshed)
324-
if (
325-
not (fast_path or (self.options is not None and self.options.fast_module_lookup))
326-
and self.results[id] is ModuleNotFoundReason.NOT_FOUND
327-
and self._can_find_module_in_parent_dir(id)
328-
):
329-
self.results[id] = ModuleNotFoundReason.WRONG_WORKING_DIRECTORY
323+
result, should_cache = self._find_module(id, use_typeshed)
324+
if should_cache:
325+
if (
326+
not (
327+
fast_path or (self.options is not None and self.options.fast_module_lookup)
328+
)
329+
and result is ModuleNotFoundReason.NOT_FOUND
330+
and self._can_find_module_in_parent_dir(id)
331+
):
332+
self.results[id] = ModuleNotFoundReason.WRONG_WORKING_DIRECTORY
333+
else:
334+
self.results[id] = result
335+
return self.results[id]
336+
else:
337+
return result
330338
return self.results[id]
331339

332340
def _typeshed_has_version(self, module: str) -> bool:
@@ -384,11 +392,11 @@ def _can_find_module_in_parent_dir(self, id: str) -> bool:
384392
while any(is_init_file(file) for file in os.listdir(working_dir)):
385393
working_dir = os.path.dirname(working_dir)
386394
parent_search.search_paths = SearchPaths((working_dir,), (), (), ())
387-
if not isinstance(parent_search._find_module(id, False), ModuleNotFoundReason):
395+
if not isinstance(parent_search._find_module(id, False)[0], ModuleNotFoundReason):
388396
return True
389397
return False
390398

391-
def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
399+
def _find_module(self, id: str, use_typeshed: bool) -> tuple[ModuleSearchResult, bool]:
392400
fscache = self.fscache
393401

394402
# Fast path for any modules in the current source set.
@@ -424,7 +432,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
424432
else None
425433
)
426434
if p:
427-
return p
435+
return p, True
428436

429437
# If we're looking for a module like 'foo.bar.baz', it's likely that most of the
430438
# many elements of lib_path don't even have a subdirectory 'foo/bar'. Discover
@@ -444,6 +452,9 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
444452
for component in (components[0], components[0] + "-stubs")
445453
for package_dir in self.find_lib_path_dirs(component, self.search_paths.package_path)
446454
}
455+
# Caching FOUND_WITHOUT_TYPE_HINTS is not always safe. That causes issues with
456+
# typed subpackages in namespace packages.
457+
can_cache_any_result = True
447458
for pkg_dir in self.search_paths.package_path:
448459
if pkg_dir not in candidate_package_dirs:
449460
continue
@@ -475,6 +486,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
475486
if isinstance(non_stub_match, ModuleNotFoundReason):
476487
if non_stub_match is ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS:
477488
found_possible_third_party_missing_type_hints = True
489+
can_cache_any_result = False
478490
else:
479491
third_party_inline_dirs.append(non_stub_match)
480492
self._update_ns_ancestors(components, non_stub_match)
@@ -513,7 +525,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
513525
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
514526
near_misses.append((path_stubs, dir_prefix))
515527
else:
516-
return path_stubs
528+
return path_stubs, True
517529

518530
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
519531
for extension in PYTHON_EXTENSIONS:
@@ -523,7 +535,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
523535
if verify and not verify_module(fscache, id, path, dir_prefix):
524536
near_misses.append((path, dir_prefix))
525537
continue
526-
return path
538+
return path, True
527539

528540
# In namespace mode, register a potential namespace package
529541
if self.options and self.options.namespace_packages:
@@ -541,7 +553,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
541553
if verify and not verify_module(fscache, id, path, dir_prefix):
542554
near_misses.append((path, dir_prefix))
543555
continue
544-
return path
556+
return path, True
545557

546558
# In namespace mode, re-check those entries that had 'verify'.
547559
# Assume search path entries xxx, yyy and zzz, and we're
@@ -570,35 +582,35 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
570582
for path, dir_prefix in near_misses
571583
]
572584
index = levels.index(max(levels))
573-
return near_misses[index][0]
585+
return near_misses[index][0], True
574586

575587
# Finally, we may be asked to produce an ancestor for an
576588
# installed package with a py.typed marker that is a
577589
# subpackage of a namespace package. We only fess up to these
578590
# if we would otherwise return "not found".
579591
ancestor = self.ns_ancestors.get(id)
580592
if ancestor is not None:
581-
return ancestor
593+
return ancestor, True
582594

583595
approved_dist_name = stub_distribution_name(id)
584596
if approved_dist_name:
585597
if len(components) == 1:
586-
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED
598+
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED, True
587599
# If we're a missing submodule of an already installed approved stubs, we don't want to
588600
# error with APPROVED_STUBS_NOT_INSTALLED, but rather want to return NOT_FOUND.
589601
for i in range(1, len(components)):
590602
parent_id = ".".join(components[:i])
591603
if stub_distribution_name(parent_id) == approved_dist_name:
592604
break
593605
else:
594-
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED
606+
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED, True
595607
if self.find_module(parent_id) is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED:
596-
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED
597-
return ModuleNotFoundReason.NOT_FOUND
608+
return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED, True
609+
return ModuleNotFoundReason.NOT_FOUND, True
598610

599611
if found_possible_third_party_missing_type_hints:
600-
return ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS
601-
return ModuleNotFoundReason.NOT_FOUND
612+
return ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS, can_cache_any_result
613+
return ModuleNotFoundReason.NOT_FOUND, True
602614

603615
def find_modules_recursive(self, module: str) -> list[BuildSource]:
604616
module_path = self.find_module(module, fast_path=True)

0 commit comments

Comments
 (0)