stubtest.py 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882
  1. """Tests for stubs.
  2. Verify that various things in stubs are consistent with how things behave at runtime.
  3. """
  4. from __future__ import annotations
  5. import argparse
  6. import collections.abc
  7. import copy
  8. import enum
  9. import importlib
  10. import inspect
  11. import os
  12. import pkgutil
  13. import re
  14. import symtable
  15. import sys
  16. import traceback
  17. import types
  18. import typing
  19. import typing_extensions
  20. import warnings
  21. from contextlib import redirect_stderr, redirect_stdout
  22. from functools import singledispatch
  23. from pathlib import Path
  24. from typing import Any, Generic, Iterator, TypeVar, Union
  25. from typing_extensions import get_origin, is_typeddict
  26. import mypy.build
  27. import mypy.modulefinder
  28. import mypy.nodes
  29. import mypy.state
  30. import mypy.types
  31. import mypy.version
  32. from mypy import nodes
  33. from mypy.config_parser import parse_config_file
  34. from mypy.evalexpr import UNKNOWN, evaluate_expression
  35. from mypy.options import Options
  36. from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder, plural_s
  37. class Missing:
  38. """Marker object for things that are missing (from a stub or the runtime)."""
  39. def __repr__(self) -> str:
  40. return "MISSING"
  41. MISSING: typing_extensions.Final = Missing()
  42. T = TypeVar("T")
  43. MaybeMissing: typing_extensions.TypeAlias = Union[T, Missing]
  44. _formatter: typing_extensions.Final = FancyFormatter(sys.stdout, sys.stderr, False)
  45. def _style(message: str, **kwargs: Any) -> str:
  46. """Wrapper around mypy.util for fancy formatting."""
  47. kwargs.setdefault("color", "none")
  48. return _formatter.style(message, **kwargs)
  49. def _truncate(message: str, length: int) -> str:
  50. if len(message) > length:
  51. return message[: length - 3] + "..."
  52. return message
  53. class StubtestFailure(Exception):
  54. pass
  55. class Error:
  56. def __init__(
  57. self,
  58. object_path: list[str],
  59. message: str,
  60. stub_object: MaybeMissing[nodes.Node],
  61. runtime_object: MaybeMissing[Any],
  62. *,
  63. stub_desc: str | None = None,
  64. runtime_desc: str | None = None,
  65. ) -> None:
  66. """Represents an error found by stubtest.
  67. :param object_path: Location of the object with the error,
  68. e.g. ``["module", "Class", "method"]``
  69. :param message: Error message
  70. :param stub_object: The mypy node representing the stub
  71. :param runtime_object: Actual object obtained from the runtime
  72. :param stub_desc: Specialised description for the stub object, should you wish
  73. :param runtime_desc: Specialised description for the runtime object, should you wish
  74. """
  75. self.object_path = object_path
  76. self.object_desc = ".".join(object_path)
  77. self.message = message
  78. self.stub_object = stub_object
  79. self.runtime_object = runtime_object
  80. self.stub_desc = stub_desc or str(getattr(stub_object, "type", stub_object))
  81. self.runtime_desc = runtime_desc or _truncate(repr(runtime_object), 100)
  82. def is_missing_stub(self) -> bool:
  83. """Whether or not the error is for something missing from the stub."""
  84. return isinstance(self.stub_object, Missing)
  85. def is_positional_only_related(self) -> bool:
  86. """Whether or not the error is for something being (or not being) positional-only."""
  87. # TODO: This is hacky, use error codes or something more resilient
  88. return "leading double underscore" in self.message
  89. def get_description(self, concise: bool = False) -> str:
  90. """Returns a description of the error.
  91. :param concise: Whether to return a concise, one-line description
  92. """
  93. if concise:
  94. return _style(self.object_desc, bold=True) + " " + self.message
  95. stub_line = None
  96. stub_file = None
  97. if not isinstance(self.stub_object, Missing):
  98. stub_line = self.stub_object.line
  99. stub_node = get_stub(self.object_path[0])
  100. if stub_node is not None:
  101. stub_file = stub_node.path or None
  102. stub_loc_str = ""
  103. if stub_file:
  104. stub_loc_str += f" in file {Path(stub_file)}"
  105. if stub_line:
  106. stub_loc_str += f"{':' if stub_file else ' at line '}{stub_line}"
  107. runtime_line = None
  108. runtime_file = None
  109. if not isinstance(self.runtime_object, Missing):
  110. try:
  111. runtime_line = inspect.getsourcelines(self.runtime_object)[1]
  112. except (OSError, TypeError, SyntaxError):
  113. pass
  114. try:
  115. runtime_file = inspect.getsourcefile(self.runtime_object)
  116. except TypeError:
  117. pass
  118. runtime_loc_str = ""
  119. if runtime_file:
  120. runtime_loc_str += f" in file {Path(runtime_file)}"
  121. if runtime_line:
  122. runtime_loc_str += f"{':' if runtime_file else ' at line '}{runtime_line}"
  123. output = [
  124. _style("error: ", color="red", bold=True),
  125. _style(self.object_desc, bold=True),
  126. " ",
  127. self.message,
  128. "\n",
  129. "Stub:",
  130. _style(stub_loc_str, dim=True),
  131. "\n",
  132. _style(self.stub_desc + "\n", color="blue", dim=True),
  133. "Runtime:",
  134. _style(runtime_loc_str, dim=True),
  135. "\n",
  136. _style(self.runtime_desc + "\n", color="blue", dim=True),
  137. ]
  138. return "".join(output)
  139. # ====================
  140. # Core logic
  141. # ====================
  142. def silent_import_module(module_name: str) -> types.ModuleType:
  143. with open(os.devnull, "w") as devnull:
  144. with warnings.catch_warnings(), redirect_stdout(devnull), redirect_stderr(devnull):
  145. warnings.simplefilter("ignore")
  146. runtime = importlib.import_module(module_name)
  147. # Also run the equivalent of `from module import *`
  148. # This could have the additional effect of loading not-yet-loaded submodules
  149. # mentioned in __all__
  150. __import__(module_name, fromlist=["*"])
  151. return runtime
  152. def test_module(module_name: str) -> Iterator[Error]:
  153. """Tests a given module's stub against introspecting it at runtime.
  154. Requires the stub to have been built already, accomplished by a call to ``build_stubs``.
  155. :param module_name: The module to test
  156. """
  157. stub = get_stub(module_name)
  158. if stub is None:
  159. if not is_probably_private(module_name.split(".")[-1]):
  160. runtime_desc = repr(sys.modules[module_name]) if module_name in sys.modules else "N/A"
  161. yield Error(
  162. [module_name], "failed to find stubs", MISSING, None, runtime_desc=runtime_desc
  163. )
  164. return
  165. try:
  166. runtime = silent_import_module(module_name)
  167. except KeyboardInterrupt:
  168. raise
  169. except BaseException as e:
  170. yield Error([module_name], f"failed to import, {type(e).__name__}: {e}", stub, MISSING)
  171. return
  172. with warnings.catch_warnings():
  173. warnings.simplefilter("ignore")
  174. try:
  175. yield from verify(stub, runtime, [module_name])
  176. except Exception as e:
  177. bottom_frame = list(traceback.walk_tb(e.__traceback__))[-1][0]
  178. bottom_module = bottom_frame.f_globals.get("__name__", "")
  179. # Pass on any errors originating from stubtest or mypy
  180. # These can occur expectedly, e.g. StubtestFailure
  181. if bottom_module == "__main__" or bottom_module.split(".")[0] == "mypy":
  182. raise
  183. yield Error(
  184. [module_name],
  185. f"encountered unexpected error, {type(e).__name__}: {e}",
  186. stub,
  187. runtime,
  188. stub_desc="N/A",
  189. runtime_desc=(
  190. "This is most likely the fault of something very dynamic in your library. "
  191. "It's also possible this is a bug in stubtest.\nIf in doubt, please "
  192. "open an issue at https://github.com/python/mypy\n\n"
  193. + traceback.format_exc().strip()
  194. ),
  195. )
  196. @singledispatch
  197. def verify(
  198. stub: MaybeMissing[nodes.Node], runtime: MaybeMissing[Any], object_path: list[str]
  199. ) -> Iterator[Error]:
  200. """Entry point for comparing a stub to a runtime object.
  201. We use single dispatch based on the type of ``stub``.
  202. :param stub: The mypy node representing a part of the stub
  203. :param runtime: The runtime object corresponding to ``stub``
  204. """
  205. yield Error(object_path, "is an unknown mypy node", stub, runtime)
  206. def _verify_exported_names(
  207. object_path: list[str], stub: nodes.MypyFile, runtime_all_as_set: set[str]
  208. ) -> Iterator[Error]:
  209. # note that this includes the case the stub simply defines `__all__: list[str]`
  210. assert "__all__" in stub.names
  211. public_names_in_stub = {m for m, o in stub.names.items() if o.module_public}
  212. names_in_stub_not_runtime = sorted(public_names_in_stub - runtime_all_as_set)
  213. names_in_runtime_not_stub = sorted(runtime_all_as_set - public_names_in_stub)
  214. if not (names_in_runtime_not_stub or names_in_stub_not_runtime):
  215. return
  216. yield Error(
  217. object_path + ["__all__"],
  218. (
  219. "names exported from the stub do not correspond to the names exported at runtime. "
  220. "This is probably due to things being missing from the stub or an inaccurate `__all__` in the stub"
  221. ),
  222. # Pass in MISSING instead of the stub and runtime objects, as the line numbers aren't very
  223. # relevant here, and it makes for a prettier error message
  224. # This means this error will be ignored when using `--ignore-missing-stub`, which is
  225. # desirable in at least the `names_in_runtime_not_stub` case
  226. stub_object=MISSING,
  227. runtime_object=MISSING,
  228. stub_desc=(
  229. f"Names exported in the stub but not at runtime: " f"{names_in_stub_not_runtime}"
  230. ),
  231. runtime_desc=(
  232. f"Names exported at runtime but not in the stub: " f"{names_in_runtime_not_stub}"
  233. ),
  234. )
  235. def _get_imported_symbol_names(runtime: types.ModuleType) -> frozenset[str] | None:
  236. """Retrieve the names in the global namespace which are known to be imported.
  237. 1). Use inspect to retrieve the source code of the module
  238. 2). Use symtable to parse the source and retrieve names that are known to be imported
  239. from other modules.
  240. If either of the above steps fails, return `None`.
  241. Note that if a set of names is returned,
  242. it won't include names imported via `from foo import *` imports.
  243. """
  244. try:
  245. source = inspect.getsource(runtime)
  246. except (OSError, TypeError, SyntaxError):
  247. return None
  248. if not source.strip():
  249. # The source code for the module was an empty file,
  250. # no point in parsing it with symtable
  251. return frozenset()
  252. try:
  253. module_symtable = symtable.symtable(source, runtime.__name__, "exec")
  254. except SyntaxError:
  255. return None
  256. return frozenset(sym.get_name() for sym in module_symtable.get_symbols() if sym.is_imported())
  257. @verify.register(nodes.MypyFile)
  258. def verify_mypyfile(
  259. stub: nodes.MypyFile, runtime: MaybeMissing[types.ModuleType], object_path: list[str]
  260. ) -> Iterator[Error]:
  261. if isinstance(runtime, Missing):
  262. yield Error(object_path, "is not present at runtime", stub, runtime)
  263. return
  264. if not isinstance(runtime, types.ModuleType):
  265. yield Error(object_path, "is not a module", stub, runtime)
  266. return
  267. runtime_all_as_set: set[str] | None
  268. if hasattr(runtime, "__all__"):
  269. runtime_all_as_set = set(runtime.__all__)
  270. if "__all__" in stub.names:
  271. # Only verify the contents of the stub's __all__
  272. # if the stub actually defines __all__
  273. yield from _verify_exported_names(object_path, stub, runtime_all_as_set)
  274. else:
  275. runtime_all_as_set = None
  276. # Check things in the stub
  277. to_check = {
  278. m
  279. for m, o in stub.names.items()
  280. if not o.module_hidden and (not is_probably_private(m) or hasattr(runtime, m))
  281. }
  282. imported_symbols = _get_imported_symbol_names(runtime)
  283. def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
  284. """Heuristics to determine whether a name originates from another module."""
  285. obj = getattr(r, attr)
  286. if isinstance(obj, types.ModuleType):
  287. return False
  288. if callable(obj):
  289. # It's highly likely to be a class or a function if it's callable,
  290. # so the __module__ attribute will give a good indication of which module it comes from
  291. try:
  292. obj_mod = obj.__module__
  293. except Exception:
  294. pass
  295. else:
  296. if isinstance(obj_mod, str):
  297. return bool(obj_mod == r.__name__)
  298. if imported_symbols is not None:
  299. return attr not in imported_symbols
  300. return True
  301. runtime_public_contents = (
  302. runtime_all_as_set
  303. if runtime_all_as_set is not None
  304. else {
  305. m
  306. for m in dir(runtime)
  307. if not is_probably_private(m)
  308. # Filter out objects that originate from other modules (best effort). Note that in the
  309. # absence of __all__, we don't have a way to detect explicit / intentional re-exports
  310. # at runtime
  311. and _belongs_to_runtime(runtime, m)
  312. }
  313. )
  314. # Check all things declared in module's __all__, falling back to our best guess
  315. to_check.update(runtime_public_contents)
  316. to_check.difference_update(IGNORED_MODULE_DUNDERS)
  317. for entry in sorted(to_check):
  318. stub_entry = stub.names[entry].node if entry in stub.names else MISSING
  319. if isinstance(stub_entry, nodes.MypyFile):
  320. # Don't recursively check exported modules, since that leads to infinite recursion
  321. continue
  322. assert stub_entry is not None
  323. try:
  324. runtime_entry = getattr(runtime, entry, MISSING)
  325. except Exception:
  326. # Catch all exceptions in case the runtime raises an unexpected exception
  327. # from __getattr__ or similar.
  328. continue
  329. yield from verify(stub_entry, runtime_entry, object_path + [entry])
  330. def _verify_final(
  331. stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str]
  332. ) -> Iterator[Error]:
  333. try:
  334. class SubClass(runtime): # type: ignore[misc]
  335. pass
  336. except TypeError:
  337. # Enum classes are implicitly @final
  338. if not stub.is_final and not issubclass(runtime, enum.Enum):
  339. yield Error(
  340. object_path,
  341. "cannot be subclassed at runtime, but isn't marked with @final in the stub",
  342. stub,
  343. runtime,
  344. stub_desc=repr(stub),
  345. )
  346. except Exception:
  347. # The class probably wants its subclasses to do something special.
  348. # Examples: ctypes.Array, ctypes._SimpleCData
  349. pass
  350. # Runtime class might be annotated with `@final`:
  351. try:
  352. runtime_final = getattr(runtime, "__final__", False)
  353. except Exception:
  354. runtime_final = False
  355. if runtime_final and not stub.is_final:
  356. yield Error(
  357. object_path,
  358. "has `__final__` attribute, but isn't marked with @final in the stub",
  359. stub,
  360. runtime,
  361. stub_desc=repr(stub),
  362. )
  363. def _verify_metaclass(
  364. stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str], *, is_runtime_typeddict: bool
  365. ) -> Iterator[Error]:
  366. # We exclude protocols, because of how complex their implementation is in different versions of
  367. # python. Enums are also hard, as are runtime TypedDicts; ignoring.
  368. # TODO: check that metaclasses are identical?
  369. if not stub.is_protocol and not stub.is_enum and not is_runtime_typeddict:
  370. runtime_metaclass = type(runtime)
  371. if runtime_metaclass is not type and stub.metaclass_type is None:
  372. # This means that runtime has a custom metaclass, but a stub does not.
  373. yield Error(
  374. object_path,
  375. "is inconsistent, metaclass differs",
  376. stub,
  377. runtime,
  378. stub_desc="N/A",
  379. runtime_desc=f"{runtime_metaclass}",
  380. )
  381. elif (
  382. runtime_metaclass is type
  383. and stub.metaclass_type is not None
  384. # We ignore extra `ABCMeta` metaclass on stubs, this might be typing hack.
  385. # We also ignore `builtins.type` metaclass as an implementation detail in mypy.
  386. and not mypy.types.is_named_instance(
  387. stub.metaclass_type, ("abc.ABCMeta", "builtins.type")
  388. )
  389. ):
  390. # This means that our stub has a metaclass that is not present at runtime.
  391. yield Error(
  392. object_path,
  393. "metaclass mismatch",
  394. stub,
  395. runtime,
  396. stub_desc=f"{stub.metaclass_type.type.fullname}",
  397. runtime_desc="N/A",
  398. )
  399. @verify.register(nodes.TypeInfo)
  400. def verify_typeinfo(
  401. stub: nodes.TypeInfo, runtime: MaybeMissing[type[Any]], object_path: list[str]
  402. ) -> Iterator[Error]:
  403. if isinstance(runtime, Missing):
  404. yield Error(object_path, "is not present at runtime", stub, runtime, stub_desc=repr(stub))
  405. return
  406. if not isinstance(runtime, type):
  407. yield Error(object_path, "is not a type", stub, runtime, stub_desc=repr(stub))
  408. return
  409. yield from _verify_final(stub, runtime, object_path)
  410. is_runtime_typeddict = stub.typeddict_type is not None and is_typeddict(runtime)
  411. yield from _verify_metaclass(
  412. stub, runtime, object_path, is_runtime_typeddict=is_runtime_typeddict
  413. )
  414. # Check everything already defined on the stub class itself (i.e. not inherited)
  415. to_check = set(stub.names)
  416. # Check all public things on the runtime class
  417. to_check.update(
  418. m for m in vars(runtime) if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS
  419. )
  420. # Special-case the __init__ method for Protocols and the __new__ method for TypedDicts
  421. #
  422. # TODO: On Python <3.11, __init__ methods on Protocol classes
  423. # are silently discarded and replaced.
  424. # However, this is not the case on Python 3.11+.
  425. # Ideally, we'd figure out a good way of validating Protocol __init__ methods on 3.11+.
  426. if stub.is_protocol:
  427. to_check.discard("__init__")
  428. if is_runtime_typeddict:
  429. to_check.discard("__new__")
  430. for entry in sorted(to_check):
  431. mangled_entry = entry
  432. if entry.startswith("__") and not entry.endswith("__"):
  433. mangled_entry = f"_{stub.name.lstrip('_')}{entry}"
  434. stub_to_verify = next((t.names[entry].node for t in stub.mro if entry in t.names), MISSING)
  435. assert stub_to_verify is not None
  436. try:
  437. try:
  438. runtime_attr = getattr(runtime, mangled_entry)
  439. except AttributeError:
  440. runtime_attr = inspect.getattr_static(runtime, mangled_entry, MISSING)
  441. except Exception:
  442. # Catch all exceptions in case the runtime raises an unexpected exception
  443. # from __getattr__ or similar.
  444. continue
  445. # Do not error for an object missing from the stub
  446. # If the runtime object is a types.WrapperDescriptorType object
  447. # and has a non-special dunder name.
  448. # The vast majority of these are false positives.
  449. if not (
  450. isinstance(stub_to_verify, Missing)
  451. and isinstance(runtime_attr, types.WrapperDescriptorType)
  452. and is_dunder(mangled_entry, exclude_special=True)
  453. ):
  454. yield from verify(stub_to_verify, runtime_attr, object_path + [entry])
  455. def _static_lookup_runtime(object_path: list[str]) -> MaybeMissing[Any]:
  456. static_runtime = importlib.import_module(object_path[0])
  457. for entry in object_path[1:]:
  458. try:
  459. static_runtime = inspect.getattr_static(static_runtime, entry)
  460. except AttributeError:
  461. # This can happen with mangled names, ignore for now.
  462. # TODO: pass more information about ancestors of nodes/objects to verify, so we don't
  463. # have to do this hacky lookup. Would be useful in several places.
  464. return MISSING
  465. return static_runtime
  466. def _verify_static_class_methods(
  467. stub: nodes.FuncBase, runtime: Any, static_runtime: MaybeMissing[Any], object_path: list[str]
  468. ) -> Iterator[str]:
  469. if stub.name in ("__new__", "__init_subclass__", "__class_getitem__"):
  470. # Special cased by Python, so don't bother checking
  471. return
  472. if inspect.isbuiltin(runtime):
  473. # The isinstance checks don't work reliably for builtins, e.g. datetime.datetime.now, so do
  474. # something a little hacky that seems to work well
  475. probably_class_method = isinstance(getattr(runtime, "__self__", None), type)
  476. if probably_class_method and not stub.is_class:
  477. yield "runtime is a classmethod but stub is not"
  478. if not probably_class_method and stub.is_class:
  479. yield "stub is a classmethod but runtime is not"
  480. return
  481. if static_runtime is MISSING:
  482. return
  483. if isinstance(static_runtime, classmethod) and not stub.is_class:
  484. yield "runtime is a classmethod but stub is not"
  485. if not isinstance(static_runtime, classmethod) and stub.is_class:
  486. yield "stub is a classmethod but runtime is not"
  487. if isinstance(static_runtime, staticmethod) and not stub.is_static:
  488. yield "runtime is a staticmethod but stub is not"
  489. if not isinstance(static_runtime, staticmethod) and stub.is_static:
  490. yield "stub is a staticmethod but runtime is not"
  491. def _verify_arg_name(
  492. stub_arg: nodes.Argument, runtime_arg: inspect.Parameter, function_name: str
  493. ) -> Iterator[str]:
  494. """Checks whether argument names match."""
  495. # Ignore exact names for most dunder methods
  496. if is_dunder(function_name, exclude_special=True):
  497. return
  498. def strip_prefix(s: str, prefix: str) -> str:
  499. return s[len(prefix) :] if s.startswith(prefix) else s
  500. if strip_prefix(stub_arg.variable.name, "__") == runtime_arg.name:
  501. return
  502. def names_approx_match(a: str, b: str) -> bool:
  503. a = a.strip("_")
  504. b = b.strip("_")
  505. return a.startswith(b) or b.startswith(a) or len(a) == 1 or len(b) == 1
  506. # Be more permissive about names matching for positional-only arguments
  507. if runtime_arg.kind == inspect.Parameter.POSITIONAL_ONLY and names_approx_match(
  508. stub_arg.variable.name, runtime_arg.name
  509. ):
  510. return
  511. # This comes up with namedtuples, so ignore
  512. if stub_arg.variable.name == "_self":
  513. return
  514. yield (
  515. f'stub argument "{stub_arg.variable.name}" '
  516. f'differs from runtime argument "{runtime_arg.name}"'
  517. )
  518. def _verify_arg_default_value(
  519. stub_arg: nodes.Argument, runtime_arg: inspect.Parameter
  520. ) -> Iterator[str]:
  521. """Checks whether argument default values are compatible."""
  522. if runtime_arg.default != inspect.Parameter.empty:
  523. if stub_arg.kind.is_required():
  524. yield (
  525. f'runtime argument "{runtime_arg.name}" '
  526. "has a default value but stub argument does not"
  527. )
  528. else:
  529. runtime_type = get_mypy_type_of_runtime_value(runtime_arg.default)
  530. # Fallback to the type annotation type if var type is missing. The type annotation
  531. # is an UnboundType, but I don't know enough to know what the pros and cons here are.
  532. # UnboundTypes have ugly question marks following them, so default to var type.
  533. # Note we do this same fallback when constructing signatures in from_overloadedfuncdef
  534. stub_type = stub_arg.variable.type or stub_arg.type_annotation
  535. if isinstance(stub_type, mypy.types.TypeVarType):
  536. stub_type = stub_type.upper_bound
  537. if (
  538. runtime_type is not None
  539. and stub_type is not None
  540. # Avoid false positives for marker objects
  541. and type(runtime_arg.default) != object
  542. # And ellipsis
  543. and runtime_arg.default is not ...
  544. and not is_subtype_helper(runtime_type, stub_type)
  545. ):
  546. yield (
  547. f'runtime argument "{runtime_arg.name}" '
  548. f"has a default value of type {runtime_type}, "
  549. f"which is incompatible with stub argument type {stub_type}"
  550. )
  551. if stub_arg.initializer is not None:
  552. stub_default = evaluate_expression(stub_arg.initializer)
  553. if (
  554. stub_default is not UNKNOWN
  555. and stub_default is not ...
  556. and (
  557. stub_default != runtime_arg.default
  558. # We want the types to match exactly, e.g. in case the stub has
  559. # True and the runtime has 1 (or vice versa).
  560. or type(stub_default) is not type(runtime_arg.default) # noqa: E721
  561. )
  562. ):
  563. yield (
  564. f'runtime argument "{runtime_arg.name}" '
  565. f"has a default value of {runtime_arg.default!r}, "
  566. f"which is different from stub argument default {stub_default!r}"
  567. )
  568. else:
  569. if stub_arg.kind.is_optional():
  570. yield (
  571. f'stub argument "{stub_arg.variable.name}" has a default value '
  572. f"but runtime argument does not"
  573. )
  574. def maybe_strip_cls(name: str, args: list[nodes.Argument]) -> list[nodes.Argument]:
  575. if args and name in ("__init_subclass__", "__class_getitem__"):
  576. # These are implicitly classmethods. If the stub chooses not to have @classmethod, we
  577. # should remove the cls argument
  578. if args[0].variable.name == "cls":
  579. return args[1:]
  580. return args
  581. class Signature(Generic[T]):
  582. def __init__(self) -> None:
  583. self.pos: list[T] = []
  584. self.kwonly: dict[str, T] = {}
  585. self.varpos: T | None = None
  586. self.varkw: T | None = None
  587. def __str__(self) -> str:
  588. def get_name(arg: Any) -> str:
  589. if isinstance(arg, inspect.Parameter):
  590. return arg.name
  591. if isinstance(arg, nodes.Argument):
  592. return arg.variable.name
  593. raise AssertionError
  594. def get_type(arg: Any) -> str | None:
  595. if isinstance(arg, inspect.Parameter):
  596. return None
  597. if isinstance(arg, nodes.Argument):
  598. return str(arg.variable.type or arg.type_annotation)
  599. raise AssertionError
  600. def has_default(arg: Any) -> bool:
  601. if isinstance(arg, inspect.Parameter):
  602. return bool(arg.default != inspect.Parameter.empty)
  603. if isinstance(arg, nodes.Argument):
  604. return arg.kind.is_optional()
  605. raise AssertionError
  606. def get_desc(arg: Any) -> str:
  607. arg_type = get_type(arg)
  608. return (
  609. get_name(arg)
  610. + (f": {arg_type}" if arg_type else "")
  611. + (" = ..." if has_default(arg) else "")
  612. )
  613. kw_only = sorted(self.kwonly.values(), key=lambda a: (has_default(a), get_name(a)))
  614. ret = "def ("
  615. ret += ", ".join(
  616. [get_desc(arg) for arg in self.pos]
  617. + (["*" + get_name(self.varpos)] if self.varpos else (["*"] if self.kwonly else []))
  618. + [get_desc(arg) for arg in kw_only]
  619. + (["**" + get_name(self.varkw)] if self.varkw else [])
  620. )
  621. ret += ")"
  622. return ret
  623. @staticmethod
  624. def from_funcitem(stub: nodes.FuncItem) -> Signature[nodes.Argument]:
  625. stub_sig: Signature[nodes.Argument] = Signature()
  626. stub_args = maybe_strip_cls(stub.name, stub.arguments)
  627. for stub_arg in stub_args:
  628. if stub_arg.kind.is_positional():
  629. stub_sig.pos.append(stub_arg)
  630. elif stub_arg.kind.is_named():
  631. stub_sig.kwonly[stub_arg.variable.name] = stub_arg
  632. elif stub_arg.kind == nodes.ARG_STAR:
  633. stub_sig.varpos = stub_arg
  634. elif stub_arg.kind == nodes.ARG_STAR2:
  635. stub_sig.varkw = stub_arg
  636. else:
  637. raise AssertionError
  638. return stub_sig
  639. @staticmethod
  640. def from_inspect_signature(signature: inspect.Signature) -> Signature[inspect.Parameter]:
  641. runtime_sig: Signature[inspect.Parameter] = Signature()
  642. for runtime_arg in signature.parameters.values():
  643. if runtime_arg.kind in (
  644. inspect.Parameter.POSITIONAL_ONLY,
  645. inspect.Parameter.POSITIONAL_OR_KEYWORD,
  646. ):
  647. runtime_sig.pos.append(runtime_arg)
  648. elif runtime_arg.kind == inspect.Parameter.KEYWORD_ONLY:
  649. runtime_sig.kwonly[runtime_arg.name] = runtime_arg
  650. elif runtime_arg.kind == inspect.Parameter.VAR_POSITIONAL:
  651. runtime_sig.varpos = runtime_arg
  652. elif runtime_arg.kind == inspect.Parameter.VAR_KEYWORD:
  653. runtime_sig.varkw = runtime_arg
  654. else:
  655. raise AssertionError
  656. return runtime_sig
  657. @staticmethod
  658. def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Argument]:
  659. """Returns a Signature from an OverloadedFuncDef.
  660. If life were simple, to verify_overloadedfuncdef, we'd just verify_funcitem for each of its
  661. items. Unfortunately, life isn't simple and overloads are pretty deceitful. So instead, we
  662. try and combine the overload's items into a single signature that is compatible with any
  663. lies it might try to tell.
  664. """
  665. # For most dunder methods, just assume all args are positional-only
  666. assume_positional_only = is_dunder(stub.name, exclude_special=True)
  667. all_args: dict[str, list[tuple[nodes.Argument, int]]] = {}
  668. for func in map(_resolve_funcitem_from_decorator, stub.items):
  669. assert func is not None
  670. args = maybe_strip_cls(stub.name, func.arguments)
  671. for index, arg in enumerate(args):
  672. # For positional-only args, we allow overloads to have different names for the same
  673. # argument. To accomplish this, we just make up a fake index-based name.
  674. name = (
  675. f"__{index}"
  676. if arg.variable.name.startswith("__") or assume_positional_only
  677. else arg.variable.name
  678. )
  679. all_args.setdefault(name, []).append((arg, index))
  680. def get_position(arg_name: str) -> int:
  681. # We just need this to return the positional args in the correct order.
  682. return max(index for _, index in all_args[arg_name])
  683. def get_type(arg_name: str) -> mypy.types.ProperType:
  684. with mypy.state.state.strict_optional_set(True):
  685. all_types = [
  686. arg.variable.type or arg.type_annotation for arg, _ in all_args[arg_name]
  687. ]
  688. return mypy.typeops.make_simplified_union([t for t in all_types if t])
  689. def get_kind(arg_name: str) -> nodes.ArgKind:
  690. kinds = {arg.kind for arg, _ in all_args[arg_name]}
  691. if nodes.ARG_STAR in kinds:
  692. return nodes.ARG_STAR
  693. if nodes.ARG_STAR2 in kinds:
  694. return nodes.ARG_STAR2
  695. # The logic here is based on two tenets:
  696. # 1) If an arg is ever optional (or unspecified), it is optional
  697. # 2) If an arg is ever positional, it is positional
  698. is_opt = (
  699. len(all_args[arg_name]) < len(stub.items)
  700. or nodes.ARG_OPT in kinds
  701. or nodes.ARG_NAMED_OPT in kinds
  702. )
  703. is_pos = nodes.ARG_OPT in kinds or nodes.ARG_POS in kinds
  704. if is_opt:
  705. return nodes.ARG_OPT if is_pos else nodes.ARG_NAMED_OPT
  706. return nodes.ARG_POS if is_pos else nodes.ARG_NAMED
  707. sig: Signature[nodes.Argument] = Signature()
  708. for arg_name in sorted(all_args, key=get_position):
  709. # example_arg_name gives us a real name (in case we had a fake index-based name)
  710. example_arg_name = all_args[arg_name][0][0].variable.name
  711. arg = nodes.Argument(
  712. nodes.Var(example_arg_name, get_type(arg_name)),
  713. type_annotation=None,
  714. initializer=None,
  715. kind=get_kind(arg_name),
  716. )
  717. if arg.kind.is_positional():
  718. sig.pos.append(arg)
  719. elif arg.kind.is_named():
  720. sig.kwonly[arg.variable.name] = arg
  721. elif arg.kind == nodes.ARG_STAR:
  722. sig.varpos = arg
  723. elif arg.kind == nodes.ARG_STAR2:
  724. sig.varkw = arg
  725. else:
  726. raise AssertionError
  727. return sig
  728. def _verify_signature(
  729. stub: Signature[nodes.Argument], runtime: Signature[inspect.Parameter], function_name: str
  730. ) -> Iterator[str]:
  731. # Check positional arguments match up
  732. for stub_arg, runtime_arg in zip(stub.pos, runtime.pos):
  733. yield from _verify_arg_name(stub_arg, runtime_arg, function_name)
  734. yield from _verify_arg_default_value(stub_arg, runtime_arg)
  735. if (
  736. runtime_arg.kind == inspect.Parameter.POSITIONAL_ONLY
  737. and not stub_arg.pos_only
  738. and not stub_arg.variable.name.startswith("__")
  739. and not stub_arg.variable.name.strip("_") == "self"
  740. and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
  741. ):
  742. yield (
  743. f'stub argument "{stub_arg.variable.name}" should be positional-only '
  744. f'(rename with a leading double underscore, i.e. "__{runtime_arg.name}")'
  745. )
  746. if (
  747. runtime_arg.kind != inspect.Parameter.POSITIONAL_ONLY
  748. and (stub_arg.pos_only or stub_arg.variable.name.startswith("__"))
  749. and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
  750. ):
  751. yield (
  752. f'stub argument "{stub_arg.variable.name}" should be positional or keyword '
  753. "(remove leading double underscore)"
  754. )
  755. # Check unmatched positional args
  756. if len(stub.pos) > len(runtime.pos):
  757. # There are cases where the stub exhaustively lists out the extra parameters the function
  758. # would take through *args. Hence, a) if runtime accepts *args, we don't check whether the
  759. # runtime has all of the stub's parameters, b) below, we don't enforce that the stub takes
  760. # *args, since runtime logic may prevent arbitrary arguments from actually being accepted.
  761. if runtime.varpos is None:
  762. for stub_arg in stub.pos[len(runtime.pos) :]:
  763. # If the variable is in runtime.kwonly, it's just mislabelled as not a
  764. # keyword-only argument
  765. if stub_arg.variable.name not in runtime.kwonly:
  766. yield f'runtime does not have argument "{stub_arg.variable.name}"'
  767. else:
  768. yield f'stub argument "{stub_arg.variable.name}" is not keyword-only'
  769. if stub.varpos is not None:
  770. yield f'runtime does not have *args argument "{stub.varpos.variable.name}"'
  771. elif len(stub.pos) < len(runtime.pos):
  772. for runtime_arg in runtime.pos[len(stub.pos) :]:
  773. if runtime_arg.name not in stub.kwonly:
  774. yield f'stub does not have argument "{runtime_arg.name}"'
  775. else:
  776. yield f'runtime argument "{runtime_arg.name}" is not keyword-only'
  777. # Checks involving *args
  778. if len(stub.pos) <= len(runtime.pos) or runtime.varpos is None:
  779. if stub.varpos is None and runtime.varpos is not None:
  780. yield f'stub does not have *args argument "{runtime.varpos.name}"'
  781. if stub.varpos is not None and runtime.varpos is None:
  782. yield f'runtime does not have *args argument "{stub.varpos.variable.name}"'
  783. # Check keyword-only args
  784. for arg in sorted(set(stub.kwonly) & set(runtime.kwonly)):
  785. stub_arg, runtime_arg = stub.kwonly[arg], runtime.kwonly[arg]
  786. yield from _verify_arg_name(stub_arg, runtime_arg, function_name)
  787. yield from _verify_arg_default_value(stub_arg, runtime_arg)
  788. # Check unmatched keyword-only args
  789. if runtime.varkw is None or not set(runtime.kwonly).issubset(set(stub.kwonly)):
  790. # There are cases where the stub exhaustively lists out the extra parameters the function
  791. # would take through **kwargs. Hence, a) if runtime accepts **kwargs (and the stub hasn't
  792. # exhaustively listed out params), we don't check whether the runtime has all of the stub's
  793. # parameters, b) below, we don't enforce that the stub takes **kwargs, since runtime logic
  794. # may prevent arbitrary keyword arguments from actually being accepted.
  795. for arg in sorted(set(stub.kwonly) - set(runtime.kwonly)):
  796. if arg in {runtime_arg.name for runtime_arg in runtime.pos}:
  797. # Don't report this if we've reported it before
  798. if arg not in {runtime_arg.name for runtime_arg in runtime.pos[len(stub.pos) :]}:
  799. yield f'runtime argument "{arg}" is not keyword-only'
  800. else:
  801. yield f'runtime does not have argument "{arg}"'
  802. for arg in sorted(set(runtime.kwonly) - set(stub.kwonly)):
  803. if arg in {stub_arg.variable.name for stub_arg in stub.pos}:
  804. # Don't report this if we've reported it before
  805. if not (
  806. runtime.varpos is None
  807. and arg in {stub_arg.variable.name for stub_arg in stub.pos[len(runtime.pos) :]}
  808. ):
  809. yield f'stub argument "{arg}" is not keyword-only'
  810. else:
  811. yield f'stub does not have argument "{arg}"'
  812. # Checks involving **kwargs
  813. if stub.varkw is None and runtime.varkw is not None:
  814. # As mentioned above, don't enforce that the stub takes **kwargs.
  815. # Also check against positional parameters, to avoid a nitpicky message when an argument
  816. # isn't marked as keyword-only
  817. stub_pos_names = {stub_arg.variable.name for stub_arg in stub.pos}
  818. # Ideally we'd do a strict subset check, but in practice the errors from that aren't useful
  819. if not set(runtime.kwonly).issubset(set(stub.kwonly) | stub_pos_names):
  820. yield f'stub does not have **kwargs argument "{runtime.varkw.name}"'
  821. if stub.varkw is not None and runtime.varkw is None:
  822. yield f'runtime does not have **kwargs argument "{stub.varkw.variable.name}"'
  823. @verify.register(nodes.FuncItem)
  824. def verify_funcitem(
  825. stub: nodes.FuncItem, runtime: MaybeMissing[Any], object_path: list[str]
  826. ) -> Iterator[Error]:
  827. if isinstance(runtime, Missing):
  828. yield Error(object_path, "is not present at runtime", stub, runtime)
  829. return
  830. if not is_probably_a_function(runtime):
  831. yield Error(object_path, "is not a function", stub, runtime)
  832. if not callable(runtime):
  833. return
  834. # Look the object up statically, to avoid binding by the descriptor protocol
  835. static_runtime = _static_lookup_runtime(object_path)
  836. if isinstance(stub, nodes.FuncDef):
  837. for error_text in _verify_abstract_status(stub, runtime):
  838. yield Error(object_path, error_text, stub, runtime)
  839. for error_text in _verify_final_method(stub, runtime, static_runtime):
  840. yield Error(object_path, error_text, stub, runtime)
  841. for message in _verify_static_class_methods(stub, runtime, static_runtime, object_path):
  842. yield Error(object_path, "is inconsistent, " + message, stub, runtime)
  843. signature = safe_inspect_signature(runtime)
  844. runtime_is_coroutine = inspect.iscoroutinefunction(runtime)
  845. if signature:
  846. stub_sig = Signature.from_funcitem(stub)
  847. runtime_sig = Signature.from_inspect_signature(signature)
  848. runtime_sig_desc = f'{"async " if runtime_is_coroutine else ""}def {signature}'
  849. stub_desc = str(stub_sig)
  850. else:
  851. runtime_sig_desc, stub_desc = None, None
  852. # Don't raise an error if the stub is a coroutine, but the runtime isn't.
  853. # That results in false positives.
  854. # See https://github.com/python/typeshed/issues/7344
  855. if runtime_is_coroutine and not stub.is_coroutine:
  856. yield Error(
  857. object_path,
  858. 'is an "async def" function at runtime, but not in the stub',
  859. stub,
  860. runtime,
  861. stub_desc=stub_desc,
  862. runtime_desc=runtime_sig_desc,
  863. )
  864. if not signature:
  865. return
  866. for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name):
  867. yield Error(
  868. object_path,
  869. "is inconsistent, " + message,
  870. stub,
  871. runtime,
  872. runtime_desc=runtime_sig_desc,
  873. )
  874. @verify.register(Missing)
  875. def verify_none(
  876. stub: Missing, runtime: MaybeMissing[Any], object_path: list[str]
  877. ) -> Iterator[Error]:
  878. yield Error(object_path, "is not present in stub", stub, runtime)
  879. @verify.register(nodes.Var)
  880. def verify_var(
  881. stub: nodes.Var, runtime: MaybeMissing[Any], object_path: list[str]
  882. ) -> Iterator[Error]:
  883. if isinstance(runtime, Missing):
  884. # Don't always yield an error here, because we often can't find instance variables
  885. if len(object_path) <= 2:
  886. yield Error(object_path, "is not present at runtime", stub, runtime)
  887. return
  888. if (
  889. stub.is_initialized_in_class
  890. and is_read_only_property(runtime)
  891. and (stub.is_settable_property or not stub.is_property)
  892. ):
  893. yield Error(object_path, "is read-only at runtime but not in the stub", stub, runtime)
  894. runtime_type = get_mypy_type_of_runtime_value(runtime)
  895. if (
  896. runtime_type is not None
  897. and stub.type is not None
  898. and not is_subtype_helper(runtime_type, stub.type)
  899. ):
  900. should_error = True
  901. # Avoid errors when defining enums, since runtime_type is the enum itself, but we'd
  902. # annotate it with the type of runtime.value
  903. if isinstance(runtime, enum.Enum):
  904. runtime_type = get_mypy_type_of_runtime_value(runtime.value)
  905. if runtime_type is not None and is_subtype_helper(runtime_type, stub.type):
  906. should_error = False
  907. if should_error:
  908. yield Error(
  909. object_path, f"variable differs from runtime type {runtime_type}", stub, runtime
  910. )
  911. @verify.register(nodes.OverloadedFuncDef)
  912. def verify_overloadedfuncdef(
  913. stub: nodes.OverloadedFuncDef, runtime: MaybeMissing[Any], object_path: list[str]
  914. ) -> Iterator[Error]:
  915. if isinstance(runtime, Missing):
  916. yield Error(object_path, "is not present at runtime", stub, runtime)
  917. return
  918. if stub.is_property:
  919. # Any property with a setter is represented as an OverloadedFuncDef
  920. if is_read_only_property(runtime):
  921. yield Error(object_path, "is read-only at runtime but not in the stub", stub, runtime)
  922. return
  923. if not is_probably_a_function(runtime):
  924. yield Error(object_path, "is not a function", stub, runtime)
  925. if not callable(runtime):
  926. return
  927. # mypy doesn't allow overloads where one overload is abstract but another isn't,
  928. # so it should be okay to just check whether the first overload is abstract or not.
  929. #
  930. # TODO: Mypy *does* allow properties where e.g. the getter is abstract but the setter is not;
  931. # and any property with a setter is represented as an OverloadedFuncDef internally;
  932. # not sure exactly what (if anything) we should do about that.
  933. first_part = stub.items[0]
  934. if isinstance(first_part, nodes.Decorator) and first_part.is_overload:
  935. for msg in _verify_abstract_status(first_part.func, runtime):
  936. yield Error(object_path, msg, stub, runtime)
  937. # Look the object up statically, to avoid binding by the descriptor protocol
  938. static_runtime = _static_lookup_runtime(object_path)
  939. for message in _verify_static_class_methods(stub, runtime, static_runtime, object_path):
  940. yield Error(object_path, "is inconsistent, " + message, stub, runtime)
  941. # TODO: Should call _verify_final_method here,
  942. # but overloaded final methods in stubs cause a stubtest crash: see #14950
  943. signature = safe_inspect_signature(runtime)
  944. if not signature:
  945. return
  946. stub_sig = Signature.from_overloadedfuncdef(stub)
  947. runtime_sig = Signature.from_inspect_signature(signature)
  948. for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name):
  949. # TODO: This is a little hacky, but the addition here is super useful
  950. if "has a default value of type" in message:
  951. message += (
  952. ". This is often caused by overloads failing to account for explicitly passing "
  953. "in the default value."
  954. )
  955. yield Error(
  956. object_path,
  957. "is inconsistent, " + message,
  958. stub,
  959. runtime,
  960. stub_desc=(str(stub.type)) + f"\nInferred signature: {stub_sig}",
  961. runtime_desc="def " + str(signature),
  962. )
  963. @verify.register(nodes.TypeVarExpr)
  964. def verify_typevarexpr(
  965. stub: nodes.TypeVarExpr, runtime: MaybeMissing[Any], object_path: list[str]
  966. ) -> Iterator[Error]:
  967. if isinstance(runtime, Missing):
  968. # We seem to insert these typevars into NamedTuple stubs, but they
  969. # don't exist at runtime. Just ignore!
  970. if stub.name == "_NT":
  971. return
  972. yield Error(object_path, "is not present at runtime", stub, runtime)
  973. return
  974. if not isinstance(runtime, TypeVar):
  975. yield Error(object_path, "is not a TypeVar", stub, runtime)
  976. return
  977. @verify.register(nodes.ParamSpecExpr)
  978. def verify_paramspecexpr(
  979. stub: nodes.ParamSpecExpr, runtime: MaybeMissing[Any], object_path: list[str]
  980. ) -> Iterator[Error]:
  981. if isinstance(runtime, Missing):
  982. yield Error(object_path, "is not present at runtime", stub, runtime)
  983. return
  984. maybe_paramspec_types = (
  985. getattr(typing, "ParamSpec", None),
  986. getattr(typing_extensions, "ParamSpec", None),
  987. )
  988. paramspec_types = tuple(t for t in maybe_paramspec_types if t is not None)
  989. if not paramspec_types or not isinstance(runtime, paramspec_types):
  990. yield Error(object_path, "is not a ParamSpec", stub, runtime)
  991. return
  992. def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[str]:
  993. assert stub.func.is_property
  994. if isinstance(runtime, property):
  995. yield from _verify_final_method(stub.func, runtime.fget, MISSING)
  996. return
  997. if inspect.isdatadescriptor(runtime):
  998. # It's enough like a property...
  999. return
  1000. # Sometimes attributes pretend to be properties, for instance, to express that they
  1001. # are read only. So allowlist if runtime_type matches the return type of stub.
  1002. runtime_type = get_mypy_type_of_runtime_value(runtime)
  1003. func_type = (
  1004. stub.func.type.ret_type if isinstance(stub.func.type, mypy.types.CallableType) else None
  1005. )
  1006. if (
  1007. runtime_type is not None
  1008. and func_type is not None
  1009. and is_subtype_helper(runtime_type, func_type)
  1010. ):
  1011. return
  1012. yield "is inconsistent, cannot reconcile @property on stub with runtime object"
  1013. def _verify_abstract_status(stub: nodes.FuncDef, runtime: Any) -> Iterator[str]:
  1014. stub_abstract = stub.abstract_status == nodes.IS_ABSTRACT
  1015. runtime_abstract = getattr(runtime, "__isabstractmethod__", False)
  1016. # The opposite can exist: some implementations omit `@abstractmethod` decorators
  1017. if runtime_abstract and not stub_abstract:
  1018. item_type = "property" if stub.is_property else "method"
  1019. yield f"is inconsistent, runtime {item_type} is abstract but stub is not"
  1020. def _verify_final_method(
  1021. stub: nodes.FuncDef, runtime: Any, static_runtime: MaybeMissing[Any]
  1022. ) -> Iterator[str]:
  1023. if stub.is_final:
  1024. return
  1025. if getattr(runtime, "__final__", False) or (
  1026. static_runtime is not MISSING and getattr(static_runtime, "__final__", False)
  1027. ):
  1028. yield "is decorated with @final at runtime, but not in the stub"
  1029. def _resolve_funcitem_from_decorator(dec: nodes.OverloadPart) -> nodes.FuncItem | None:
  1030. """Returns a FuncItem that corresponds to the output of the decorator.
  1031. Returns None if we can't figure out what that would be. For convenience, this function also
  1032. accepts FuncItems.
  1033. """
  1034. if isinstance(dec, nodes.FuncItem):
  1035. return dec
  1036. if dec.func.is_property:
  1037. return None
  1038. def apply_decorator_to_funcitem(
  1039. decorator: nodes.Expression, func: nodes.FuncItem
  1040. ) -> nodes.FuncItem | None:
  1041. if not isinstance(decorator, nodes.RefExpr):
  1042. return None
  1043. if not decorator.fullname:
  1044. # Happens with namedtuple
  1045. return None
  1046. if (
  1047. decorator.fullname in ("builtins.staticmethod", "abc.abstractmethod")
  1048. or decorator.fullname in mypy.types.OVERLOAD_NAMES
  1049. ):
  1050. return func
  1051. if decorator.fullname == "builtins.classmethod":
  1052. if func.arguments[0].variable.name not in ("cls", "mcs", "metacls"):
  1053. raise StubtestFailure(
  1054. f"unexpected class argument name {func.arguments[0].variable.name!r} "
  1055. f"in {dec.fullname}"
  1056. )
  1057. # FuncItem is written so that copy.copy() actually works, even when compiled
  1058. ret = copy.copy(func)
  1059. # Remove the cls argument, since it's not present in inspect.signature of classmethods
  1060. ret.arguments = ret.arguments[1:]
  1061. return ret
  1062. # Just give up on any other decorators. After excluding properties, we don't run into
  1063. # anything else when running on typeshed's stdlib.
  1064. return None
  1065. func: nodes.FuncItem = dec.func
  1066. for decorator in dec.original_decorators:
  1067. resulting_func = apply_decorator_to_funcitem(decorator, func)
  1068. if resulting_func is None:
  1069. return None
  1070. func = resulting_func
  1071. return func
  1072. @verify.register(nodes.Decorator)
  1073. def verify_decorator(
  1074. stub: nodes.Decorator, runtime: MaybeMissing[Any], object_path: list[str]
  1075. ) -> Iterator[Error]:
  1076. if isinstance(runtime, Missing):
  1077. yield Error(object_path, "is not present at runtime", stub, runtime)
  1078. return
  1079. if stub.func.is_property:
  1080. for message in _verify_readonly_property(stub, runtime):
  1081. yield Error(object_path, message, stub, runtime)
  1082. for message in _verify_abstract_status(stub.func, runtime):
  1083. yield Error(object_path, message, stub, runtime)
  1084. return
  1085. func = _resolve_funcitem_from_decorator(stub)
  1086. if func is not None:
  1087. yield from verify(func, runtime, object_path)
  1088. @verify.register(nodes.TypeAlias)
  1089. def verify_typealias(
  1090. stub: nodes.TypeAlias, runtime: MaybeMissing[Any], object_path: list[str]
  1091. ) -> Iterator[Error]:
  1092. stub_target = mypy.types.get_proper_type(stub.target)
  1093. stub_desc = f"Type alias for {stub_target}"
  1094. if isinstance(runtime, Missing):
  1095. yield Error(object_path, "is not present at runtime", stub, runtime, stub_desc=stub_desc)
  1096. return
  1097. runtime_origin = get_origin(runtime) or runtime
  1098. if isinstance(stub_target, mypy.types.Instance):
  1099. if not isinstance(runtime_origin, type):
  1100. yield Error(
  1101. object_path,
  1102. "is inconsistent, runtime is not a type",
  1103. stub,
  1104. runtime,
  1105. stub_desc=stub_desc,
  1106. )
  1107. return
  1108. stub_origin = stub_target.type
  1109. # Do our best to figure out the fullname of the runtime object...
  1110. runtime_name: object
  1111. try:
  1112. runtime_name = runtime_origin.__qualname__
  1113. except AttributeError:
  1114. runtime_name = getattr(runtime_origin, "__name__", MISSING)
  1115. if isinstance(runtime_name, str):
  1116. runtime_module: object = getattr(runtime_origin, "__module__", MISSING)
  1117. if isinstance(runtime_module, str):
  1118. if runtime_module == "collections.abc" or (
  1119. runtime_module == "re" and runtime_name in {"Match", "Pattern"}
  1120. ):
  1121. runtime_module = "typing"
  1122. runtime_fullname = f"{runtime_module}.{runtime_name}"
  1123. if re.fullmatch(rf"_?{re.escape(stub_origin.fullname)}", runtime_fullname):
  1124. # Okay, we're probably fine.
  1125. return
  1126. # Okay, either we couldn't construct a fullname
  1127. # or the fullname of the stub didn't match the fullname of the runtime.
  1128. # Fallback to a full structural check of the runtime vis-a-vis the stub.
  1129. yield from verify(stub_origin, runtime_origin, object_path)
  1130. return
  1131. if isinstance(stub_target, mypy.types.UnionType):
  1132. # complain if runtime is not a Union or UnionType
  1133. if runtime_origin is not Union and (
  1134. not (sys.version_info >= (3, 10) and isinstance(runtime, types.UnionType))
  1135. ):
  1136. yield Error(object_path, "is not a Union", stub, runtime, stub_desc=str(stub_target))
  1137. # could check Union contents here...
  1138. return
  1139. if isinstance(stub_target, mypy.types.TupleType):
  1140. if tuple not in getattr(runtime_origin, "__mro__", ()):
  1141. yield Error(
  1142. object_path, "is not a subclass of tuple", stub, runtime, stub_desc=stub_desc
  1143. )
  1144. # could check Tuple contents here...
  1145. return
  1146. if isinstance(stub_target, mypy.types.CallableType):
  1147. if runtime_origin is not collections.abc.Callable:
  1148. yield Error(
  1149. object_path, "is not a type alias for Callable", stub, runtime, stub_desc=stub_desc
  1150. )
  1151. # could check Callable contents here...
  1152. return
  1153. if isinstance(stub_target, mypy.types.AnyType):
  1154. return
  1155. yield Error(object_path, "is not a recognised type alias", stub, runtime, stub_desc=stub_desc)
  1156. # ====================
  1157. # Helpers
  1158. # ====================
  1159. IGNORED_MODULE_DUNDERS: typing_extensions.Final = frozenset(
  1160. {
  1161. "__file__",
  1162. "__doc__",
  1163. "__name__",
  1164. "__builtins__",
  1165. "__package__",
  1166. "__cached__",
  1167. "__loader__",
  1168. "__spec__",
  1169. "__annotations__",
  1170. "__path__", # mypy adds __path__ to packages, but C packages don't have it
  1171. "__getattr__", # resulting behaviour might be typed explicitly
  1172. # Created by `warnings.warn`, does not make much sense to have in stubs:
  1173. "__warningregistry__",
  1174. # TODO: remove the following from this list
  1175. "__author__",
  1176. "__version__",
  1177. "__copyright__",
  1178. }
  1179. )
  1180. IGNORABLE_CLASS_DUNDERS: typing_extensions.Final = frozenset(
  1181. {
  1182. # Special attributes
  1183. "__dict__",
  1184. "__annotations__",
  1185. "__text_signature__",
  1186. "__weakref__",
  1187. "__del__", # Only ever called when an object is being deleted, who cares?
  1188. "__hash__",
  1189. "__getattr__", # resulting behaviour might be typed explicitly
  1190. "__setattr__", # defining this on a class can cause worse type checking
  1191. "__vectorcalloffset__", # undocumented implementation detail of the vectorcall protocol
  1192. # isinstance/issubclass hooks that type-checkers don't usually care about
  1193. "__instancecheck__",
  1194. "__subclasshook__",
  1195. "__subclasscheck__",
  1196. # python2 only magic methods:
  1197. "__cmp__",
  1198. "__nonzero__",
  1199. "__unicode__",
  1200. "__div__",
  1201. # cython methods
  1202. "__pyx_vtable__",
  1203. # Pickle methods
  1204. "__setstate__",
  1205. "__getstate__",
  1206. "__getnewargs__",
  1207. "__getinitargs__",
  1208. "__reduce_ex__",
  1209. "__reduce__",
  1210. # ctypes weirdness
  1211. "__ctype_be__",
  1212. "__ctype_le__",
  1213. "__ctypes_from_outparam__",
  1214. # mypy limitations
  1215. "__abstractmethods__", # Classes with metaclass=ABCMeta inherit this attribute
  1216. "__new_member__", # If an enum defines __new__, the method is renamed as __new_member__
  1217. "__dataclass_fields__", # Generated by dataclasses
  1218. "__dataclass_params__", # Generated by dataclasses
  1219. "__doc__", # mypy's semanal for namedtuples assumes this is str, not Optional[str]
  1220. # Added to all protocol classes on 3.12+ (or if using typing_extensions.Protocol)
  1221. "__protocol_attrs__",
  1222. "__callable_proto_members_only__",
  1223. # typing implementation details, consider removing some of these:
  1224. "__parameters__",
  1225. "__origin__",
  1226. "__args__",
  1227. "__orig_bases__",
  1228. "__final__", # Has a specialized check
  1229. # Consider removing __slots__?
  1230. "__slots__",
  1231. }
  1232. )
  1233. def is_probably_private(name: str) -> bool:
  1234. return name.startswith("_") and not is_dunder(name)
  1235. def is_probably_a_function(runtime: Any) -> bool:
  1236. return (
  1237. isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType))
  1238. or isinstance(runtime, (types.MethodType, types.BuiltinMethodType))
  1239. or (inspect.ismethoddescriptor(runtime) and callable(runtime))
  1240. )
  1241. def is_read_only_property(runtime: object) -> bool:
  1242. return isinstance(runtime, property) and runtime.fset is None
  1243. def safe_inspect_signature(runtime: Any) -> inspect.Signature | None:
  1244. try:
  1245. return inspect.signature(runtime)
  1246. except Exception:
  1247. # inspect.signature throws ValueError all the time
  1248. # catch RuntimeError because of https://bugs.python.org/issue39504
  1249. # catch TypeError because of https://github.com/python/typeshed/pull/5762
  1250. # catch AttributeError because of inspect.signature(_curses.window.border)
  1251. return None
  1252. def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool:
  1253. """Checks whether ``left`` is a subtype of ``right``."""
  1254. left = mypy.types.get_proper_type(left)
  1255. right = mypy.types.get_proper_type(right)
  1256. if (
  1257. isinstance(left, mypy.types.LiteralType)
  1258. and isinstance(left.value, int)
  1259. and left.value in (0, 1)
  1260. and mypy.types.is_named_instance(right, "builtins.bool")
  1261. ):
  1262. # Pretend Literal[0, 1] is a subtype of bool to avoid unhelpful errors.
  1263. return True
  1264. if isinstance(right, mypy.types.TypedDictType) and mypy.types.is_named_instance(
  1265. left, "builtins.dict"
  1266. ):
  1267. # Special case checks against TypedDicts
  1268. return True
  1269. with mypy.state.state.strict_optional_set(True):
  1270. return mypy.subtypes.is_subtype(left, right)
  1271. def get_mypy_type_of_runtime_value(runtime: Any) -> mypy.types.Type | None:
  1272. """Returns a mypy type object representing the type of ``runtime``.
  1273. Returns None if we can't find something that works.
  1274. """
  1275. if runtime is None:
  1276. return mypy.types.NoneType()
  1277. if isinstance(runtime, property):
  1278. # Give up on properties to avoid issues with things that are typed as attributes.
  1279. return None
  1280. def anytype() -> mypy.types.AnyType:
  1281. return mypy.types.AnyType(mypy.types.TypeOfAny.unannotated)
  1282. if isinstance(
  1283. runtime,
  1284. (types.FunctionType, types.BuiltinFunctionType, types.MethodType, types.BuiltinMethodType),
  1285. ):
  1286. builtins = get_stub("builtins")
  1287. assert builtins is not None
  1288. type_info = builtins.names["function"].node
  1289. assert isinstance(type_info, nodes.TypeInfo)
  1290. fallback = mypy.types.Instance(type_info, [anytype()])
  1291. signature = safe_inspect_signature(runtime)
  1292. if signature:
  1293. arg_types = []
  1294. arg_kinds = []
  1295. arg_names = []
  1296. for arg in signature.parameters.values():
  1297. arg_types.append(anytype())
  1298. arg_names.append(
  1299. None if arg.kind == inspect.Parameter.POSITIONAL_ONLY else arg.name
  1300. )
  1301. has_default = arg.default == inspect.Parameter.empty
  1302. if arg.kind == inspect.Parameter.POSITIONAL_ONLY:
  1303. arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
  1304. elif arg.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
  1305. arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
  1306. elif arg.kind == inspect.Parameter.KEYWORD_ONLY:
  1307. arg_kinds.append(nodes.ARG_NAMED if has_default else nodes.ARG_NAMED_OPT)
  1308. elif arg.kind == inspect.Parameter.VAR_POSITIONAL:
  1309. arg_kinds.append(nodes.ARG_STAR)
  1310. elif arg.kind == inspect.Parameter.VAR_KEYWORD:
  1311. arg_kinds.append(nodes.ARG_STAR2)
  1312. else:
  1313. raise AssertionError
  1314. else:
  1315. arg_types = [anytype(), anytype()]
  1316. arg_kinds = [nodes.ARG_STAR, nodes.ARG_STAR2]
  1317. arg_names = [None, None]
  1318. return mypy.types.CallableType(
  1319. arg_types,
  1320. arg_kinds,
  1321. arg_names,
  1322. ret_type=anytype(),
  1323. fallback=fallback,
  1324. is_ellipsis_args=True,
  1325. )
  1326. # Try and look up a stub for the runtime object
  1327. stub = get_stub(type(runtime).__module__)
  1328. if stub is None:
  1329. return None
  1330. type_name = type(runtime).__name__
  1331. if type_name not in stub.names:
  1332. return None
  1333. type_info = stub.names[type_name].node
  1334. if isinstance(type_info, nodes.Var):
  1335. return type_info.type
  1336. if not isinstance(type_info, nodes.TypeInfo):
  1337. return None
  1338. if isinstance(runtime, tuple):
  1339. # Special case tuples so we construct a valid mypy.types.TupleType
  1340. optional_items = [get_mypy_type_of_runtime_value(v) for v in runtime]
  1341. items = [(i if i is not None else anytype()) for i in optional_items]
  1342. fallback = mypy.types.Instance(type_info, [anytype()])
  1343. return mypy.types.TupleType(items, fallback)
  1344. fallback = mypy.types.Instance(type_info, [anytype() for _ in type_info.type_vars])
  1345. value: bool | int | str
  1346. if isinstance(runtime, bytes):
  1347. value = bytes_to_human_readable_repr(runtime)
  1348. elif isinstance(runtime, enum.Enum):
  1349. value = runtime.name
  1350. elif isinstance(runtime, (bool, int, str)):
  1351. value = runtime
  1352. else:
  1353. return fallback
  1354. return mypy.types.LiteralType(value=value, fallback=fallback)
  1355. # ====================
  1356. # Build and entrypoint
  1357. # ====================
  1358. _all_stubs: dict[str, nodes.MypyFile] = {}
  1359. def build_stubs(modules: list[str], options: Options, find_submodules: bool = False) -> list[str]:
  1360. """Uses mypy to construct stub objects for the given modules.
  1361. This sets global state that ``get_stub`` can access.
  1362. Returns all modules we might want to check. If ``find_submodules`` is False, this is equal
  1363. to ``modules``.
  1364. :param modules: List of modules to build stubs for.
  1365. :param options: Mypy options for finding and building stubs.
  1366. :param find_submodules: Whether to attempt to find submodules of the given modules as well.
  1367. """
  1368. data_dir = mypy.build.default_data_dir()
  1369. search_path = mypy.modulefinder.compute_search_paths([], options, data_dir)
  1370. find_module_cache = mypy.modulefinder.FindModuleCache(
  1371. search_path, fscache=None, options=options
  1372. )
  1373. all_modules = []
  1374. sources = []
  1375. for module in modules:
  1376. all_modules.append(module)
  1377. if not find_submodules:
  1378. module_path = find_module_cache.find_module(module)
  1379. if not isinstance(module_path, str):
  1380. # test_module will yield an error later when it can't find stubs
  1381. continue
  1382. sources.append(mypy.modulefinder.BuildSource(module_path, module, None))
  1383. else:
  1384. found_sources = find_module_cache.find_modules_recursive(module)
  1385. sources.extend(found_sources)
  1386. # find submodules via mypy
  1387. all_modules.extend(s.module for s in found_sources if s.module not in all_modules)
  1388. # find submodules via pkgutil
  1389. try:
  1390. runtime = silent_import_module(module)
  1391. all_modules.extend(
  1392. m.name
  1393. for m in pkgutil.walk_packages(runtime.__path__, runtime.__name__ + ".")
  1394. if m.name not in all_modules
  1395. )
  1396. except KeyboardInterrupt:
  1397. raise
  1398. except BaseException:
  1399. pass
  1400. if sources:
  1401. try:
  1402. res = mypy.build.build(sources=sources, options=options)
  1403. except mypy.errors.CompileError as e:
  1404. raise StubtestFailure(f"failed mypy compile:\n{e}") from e
  1405. if res.errors:
  1406. raise StubtestFailure("mypy build errors:\n" + "\n".join(res.errors))
  1407. global _all_stubs
  1408. _all_stubs = res.files
  1409. return all_modules
  1410. def get_stub(module: str) -> nodes.MypyFile | None:
  1411. """Returns a stub object for the given module, if we've built one."""
  1412. return _all_stubs.get(module)
  1413. def get_typeshed_stdlib_modules(
  1414. custom_typeshed_dir: str | None, version_info: tuple[int, int] | None = None
  1415. ) -> list[str]:
  1416. """Returns a list of stdlib modules in typeshed (for current Python version)."""
  1417. stdlib_py_versions = mypy.modulefinder.load_stdlib_py_versions(custom_typeshed_dir)
  1418. if version_info is None:
  1419. version_info = sys.version_info[0:2]
  1420. def exists_in_version(module: str) -> bool:
  1421. assert version_info is not None
  1422. parts = module.split(".")
  1423. for i in range(len(parts), 0, -1):
  1424. current_module = ".".join(parts[:i])
  1425. if current_module in stdlib_py_versions:
  1426. minver, maxver = stdlib_py_versions[current_module]
  1427. return version_info >= minver and (maxver is None or version_info <= maxver)
  1428. return False
  1429. if custom_typeshed_dir:
  1430. typeshed_dir = Path(custom_typeshed_dir)
  1431. else:
  1432. typeshed_dir = Path(mypy.build.default_data_dir()) / "typeshed"
  1433. stdlib_dir = typeshed_dir / "stdlib"
  1434. modules = []
  1435. for path in stdlib_dir.rglob("*.pyi"):
  1436. if path.stem == "__init__":
  1437. path = path.parent
  1438. module = ".".join(path.relative_to(stdlib_dir).parts[:-1] + (path.stem,))
  1439. if exists_in_version(module):
  1440. modules.append(module)
  1441. return sorted(modules)
  1442. def get_allowlist_entries(allowlist_file: str) -> Iterator[str]:
  1443. def strip_comments(s: str) -> str:
  1444. try:
  1445. return s[: s.index("#")].strip()
  1446. except ValueError:
  1447. return s.strip()
  1448. with open(allowlist_file) as f:
  1449. for line in f:
  1450. entry = strip_comments(line)
  1451. if entry:
  1452. yield entry
  1453. class _Arguments:
  1454. modules: list[str]
  1455. concise: bool
  1456. ignore_missing_stub: bool
  1457. ignore_positional_only: bool
  1458. allowlist: list[str]
  1459. generate_allowlist: bool
  1460. ignore_unused_allowlist: bool
  1461. mypy_config_file: str
  1462. custom_typeshed_dir: str
  1463. check_typeshed: bool
  1464. version: str
  1465. def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
  1466. """This is stubtest! It's time to test the stubs!"""
  1467. # Load the allowlist. This is a series of strings corresponding to Error.object_desc
  1468. # Values in the dict will store whether we used the allowlist entry or not.
  1469. allowlist = {
  1470. entry: False
  1471. for allowlist_file in args.allowlist
  1472. for entry in get_allowlist_entries(allowlist_file)
  1473. }
  1474. allowlist_regexes = {entry: re.compile(entry) for entry in allowlist}
  1475. # If we need to generate an allowlist, we store Error.object_desc for each error here.
  1476. generated_allowlist = set()
  1477. modules = args.modules
  1478. if args.check_typeshed:
  1479. if args.modules:
  1480. print(
  1481. _style("error:", color="red", bold=True),
  1482. "cannot pass both --check-typeshed and a list of modules",
  1483. )
  1484. return 1
  1485. modules = get_typeshed_stdlib_modules(args.custom_typeshed_dir)
  1486. # typeshed added a stub for __main__, but that causes stubtest to check itself
  1487. annoying_modules = {"antigravity", "this", "__main__"}
  1488. modules = [m for m in modules if m not in annoying_modules]
  1489. if not modules:
  1490. print(_style("error:", color="red", bold=True), "no modules to check")
  1491. return 1
  1492. options = Options()
  1493. options.incremental = False
  1494. options.custom_typeshed_dir = args.custom_typeshed_dir
  1495. if options.custom_typeshed_dir:
  1496. options.abs_custom_typeshed_dir = os.path.abspath(args.custom_typeshed_dir)
  1497. options.config_file = args.mypy_config_file
  1498. options.use_builtins_fixtures = use_builtins_fixtures
  1499. if options.config_file:
  1500. def set_strict_flags() -> None: # not needed yet
  1501. return
  1502. parse_config_file(options, set_strict_flags, options.config_file, sys.stdout, sys.stderr)
  1503. try:
  1504. modules = build_stubs(modules, options, find_submodules=not args.check_typeshed)
  1505. except StubtestFailure as stubtest_failure:
  1506. print(
  1507. _style("error:", color="red", bold=True),
  1508. f"not checking stubs due to {stubtest_failure}",
  1509. )
  1510. return 1
  1511. exit_code = 0
  1512. error_count = 0
  1513. for module in modules:
  1514. for error in test_module(module):
  1515. # Filter errors
  1516. if args.ignore_missing_stub and error.is_missing_stub():
  1517. continue
  1518. if args.ignore_positional_only and error.is_positional_only_related():
  1519. continue
  1520. if error.object_desc in allowlist:
  1521. allowlist[error.object_desc] = True
  1522. continue
  1523. is_allowlisted = False
  1524. for w in allowlist:
  1525. if allowlist_regexes[w].fullmatch(error.object_desc):
  1526. allowlist[w] = True
  1527. is_allowlisted = True
  1528. break
  1529. if is_allowlisted:
  1530. continue
  1531. # We have errors, so change exit code, and output whatever necessary
  1532. exit_code = 1
  1533. if args.generate_allowlist:
  1534. generated_allowlist.add(error.object_desc)
  1535. continue
  1536. print(error.get_description(concise=args.concise))
  1537. error_count += 1
  1538. # Print unused allowlist entries
  1539. if not args.ignore_unused_allowlist:
  1540. for w in allowlist:
  1541. # Don't consider an entry unused if it regex-matches the empty string
  1542. # This lets us allowlist errors that don't manifest at all on some systems
  1543. if not allowlist[w] and not allowlist_regexes[w].fullmatch(""):
  1544. exit_code = 1
  1545. error_count += 1
  1546. print(f"note: unused allowlist entry {w}")
  1547. # Print the generated allowlist
  1548. if args.generate_allowlist:
  1549. for e in sorted(generated_allowlist):
  1550. print(e)
  1551. exit_code = 0
  1552. elif not args.concise:
  1553. if error_count:
  1554. print(
  1555. _style(
  1556. f"Found {error_count} error{plural_s(error_count)}"
  1557. f" (checked {len(modules)} module{plural_s(modules)})",
  1558. color="red",
  1559. bold=True,
  1560. )
  1561. )
  1562. else:
  1563. print(
  1564. _style(
  1565. f"Success: no issues found in {len(modules)} module{plural_s(modules)}",
  1566. color="green",
  1567. bold=True,
  1568. )
  1569. )
  1570. return exit_code
  1571. def parse_options(args: list[str]) -> _Arguments:
  1572. parser = argparse.ArgumentParser(
  1573. description="Compares stubs to objects introspected from the runtime."
  1574. )
  1575. parser.add_argument("modules", nargs="*", help="Modules to test")
  1576. parser.add_argument(
  1577. "--concise",
  1578. action="store_true",
  1579. help="Makes stubtest's output more concise, one line per error",
  1580. )
  1581. parser.add_argument(
  1582. "--ignore-missing-stub",
  1583. action="store_true",
  1584. help="Ignore errors for stub missing things that are present at runtime",
  1585. )
  1586. parser.add_argument(
  1587. "--ignore-positional-only",
  1588. action="store_true",
  1589. help="Ignore errors for whether an argument should or shouldn't be positional-only",
  1590. )
  1591. parser.add_argument(
  1592. "--allowlist",
  1593. "--whitelist",
  1594. action="append",
  1595. metavar="FILE",
  1596. default=[],
  1597. help=(
  1598. "Use file as an allowlist. Can be passed multiple times to combine multiple "
  1599. "allowlists. Allowlists can be created with --generate-allowlist. Allowlists "
  1600. "support regular expressions."
  1601. ),
  1602. )
  1603. parser.add_argument(
  1604. "--generate-allowlist",
  1605. "--generate-whitelist",
  1606. action="store_true",
  1607. help="Print an allowlist (to stdout) to be used with --allowlist",
  1608. )
  1609. parser.add_argument(
  1610. "--ignore-unused-allowlist",
  1611. "--ignore-unused-whitelist",
  1612. action="store_true",
  1613. help="Ignore unused allowlist entries",
  1614. )
  1615. parser.add_argument(
  1616. "--mypy-config-file",
  1617. metavar="FILE",
  1618. help=("Use specified mypy config file to determine mypy plugins and mypy path"),
  1619. )
  1620. parser.add_argument(
  1621. "--custom-typeshed-dir", metavar="DIR", help="Use the custom typeshed in DIR"
  1622. )
  1623. parser.add_argument(
  1624. "--check-typeshed", action="store_true", help="Check all stdlib modules in typeshed"
  1625. )
  1626. parser.add_argument(
  1627. "--version", action="version", version="%(prog)s " + mypy.version.__version__
  1628. )
  1629. return parser.parse_args(args, namespace=_Arguments())
  1630. def main() -> int:
  1631. mypy.util.check_python_version("stubtest")
  1632. return test_stubs(parse_options(sys.argv[1:]))
  1633. if __name__ == "__main__":
  1634. sys.exit(main())