Repository: python/mypy

Issue:
Failure following imports in daemon when importing a submodule
This test case fails:

```
[case testFollowImportsNormalFromImportModule]
# flags: --follow-imports=normal
# cmd: mypy main.py

[file main.py]
import pkg

[file pkg/__init__.py.2]
from . import mod1

[file pkg/mod1.py.2]
from . import mod2

[file pkg/mod2.py.2]

[out]
main.py:1: error: Cannot find implementation or library stub for module named "pkg"
main.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
==
```

The second increment generates an unexpected error:

```
pkg/__init__.py:1: error: Module 'pkg' has no attribute 'mod1'
```

If I change the import from `from . import mod1` to `import pkg.mod1` the test case passes, so this seems related from `from ... import`.


Discussion:

Patch:
diff --git a/mypy/server/update.py b/mypy/server/update.py
--- a/mypy/server/update.py
+++ b/mypy/server/update.py
@@ -232,15 +232,17 @@ def update(self,
             self.manager.log_fine_grained('previous targets with errors: %s' %
                              sorted(self.previous_targets_with_errors))
 
+        blocking_error = None
         if self.blocking_error:
             # Handle blocking errors first. We'll exit as soon as we find a
             # module that still has blocking errors.
             self.manager.log_fine_grained('existing blocker: %s' % self.blocking_error[0])
             changed_modules = dedupe_modules([self.blocking_error] + changed_modules)
+            blocking_error = self.blocking_error[0]
             self.blocking_error = None
 
         while True:
-            result = self.update_one(changed_modules, initial_set, removed_set)
+            result = self.update_one(changed_modules, initial_set, removed_set, blocking_error)
             changed_modules, (next_id, next_path), blocker_messages = result
 
             if blocker_messages is not None:
@@ -289,9 +291,10 @@ def trigger(self, target: str) -> List[str]:
     def update_one(self,
                    changed_modules: List[Tuple[str, str]],
                    initial_set: Set[str],
-                   removed_set: Set[str]) -> Tuple[List[Tuple[str, str]],
-                                                   Tuple[str, str],
-                                                   Optional[List[str]]]:
+                   removed_set: Set[str],
+                   blocking_error: Optional[str]) -> Tuple[List[Tuple[str, str]],
+                                                           Tuple[str, str],
+                                                           Optional[List[str]]]:
         """Process a module from the list of changed modules.
 
         Returns:
@@ -303,9 +306,17 @@ def update_one(self,
         """
         t0 = time.time()
         next_id, next_path = changed_modules.pop(0)
-        if next_id not in self.previous_modules and next_id not in initial_set:
-            self.manager.log_fine_grained('skip %r (module not in import graph)' % next_id)
+
+        # If we have a module with a blocking error that is no longer
+        # in the import graph, we must skip it as otherwise we'll be
+        # stuck with the blocking error.
+        if (next_id == blocking_error
+                and next_id not in self.previous_modules
+                and next_id not in initial_set):
+            self.manager.log_fine_grained(
+                'skip %r (module with blocking error not in import graph)' % next_id)
             return changed_modules, (next_id, next_path), None
+
         result = self.update_module(next_id, next_path, next_id in removed_set)
         remaining, (next_id, next_path), blocker_messages = result
         changed_modules = [(id, path) for id, path in changed_modules


Test patch:
diff --git a/test-data/unit/fine-grained-follow-imports.test b/test-data/unit/fine-grained-follow-imports.test
--- a/test-data/unit/fine-grained-follow-imports.test
+++ b/test-data/unit/fine-grained-follow-imports.test
@@ -719,3 +719,23 @@ def f() -> None: pass
 [out]
 ==
 main.py:2: error: Too many arguments for "f"
+
+[case testFollowImportsNormalMultipleImportedModulesSpecialCase]
+# flags: --follow-imports=normal
+# cmd: mypy main.py
+
+[file main.py]
+import pkg
+
+[file pkg/__init__.py.2]
+from . import mod1
+
+[file pkg/mod1.py.2]
+from . import mod2
+
+[file pkg/mod2.py.2]
+
+[out]
+main.py:1: error: Cannot find implementation or library stub for module named "pkg"
+main.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
+==