|
15 | 15 | import os
|
16 | 16 | import pkgutil
|
17 | 17 | import re
|
| 18 | +import symtable |
18 | 19 | import sys
|
19 | 20 | import traceback
|
20 | 21 | import types
|
@@ -283,6 +284,36 @@ def _verify_exported_names(
|
283 | 284 | )
|
284 | 285 |
|
285 | 286 |
|
| 287 | +def _get_imported_symbol_names(runtime: types.ModuleType) -> frozenset[str] | None: |
| 288 | + """Retrieve the names in the global namespace which are known to be imported. |
| 289 | +
|
| 290 | + 1). Use inspect to retrieve the source code of the module |
| 291 | + 2). Use symtable to parse the source and retrieve names that are known to be imported |
| 292 | + from other modules. |
| 293 | +
|
| 294 | + If either of the above steps fails, return `None`. |
| 295 | +
|
| 296 | + Note that if a set of names is returned, |
| 297 | + it won't include names imported via `from foo import *` imports. |
| 298 | + """ |
| 299 | + try: |
| 300 | + source = inspect.getsource(runtime) |
| 301 | + except (OSError, TypeError, SyntaxError): |
| 302 | + return None |
| 303 | + |
| 304 | + if not source.strip(): |
| 305 | + # The source code for the module was an empty file, |
| 306 | + # no point in parsing it with symtable |
| 307 | + return frozenset() |
| 308 | + |
| 309 | + try: |
| 310 | + module_symtable = symtable.symtable(source, runtime.__name__, "exec") |
| 311 | + except SyntaxError: |
| 312 | + return None |
| 313 | + |
| 314 | + return frozenset(sym.get_name() for sym in module_symtable.get_symbols() if sym.is_imported()) |
| 315 | + |
| 316 | + |
286 | 317 | @verify.register(nodes.MypyFile)
|
287 | 318 | def verify_mypyfile(
|
288 | 319 | stub: nodes.MypyFile, runtime: MaybeMissing[types.ModuleType], object_path: list[str]
|
@@ -312,15 +343,22 @@ def verify_mypyfile(
|
312 | 343 | if not o.module_hidden and (not is_probably_private(m) or hasattr(runtime, m))
|
313 | 344 | }
|
314 | 345 |
|
| 346 | + imported_symbols = _get_imported_symbol_names(runtime) |
| 347 | + |
315 | 348 | def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
|
316 | 349 | obj = getattr(r, attr)
|
317 |
| - try: |
318 |
| - obj_mod = getattr(obj, "__module__", None) |
319 |
| - except Exception: |
| 350 | + if isinstance(obj, types.ModuleType): |
320 | 351 | return False
|
321 |
| - if obj_mod is not None: |
322 |
| - return bool(obj_mod == r.__name__) |
323 |
| - return not isinstance(obj, types.ModuleType) |
| 352 | + if callable(obj): |
| 353 | + try: |
| 354 | + obj_mod = getattr(obj, "__module__", None) |
| 355 | + except Exception: |
| 356 | + return False |
| 357 | + if obj_mod is not None: |
| 358 | + return bool(obj_mod == r.__name__) |
| 359 | + if imported_symbols is not None: |
| 360 | + return attr not in imported_symbols |
| 361 | + return True |
324 | 362 |
|
325 | 363 | runtime_public_contents = (
|
326 | 364 | runtime_all_as_set
|
|
0 commit comments