Repository: python/mypy

Issue:
`from module import submodule` implicit reexport is allowed
**Bug Report**
`mypy --no-implicit-reexport` doesn't catch reexports written in the form of `from module import submodule`.

**To Reproduce**

```python
# pkg/lib.py

# implicit re-exports
import requests
import requests as demands
import requests.status_codes as requests_status_codes
from requests import Request

# implicit submodule re-export, but mypy doesn't care
from requests import status_codes
```

```python
# pkg/shell.py

from pkg.lib import requests, demands, requests_status_codes, Request, status_codes
```

**Expected Behavior**
I would expect each of those reexports to trigger an error.

**Actual Behavior**
`from requests import status_codes` is ignored.

```bash
❯ mypy --no-implicit-reexport pkg
pkg/shell.py:1: error: Module "pkg.lib" does not explicitly export attribute "requests"; implicit reexport disabled
pkg/shell.py:1: error: Module "pkg.lib" does not explicitly export attribute "demands"; implicit reexport disabled
pkg/shell.py:1: error: Module "pkg.lib" does not explicitly export attribute "requests_status_codes"; implicit reexport disabled
pkg/shell.py:1: error: Module "pkg.lib" does not explicitly export attribute "Request"; implicit reexport disabled
Found 4 errors in 1 file (checked 3 source files)
```

**Your Environment**

- Mypy version used: 0.942
- Mypy command-line flags: `--no-implicit-reexports`
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: 3.9.10
- Operating system and version: Arch Linux latest



Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1894,10 +1894,14 @@ def process_imported_symbol(self,
                                 fullname: str,
                                 module_public: bool,
                                 context: ImportBase) -> None:
-        module_hidden = not module_public and not (
-            # `from package import module` should work regardless of whether package
-            # re-exports module
-            isinstance(node.node, MypyFile) and fullname in self.modules
+        module_hidden = not module_public and (
+            # `from package import submodule` should work regardless of whether package
+            # re-exports submodule, so we shouldn't hide it
+            not isinstance(node.node, MypyFile)
+            or fullname not in self.modules
+            # but given `from somewhere import random_unrelated_module` we should hide
+            # random_unrelated_module
+            or not fullname.startswith(self.cur_mod_id + ".")
         )
 
         if isinstance(node.node, PlaceholderNode):


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
@@ -1929,6 +1929,28 @@ from package import mod as mod
 from package import mod as internal_detail
 [builtins fixtures/module.pyi]
 
+[case testNoReExportUnrelatedModule]
+from mod2 import unrelated  # E: Module "mod2" has no attribute "unrelated"
+
+[file mod1/__init__.pyi]
+[file mod1/unrelated.pyi]
+x: int
+
+[file mod2.pyi]
+from mod1 import unrelated
+[builtins fixtures/module.pyi]
+
+[case testNoReExportUnrelatedSiblingPrefix]
+from pkg.unrel import unrelated  # E: Module "pkg.unrel" has no attribute "unrelated"
+
+[file pkg/__init__.pyi]
+[file pkg/unrelated.pyi]
+x: int
+
+[file pkg/unrel.pyi]
+from pkg import unrelated
+[builtins fixtures/module.pyi]
+
 [case testNoReExportChildStubs]
 import mod
 from mod import C, D  # E: Module "mod" has no attribute "C"