15
15
import warnings
16
16
from functools import singledispatch
17
17
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
19
19
20
20
from typing_extensions import Type
21
21
@@ -194,6 +194,42 @@ def verify(
194
194
yield Error (object_path , "is an unknown mypy node" , stub , runtime )
195
195
196
196
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
+
197
233
@verify .register (nodes .MypyFile )
198
234
def verify_mypyfile (
199
235
stub : nodes .MypyFile , runtime : MaybeMissing [types .ModuleType ], object_path : List [str ]
@@ -205,6 +241,14 @@ def verify_mypyfile(
205
241
yield Error (object_path , "is not a module" , stub , runtime )
206
242
return
207
243
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
+
208
252
# Check things in the stub
209
253
to_check = set (
210
254
m
@@ -223,8 +267,8 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
223
267
return not isinstance (obj , types .ModuleType )
224
268
225
269
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
228
272
else [
229
273
m
230
274
for m in dir (runtime )
0 commit comments