Closed
Description
Currently, users have to pay double jeopardy if they rely on an implicit reexport. First, they get an error "module does not explicitly export attribute [attr-defined]", but then the type of the thing also becomes Any.
I think it would be friendlier to have mypy just use the type of the hidden thing. This is useful in cases where you can't change the module you're importing from. In some cases, you can have a per-module implicit_reexport config (although I'd argue most users would struggle to find that solution), but in some "consenting adults" situations this isn't quite what you want.
Here's what the patch could look like for the semanal portion of this (edit: merged!). We'd also need to modify attribute access logic.
diff --git a/mypy/semanal.py b/mypy/semanal.py
index b37c9b2a5..06d648a16 100644
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2242,10 +2242,20 @@ class SemanticAnalyzer(
)
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(
@@ -2333,6 +2343,7 @@ class SemanticAnalyzer(
module_public: bool,
module_hidden: bool,
context: Node,
+ add_unknown_imported_symbol: bool = True,
) -> None:
# Missing attribute.
if self.is_incomplete_namespace(import_id):
@@ -2357,13 +2368,14 @@ class SemanticAnalyzer(
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.
diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test
index 5a075dd6e..9beffd2f9 100644
--- a/test-data/unit/check-flags.test
+++ b/test-data/unit/check-flags.test
@@ -1606,14 +1606,18 @@ 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
+reveal_type(other_module_2.a) # E: "object" does not explicitly export attribute "a" [attr-defined] \
+ # N: Revealed type is "builtins.int"
+
[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