Skip to content

Commit 859df5a

Browse files
committed
Reduce duplicate errors when __all__ is missing from the stub
1 parent 2b41db4 commit 859df5a

File tree

2 files changed

+20
-10
lines changed

2 files changed

+20
-10
lines changed

mypy/stubtest.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def _verify_exported_names(
217217
(
218218
"module: names exported from the stub "
219219
"do not correspond to the names exported at runtime. "
220-
"(Note: This may be due to a missing or inaccurate "
220+
"(Note: This is probably due to an inaccurate "
221221
"`__all__` in the stub.)"
222222
),
223223
# pass in MISSING instead of the stub and runtime objects,
@@ -245,7 +245,11 @@ def verify_mypyfile(
245245

246246
if hasattr(runtime, "__all__"):
247247
runtime_all_as_set = set(runtime.__all__)
248-
yield from _verify_exported_names(object_path, stub, runtime_all_as_set)
248+
if "__all__" in stub.names:
249+
# Only verify the contents of the stub's __all__
250+
# if the stub actually defines __all__
251+
# Otherwise we end up with duplicate errors when __all__ is missing
252+
yield from _verify_exported_names(object_path, stub, runtime_all_as_set)
249253
else:
250254
runtime_all_as_set = None
251255

@@ -267,16 +271,16 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
267271
return not isinstance(obj, types.ModuleType)
268272

269273
runtime_public_contents = (
270-
["__all__", *runtime_all_as_set]
274+
runtime_all_as_set | {"__all__"}
271275
if runtime_all_as_set is not None
272-
else [
276+
else {
273277
m
274278
for m in dir(runtime)
275279
if not is_probably_private(m)
276280
# Ensure that the object's module is `runtime`, since in the absence of __all__ we
277281
# don't have a good way to detect re-exports at runtime.
278282
and _belongs_to_runtime(runtime, m)
279-
]
283+
}
280284
)
281285
# Check all things declared in module's __all__, falling back to our best guess
282286
to_check.update(runtime_public_contents)

mypy/test/teststubtest.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,10 @@ def f(): return 3
692692
error=None,
693693
)
694694

695+
@collect_cases
696+
def test_missing_all(self) -> Iterator[Case]:
697+
yield Case(stub="", runtime="__all__ = []", error="__all__")
698+
695699
@collect_cases
696700
def test_missing(self) -> Iterator[Case]:
697701
yield Case(stub="x = 5", runtime="", error="x")
@@ -708,12 +712,14 @@ def h(x: str): ...
708712
runtime="",
709713
error="h",
710714
)
711-
# __all__ present at runtime, but not in stub -> error
712-
yield Case(stub="", runtime="__all__ = []", error="__all__")
713715
# If runtime has __all__ but stub does not,
714-
# we should raise an error with the module name itself
715-
# if there are any names defined in the stub that are not in the runtime __all__
716-
yield Case(stub="_Z = int", runtime="", error="")
716+
# we'll already raise an error for a missing __all__,
717+
# so we shouldn't raise another error for inconsistent exported names
718+
yield Case(stub="_Z = int", runtime="__all__ = []", error=None)
719+
# But we *should* raise an error with the module name itself,
720+
# if the stub *does* define __all__,
721+
# but the stub's __all__ is inconsistent with the runtime's __all__
722+
yield Case(stub="__all__ = ['foo']", runtime="", error="")
717723
yield Case(stub="", runtime="__all__ += ['y']\ny = 5", error="y")
718724
yield Case(stub="", runtime="__all__ += ['g']\ndef g(): pass", error="g")
719725
# Here we should only check that runtime has B, since the stub explicitly re-exports it

0 commit comments

Comments
 (0)