Repository: python/mypy

Issue:
`no-implicit-reexport` resolves implicit re-exports as `Any`
```py
# foo.py
class Foo:
    ...
```
```py
# bar.py
from foo import Foo
```
```py
# baz.py
from bar import Foo # Module "bar" does not explicitly export attribute "Foo"; implicit reexport disabled  [attr-defined]

reveal_type(Foo) # Revealed type is "Any"
```

mypy should still resolve the import like it does when `no-implicit-reexport` is disabled.

in my case, i'm trying to use a library that imports its own classes using implicit re-exports, which results in pretty much everything being treated as `Any` in my project


https://github.com/python/mypy/issues/12749#tasklist-block-f9d696db-4078-45d6-ae8a-e466f1a3d798



Discussion:
As a workaround you could apply an override to the mypy config:
```toml
[[tool.mypy.overrides]]
module = "thepackage.*"
implicit_reexport = true
```

Just to be clear - are you expecting mypy to give the error (`... does not explicitly export attribute ...`) but then still treat it as being exported?

Also, in your examples the files are all .py files - are there stubs for the external libraries? If so, can you change the stubs to have explicit re-exports?
> Just to be clear - are you expecting mypy to give the error (`... does not explicitly export attribute ...`) but then still treat it as being exported?

yes

> Also, in your examples the files are all .py files - are there stubs for the external libraries? If so, can you change the stubs to have explicit re-exports?

the library i'm using has types defined in its `.py` files, but i'm pretty sure it would behave the same if they were stubs. i also can't really fix them without making stubs for the entire thing which would be a waste of time because they're already typed

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2303,10 +2303,20 @@ def visit_import_from(self, imp: ImportFrom) -> None:
                     )
                     continue
 
-            if node and not node.module_hidden:
+            if node:
                 self.process_imported_symbol(
                     node, module_id, id, imported_id, fullname, module_public, context=imp
                 )
+                if node.module_hidden:
+                    self.report_missing_module_attribute(
+                        module_id,
+                        id,
+                        imported_id,
+                        module_public=module_public,
+                        module_hidden=not module_public,
+                        context=imp,
+                        add_unknown_imported_symbol=False,
+                    )
             elif module and not missing_submodule:
                 # Target module exists but the imported name is missing or hidden.
                 self.report_missing_module_attribute(
@@ -2394,6 +2404,7 @@ def report_missing_module_attribute(
         module_public: bool,
         module_hidden: bool,
         context: Node,
+        add_unknown_imported_symbol: bool = True,
     ) -> None:
         # Missing attribute.
         if self.is_incomplete_namespace(import_id):
@@ -2418,13 +2429,14 @@ def report_missing_module_attribute(
                     suggestion = f"; maybe {pretty_seq(matches, 'or')}?"
                     message += f"{suggestion}"
         self.fail(message, context, code=codes.ATTR_DEFINED)
-        self.add_unknown_imported_symbol(
-            imported_id,
-            context,
-            target_name=None,
-            module_public=module_public,
-            module_hidden=not module_public,
-        )
+        if add_unknown_imported_symbol:
+            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.


Test patch:
diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test
--- a/test-data/unit/check-flags.test
+++ b/test-data/unit/check-flags.test
@@ -1606,14 +1606,19 @@ strict_equality = false
 
 
 [case testNoImplicitReexport]
-# flags: --no-implicit-reexport
-from other_module_2 import a
+# flags: --no-implicit-reexport --show-error-codes
+from other_module_2 import a  # E: Module "other_module_2" does not explicitly export attribute "a"  [attr-defined]
+reveal_type(a)  # N: Revealed type is "builtins.int"
+
+import other_module_2
+# TODO: this should also reveal builtins.int, see #13965
+reveal_type(other_module_2.a)  # E: "object" does not explicitly export attribute "a"  [attr-defined] \
+                               # N: Revealed type is "Any"
+
 [file other_module_1.py]
 a = 5
 [file other_module_2.py]
 from other_module_1 import a
-[out]
-main:2: error: Module "other_module_2" does not explicitly export attribute "a"
 
 [case testNoImplicitReexportRespectsAll]
 # flags: --no-implicit-reexport
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
@@ -1794,7 +1794,7 @@ from stub import C
 c = C()
 reveal_type(c.x)  # N: Revealed type is "builtins.int"
 it: Iterable[int]
-reveal_type(it)  # N: Revealed type is "Any"
+reveal_type(it)  # N: Revealed type is "typing.Iterable[builtins.int]"
 
 [file stub.pyi]
 from typing import Iterable