@@ -2379,9 +2379,19 @@ def new_state(
23792379 # suppressed dependencies. Therefore, when the package with module is added,
23802380 # we need to re-calculate dependencies.
23812381 # NOTE: see comment below for why we skip this in fine-grained mode.
2382- if exist_added_packages (suppressed , manager , options ):
2382+ if exist_added_packages (suppressed , manager ):
23832383 state .parse_file () # This is safe because the cache is anyway stale.
23842384 state .compute_dependencies ()
2385+ # This is an inverse to the situation above. If we had an import like this:
2386+ # from pkg import mod
2387+ # and then mod was deleted, we need to force recompute dependencies, to
2388+ # decide whether we should still depend on a missing pkg.mod. Otherwise,
2389+ # the above import is indistinguishable from something like this:
2390+ # import pkg
2391+ # import pkg.mod
2392+ if exist_removed_submodules (dependencies , manager ):
2393+ state .parse_file () # Same as above, the current state is stale anyway.
2394+ state .compute_dependencies ()
23852395 state .size_hint = meta .size
23862396 else :
23872397 # When doing a fine-grained cache load, pretend we only
@@ -3265,7 +3275,7 @@ def find_module_and_diagnose(
32653275 raise ModuleNotFound
32663276
32673277
3268- def exist_added_packages (suppressed : list [str ], manager : BuildManager , options : Options ) -> bool :
3278+ def exist_added_packages (suppressed : list [str ], manager : BuildManager ) -> bool :
32693279 """Find if there are any newly added packages that were previously suppressed.
32703280
32713281 Exclude everything not in build for follow-imports=skip.
@@ -3278,13 +3288,41 @@ def exist_added_packages(suppressed: list[str], manager: BuildManager, options:
32783288 path = find_module_simple (dep , manager )
32793289 if not path :
32803290 continue
3281- if options .follow_imports == "skip" and (
3291+ options = manager .options .clone_for_module (dep )
3292+ # Technically this is not 100% correct, since we can have:
3293+ # from pkg import mod
3294+ # with
3295+ # [mypy-pkg]
3296+ # follow-import = silent
3297+ # [mypy-pkg.mod]
3298+ # follow-imports = normal
3299+ # But such cases are extremely rare, and this allows us to avoid
3300+ # massive performance impact in much more common situations.
3301+ if options .follow_imports in ("skip" , "error" ) and (
32823302 not path .endswith (".pyi" ) or options .follow_imports_for_stubs
32833303 ):
32843304 continue
3285- if "__init__.py" in path :
3286- # It is better to have a bit lenient test, this will only slightly reduce
3287- # performance, while having a too strict test may affect correctness.
3305+ if os .path .basename (path ) in ("__init__.py" , "__init__.pyi" ):
3306+ return True
3307+ return False
3308+
3309+
3310+ def exist_removed_submodules (dependencies : list [str ], manager : BuildManager ) -> bool :
3311+ """Find if there are any submodules of packages that are now missing.
3312+
3313+ This is conceptually an inverse of exist_added_packages().
3314+ """
3315+ dependencies_set = set (dependencies )
3316+ for dep in dependencies :
3317+ if "." not in dep :
3318+ continue
3319+ if dep in manager .source_set .source_modules :
3320+ # We still know it is definitely a module.
3321+ continue
3322+ direct_ancestor , _ = dep .rsplit ("." , maxsplit = 1 )
3323+ if direct_ancestor not in dependencies_set :
3324+ continue
3325+ if find_module_simple (dep , manager ) is None :
32883326 return True
32893327 return False
32903328
@@ -3809,6 +3847,7 @@ def load_graph(
38093847 for dep in st .suppressed :
38103848 if dep in graph :
38113849 st .add_dependency (dep )
3850+ manager .missing_modules .discard (dep )
38123851 # Second, in the initial loop we skip indirect dependencies, so to make indirect dependencies
38133852 # behave more consistently with regular ones, we suppress them manually here (when needed).
38143853 for st in graph .values ():
0 commit comments