Skip to content

Commit 2b41db4

Browse files
committed
Rebase
1 parent 68b208d commit 2b41db4

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

mypy/stubtest.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import warnings
1616
from functools import singledispatch
1717
from pathlib import Path
18-
from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, TypeVar, Union, cast
18+
from typing import Any, Dict, Generic, Iterator, List, Optional, Set, Tuple, TypeVar, Union, cast
1919

2020
from typing_extensions import Type
2121

@@ -194,6 +194,42 @@ def verify(
194194
yield Error(object_path, "is an unknown mypy node", stub, runtime)
195195

196196

197+
def _verify_exported_names(
198+
object_path: List[str], stub: nodes.MypyFile, runtime_all_as_set: Set[str]
199+
) -> Iterator[Error]:
200+
public_names_in_stub = {
201+
m
202+
for m, o in stub.names.items()
203+
if o.module_public and m not in IGNORED_MODULE_DUNDERS
204+
}
205+
if not runtime_all_as_set.symmetric_difference(public_names_in_stub):
206+
return
207+
sorted_runtime_names = sorted(
208+
runtime_all_as_set,
209+
key=lambda name: ((name not in public_names_in_stub), name)
210+
)
211+
sorted_names_in_stub = sorted(
212+
public_names_in_stub,
213+
key=lambda name: ((name not in runtime_all_as_set), name)
214+
)
215+
yield Error(
216+
object_path,
217+
(
218+
"module: names exported from the stub "
219+
"do not correspond to the names exported at runtime. "
220+
"(Note: This may be due to a missing or inaccurate "
221+
"`__all__` in the stub.)"
222+
),
223+
# pass in MISSING instead of the stub and runtime objects,
224+
# as the line numbers aren't very relevant here,
225+
# and it makes for a prettier error message.
226+
MISSING,
227+
MISSING,
228+
stub_desc=f"Names exported are: {sorted_names_in_stub}",
229+
runtime_desc=f"Names exported are: {sorted_runtime_names}"
230+
)
231+
232+
197233
@verify.register(nodes.MypyFile)
198234
def verify_mypyfile(
199235
stub: nodes.MypyFile, runtime: MaybeMissing[types.ModuleType], object_path: List[str]
@@ -205,6 +241,14 @@ def verify_mypyfile(
205241
yield Error(object_path, "is not a module", stub, runtime)
206242
return
207243

244+
runtime_all_as_set: Optional[Set[str]]
245+
246+
if hasattr(runtime, "__all__"):
247+
runtime_all_as_set = set(runtime.__all__)
248+
yield from _verify_exported_names(object_path, stub, runtime_all_as_set)
249+
else:
250+
runtime_all_as_set = None
251+
208252
# Check things in the stub
209253
to_check = set(
210254
m
@@ -223,8 +267,8 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
223267
return not isinstance(obj, types.ModuleType)
224268

225269
runtime_public_contents = (
226-
runtime.__all__
227-
if hasattr(runtime, "__all__")
270+
["__all__", *runtime_all_as_set]
271+
if runtime_all_as_set is not None
228272
else [
229273
m
230274
for m in dir(runtime)

mypy/test/teststubtest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,12 @@ def h(x: str): ...
708708
runtime="",
709709
error="h",
710710
)
711-
yield Case(stub="", runtime="__all__ = []", error=None) # dummy case
711+
# __all__ present at runtime, but not in stub -> error
712+
yield Case(stub="", runtime="__all__ = []", error="__all__")
713+
# 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="")
712717
yield Case(stub="", runtime="__all__ += ['y']\ny = 5", error="y")
713718
yield Case(stub="", runtime="__all__ += ['g']\ndef g(): pass", error="g")
714719
# Here we should only check that runtime has B, since the stub explicitly re-exports it

0 commit comments

Comments
 (0)