Repository: python/mypy

Issue:
Missing imports don't obey --no-implicit-reexport
Noticed this while making #9515, will fix once it's merged. Related to the problem behind #8649, which I fixed a while ago, but was timid about and hence didn't think about this bug at that time.

```
~/tmp/rexp λ cat x.py
from mystery import a, b as b, c as d
~/tmp/rexp λ cat y.py
from x import a
from x import b
from x import c
from x import d
~/tmp/rexp λ mypy --no-implicit-reexport .
x.py:1: error: Cannot find implementation or library stub for module named 'mystery'
x.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
y.py:3: error: Module 'x' has no attribute 'c'
Found 2 errors in 2 files (checked 2 source files)
```

whereas you'd expect:
```
~/tmp/rexp λ mypy --no-implicit-reexport . 
x.py:1: error: Cannot find implementation or library stub for module named 'mystery'
x.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
y.py:1: error: Module 'x' does not explicitly export attribute 'a'; implicit reexport disabled
y.py:3: error: Module 'x' has no attribute 'c'
y.py:4: error: Module 'x' does not explicitly export attribute 'd'; implicit reexport disabled
Found 4 errors in 2 files (checked 2 source files)
```


Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1776,11 +1776,15 @@ def visit_import_from(self, imp: ImportFrom) -> None:
                 )
             elif module and not missing_submodule:
                 # Target module exists but the imported name is missing or hidden.
-                self.report_missing_module_attribute(module_id, id, imported_id, imp)
+                self.report_missing_module_attribute(
+                    module_id, id, imported_id, module_public=module_public,
+                    module_hidden=not module_public, context=imp
+                )
             else:
                 # Import of a missing (sub)module.
                 self.add_unknown_imported_symbol(
-                    imported_id, imp, target_name=fullname, module_public=module_public
+                    imported_id, imp, target_name=fullname, module_public=module_public,
+                    module_hidden=not module_public
                 )
 
     def process_imported_symbol(self,
@@ -1795,7 +1799,10 @@ def process_imported_symbol(self,
 
         if isinstance(node.node, PlaceholderNode):
             if self.final_iteration:
-                self.report_missing_module_attribute(module_id, id, imported_id, context)
+                self.report_missing_module_attribute(
+                    module_id, id, imported_id, module_public=module_public,
+                    module_hidden=module_hidden, context=context
+                )
                 return
             else:
                 # This might become a type.
@@ -1820,8 +1827,10 @@ def process_imported_symbol(self,
                                  module_public=module_public,
                                  module_hidden=module_hidden)
 
-    def report_missing_module_attribute(self, import_id: str, source_id: str, imported_id: str,
-                                        context: Node) -> None:
+    def report_missing_module_attribute(
+        self, import_id: str, source_id: str, imported_id: str, module_public: bool,
+        module_hidden: bool, context: Node
+    ) -> None:
         # Missing attribute.
         if self.is_incomplete_namespace(import_id):
             # We don't know whether the name will be there, since the namespace
@@ -1842,7 +1851,10 @@ def report_missing_module_attribute(self, import_id: str, source_id: str, import
                     suggestion = "; maybe {}?".format(pretty_seq(matches, "or"))
                     message += "{}".format(suggestion)
         self.fail(message, context, code=codes.ATTR_DEFINED)
-        self.add_unknown_imported_symbol(imported_id, context)
+        self.add_unknown_imported_symbol(
+            imported_id, context, target_name=None, module_public=module_public,
+            module_hidden=not module_public
+        )
 
         if import_id == 'typing':
             # The user probably has a missing definition in a test fixture. Let's verify.
@@ -4438,7 +4450,8 @@ def add_module_symbol(self,
                             module_hidden=module_hidden)
         else:
             self.add_unknown_imported_symbol(
-                as_id, context, target_name=id, module_public=module_public
+                as_id, context, target_name=id, module_public=module_public,
+                module_hidden=module_hidden
             )
 
     def add_imported_symbol(self,
@@ -4457,8 +4470,9 @@ def add_imported_symbol(self,
     def add_unknown_imported_symbol(self,
                                     name: str,
                                     context: Context,
-                                    target_name: Optional[str] = None,
-                                    module_public: bool = True) -> None:
+                                    target_name: Optional[str],
+                                    module_public: bool,
+                                    module_hidden: bool) -> None:
         """Add symbol that we don't know what it points to because resolving an import failed.
 
         This can happen if a module is missing, or it is present, but doesn't have
@@ -4486,7 +4500,9 @@ def add_unknown_imported_symbol(self,
         any_type = AnyType(TypeOfAny.from_unimported_type, missing_import_name=var._fullname)
         var.type = any_type
         var.is_suppressed_import = True
-        self.add_symbol(name, var, context, module_public=module_public)
+        self.add_symbol(
+            name, var, context, module_public=module_public, module_hidden=module_hidden
+        )
 
     #
     # Other helpers


Test patch:
diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test
--- a/test-data/unit/check-modules.test
+++ b/test-data/unit/check-modules.test
@@ -2812,3 +2812,16 @@ CustomDict = TypedDict(
 )
 [typing fixtures/typing-full.pyi]
 [builtins fixtures/tuple.pyi]
+
+[case testNoReExportFromMissingStubs]
+from stub import a  # E: Module 'stub' has no attribute 'a'
+from stub import b
+from stub import c  # E: Module 'stub' has no attribute 'c'
+from stub import d  # E: Module 'stub' has no attribute 'd'
+
+[file stub.pyi]
+from mystery import a, b as b, c as d
+
+[out]
+tmp/stub.pyi:1: error: Cannot find implementation or library stub for module named 'mystery'
+tmp/stub.pyi:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports