Skip to content

Make --no-implicit-reexport friendlier #13965

Description

@hauntsaninja

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.

Details
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

Activity

  1. erictraut commented on Oct 30, 2022

    @erictraut

    This is the same thought process we went through in pyright. In earlier versions, all implicitly re-exported symbols from a "py.typed" library evaluated to Unknown (which is what pyright calls an implicit Any). Pyright users and library authors complained about this behavior, so we modified it to work the way that you're proposing above. The imported type is now resolved to its hidden type, but an error is emitted indicating that it is a private symbol.

  2. JelleZijlstra commented on Oct 30, 2022

    @JelleZijlstra
    Member

    #13933 is a good example of the perils of the current behavior. I agree with your suggestion.

  3. emirkmo commented on Nov 16, 2022

    @emirkmo

    Out of curiosity, is it a recent behavior that --implicit-reexport does not work on from .<file> import <attr>. It says not explictly exported even though I would low to ignore that.

  4. hauntsaninja commented on Nov 16, 2022

    @hauntsaninja
    CollaboratorAuthor

    @emirkmo please file a separate issue with details about what you're doing

  5. added a commit that references this issue on Sep 16, 2023
    64157f1
  6. added a commit that references this issue on Sep 19, 2023
    1dcff0d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions