From 6bb30aabca1160e5e7136a55bb86b9d55ee62e3c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 29 Dec 2025 23:52:17 +0000 Subject: [PATCH] More robust fix for re-export of __all__ --- mypy/semanal.py | 11 ++++++++-- test-data/unit/check-incremental.test | 29 +++++++++++++++++++++++++++ test-data/unit/check-modules.test | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index ca24c33ef4ac..6c4697d64b00 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -494,6 +494,8 @@ def __init__( self.incomplete_namespaces = incomplete_namespaces self.all_exports: list[str] = [] # Map from module id to list of explicitly exported names (i.e. names in __all__). + # This is used by stubgen/stubtest, DO NOT use for any other purposes as it is + # not populated on incremental runs (nor in parallel mode). self.export_map: dict[str, list[str]] = {} self.plugin = plugin # If True, process function definitions. If False, don't. This is used @@ -2967,8 +2969,13 @@ def visit_import_from(self, imp: ImportFrom) -> None: # precedence, but doesn't seem to be important in most use cases. node = SymbolTableNode(GDEF, self.modules[fullname]) else: - if id == as_id == "__all__" and module_id in self.export_map: - self.all_exports[:] = self.export_map[module_id] + if id == as_id == "__all__": + # For modules with __all__ public status of symbols is determined uniquely + # by contents of __all__, so we can recover the latter here, and avoid + # serializing this (redundant) information in MypyFile. + self.all_exports[:] = [ + name for name, sym in module.names.items() if sym.module_public + ] node = module.names.get(id) missing_submodule = False diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 46cf828ef52c..b30357e8d8f3 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7635,3 +7635,32 @@ def wrong() -> int: main:2: error: Missing return statement [out2] {"file": "main", "line": 2, "column": 0, "message": "Missing return statement", "hint": null, "code": "return", "severity": "error"} + +[case testReExportAllInStubIncremental] +from m1 import C +from m1 import D +C() +C(1) +[file m1.pyi] +from m2 import * +[file m2.pyi] +from m3 import * +from m3 import __all__ as __all__ +class D: pass +[file m2.pyi.2] +from m3 import * +from m3 import __all__ as __all__ +class D: + x = 1 +[file m3.pyi] +from m4 import C as C +__all__ = ['C'] +[file m4.pyi] +class C: pass +[builtins fixtures/list.pyi] +[out] +main:2: error: Module "m1" has no attribute "D" +main:4: error: Too many arguments for "C" +[out2] +main:2: error: Module "m1" has no attribute "D" +main:4: error: Too many arguments for "C" diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index fd81765f5624..5ec786c7f08a 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2960,7 +2960,7 @@ class Some: name = __name__ reveal_type(Some.name) # N: Revealed type is "builtins.str" -[case testReExportAllInStub_no_parallel] +[case testReExportAllInStub] from m1 import C from m1 import D # E: Module "m1" has no attribute "D" C()