raw_building.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  2. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
  4. """this module contains a set of functions to create astroid trees from scratch
  5. (build_* functions) or from living object (object_build_* functions)
  6. """
  7. from __future__ import annotations
  8. import builtins
  9. import inspect
  10. import io
  11. import logging
  12. import os
  13. import sys
  14. import types
  15. import warnings
  16. from collections.abc import Iterable
  17. from contextlib import redirect_stderr, redirect_stdout
  18. from typing import Any, Union
  19. from astroid import bases, nodes
  20. from astroid.const import _EMPTY_OBJECT_MARKER, IS_PYPY
  21. from astroid.manager import AstroidManager
  22. from astroid.nodes import node_classes
  23. logger = logging.getLogger(__name__)
  24. _FunctionTypes = Union[
  25. types.FunctionType,
  26. types.MethodType,
  27. types.BuiltinFunctionType,
  28. types.WrapperDescriptorType,
  29. types.MethodDescriptorType,
  30. types.ClassMethodDescriptorType,
  31. ]
  32. # the keys of CONST_CLS eg python builtin types
  33. _CONSTANTS = tuple(node_classes.CONST_CLS)
  34. _BUILTINS = vars(builtins)
  35. TYPE_NONE = type(None)
  36. TYPE_NOTIMPLEMENTED = type(NotImplemented)
  37. TYPE_ELLIPSIS = type(...)
  38. def _attach_local_node(parent, node, name: str) -> None:
  39. node.name = name # needed by add_local_node
  40. parent.add_local_node(node)
  41. def _add_dunder_class(func, member) -> None:
  42. """Add a __class__ member to the given func node, if we can determine it."""
  43. python_cls = member.__class__
  44. cls_name = getattr(python_cls, "__name__", None)
  45. if not cls_name:
  46. return
  47. cls_bases = [ancestor.__name__ for ancestor in python_cls.__bases__]
  48. ast_klass = build_class(cls_name, cls_bases, python_cls.__doc__)
  49. func.instance_attrs["__class__"] = [ast_klass]
  50. def attach_dummy_node(node, name: str, runtime_object=_EMPTY_OBJECT_MARKER) -> None:
  51. """create a dummy node and register it in the locals of the given
  52. node with the specified name
  53. """
  54. enode = nodes.EmptyNode()
  55. enode.object = runtime_object
  56. _attach_local_node(node, enode, name)
  57. def attach_const_node(node, name: str, value) -> None:
  58. """create a Const node and register it in the locals of the given
  59. node with the specified name
  60. """
  61. if name not in node.special_attributes:
  62. _attach_local_node(node, nodes.const_factory(value), name)
  63. def attach_import_node(node, modname: str, membername: str) -> None:
  64. """create a ImportFrom node and register it in the locals of the given
  65. node with the specified name
  66. """
  67. from_node = nodes.ImportFrom(modname, [(membername, None)])
  68. _attach_local_node(node, from_node, membername)
  69. def build_module(name: str, doc: str | None = None) -> nodes.Module:
  70. """create and initialize an astroid Module node"""
  71. node = nodes.Module(name, pure_python=False, package=False)
  72. node.postinit(
  73. body=[],
  74. doc_node=nodes.Const(value=doc) if doc else None,
  75. )
  76. return node
  77. def build_class(
  78. name: str, basenames: Iterable[str] = (), doc: str | None = None
  79. ) -> nodes.ClassDef:
  80. """Create and initialize an astroid ClassDef node."""
  81. node = nodes.ClassDef(name)
  82. node.postinit(
  83. bases=[nodes.Name(name=base, parent=node) for base in basenames],
  84. body=[],
  85. decorators=None,
  86. doc_node=nodes.Const(value=doc) if doc else None,
  87. )
  88. return node
  89. def build_function(
  90. name: str,
  91. args: list[str] | None = None,
  92. posonlyargs: list[str] | None = None,
  93. defaults: list[Any] | None = None,
  94. doc: str | None = None,
  95. kwonlyargs: list[str] | None = None,
  96. kwonlydefaults: list[Any] | None = None,
  97. ) -> nodes.FunctionDef:
  98. """create and initialize an astroid FunctionDef node"""
  99. # first argument is now a list of decorators
  100. func = nodes.FunctionDef(name)
  101. argsnode = nodes.Arguments(parent=func)
  102. # If args is None we don't have any information about the signature
  103. # (in contrast to when there are no arguments and args == []). We pass
  104. # this to the builder to indicate this.
  105. if args is not None:
  106. arguments = [nodes.AssignName(name=arg, parent=argsnode) for arg in args]
  107. else:
  108. arguments = None
  109. default_nodes: list[nodes.NodeNG] | None = []
  110. if defaults is not None:
  111. for default in defaults:
  112. default_node = nodes.const_factory(default)
  113. default_node.parent = argsnode
  114. default_nodes.append(default_node)
  115. else:
  116. default_nodes = None
  117. kwonlydefault_nodes: list[nodes.NodeNG | None] | None = []
  118. if kwonlydefaults is not None:
  119. for kwonlydefault in kwonlydefaults:
  120. kwonlydefault_node = nodes.const_factory(kwonlydefault)
  121. kwonlydefault_node.parent = argsnode
  122. kwonlydefault_nodes.append(kwonlydefault_node)
  123. else:
  124. kwonlydefault_nodes = None
  125. argsnode.postinit(
  126. args=arguments,
  127. defaults=default_nodes,
  128. kwonlyargs=[
  129. nodes.AssignName(name=arg, parent=argsnode) for arg in kwonlyargs or ()
  130. ],
  131. kw_defaults=kwonlydefault_nodes,
  132. annotations=[],
  133. posonlyargs=[
  134. nodes.AssignName(name=arg, parent=argsnode) for arg in posonlyargs or ()
  135. ],
  136. )
  137. func.postinit(
  138. args=argsnode,
  139. body=[],
  140. doc_node=nodes.Const(value=doc) if doc else None,
  141. )
  142. if args:
  143. register_arguments(func)
  144. return func
  145. def build_from_import(fromname: str, names: list[str]) -> nodes.ImportFrom:
  146. """create and initialize an astroid ImportFrom import statement"""
  147. return nodes.ImportFrom(fromname, [(name, None) for name in names])
  148. def register_arguments(func: nodes.FunctionDef, args: list | None = None) -> None:
  149. """add given arguments to local
  150. args is a list that may contains nested lists
  151. (i.e. def func(a, (b, c, d)): ...)
  152. """
  153. # If no args are passed in, get the args from the function.
  154. if args is None:
  155. if func.args.vararg:
  156. func.set_local(func.args.vararg, func.args)
  157. if func.args.kwarg:
  158. func.set_local(func.args.kwarg, func.args)
  159. args = func.args.args
  160. # If the function has no args, there is nothing left to do.
  161. if args is None:
  162. return
  163. for arg in args:
  164. if isinstance(arg, nodes.AssignName):
  165. func.set_local(arg.name, arg)
  166. else:
  167. register_arguments(func, arg.elts)
  168. def object_build_class(
  169. node: nodes.Module | nodes.ClassDef, member: type, localname: str
  170. ) -> nodes.ClassDef:
  171. """create astroid for a living class object"""
  172. basenames = [base.__name__ for base in member.__bases__]
  173. return _base_class_object_build(node, member, basenames, localname=localname)
  174. def _get_args_info_from_callable(
  175. member: _FunctionTypes,
  176. ) -> tuple[list[str], list[str], list[Any], list[str], list[Any]]:
  177. """Returns args, posonlyargs, defaults, kwonlyargs.
  178. :note: currently ignores the return annotation.
  179. """
  180. signature = inspect.signature(member)
  181. args: list[str] = []
  182. defaults: list[Any] = []
  183. posonlyargs: list[str] = []
  184. kwonlyargs: list[str] = []
  185. kwonlydefaults: list[Any] = []
  186. for param_name, param in signature.parameters.items():
  187. if param.kind == inspect.Parameter.POSITIONAL_ONLY:
  188. posonlyargs.append(param_name)
  189. elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
  190. args.append(param_name)
  191. elif param.kind == inspect.Parameter.VAR_POSITIONAL:
  192. args.append(param_name)
  193. elif param.kind == inspect.Parameter.VAR_KEYWORD:
  194. args.append(param_name)
  195. elif param.kind == inspect.Parameter.KEYWORD_ONLY:
  196. kwonlyargs.append(param_name)
  197. if param.default is not inspect.Parameter.empty:
  198. kwonlydefaults.append(param.default)
  199. continue
  200. if param.default is not inspect.Parameter.empty:
  201. defaults.append(param.default)
  202. return args, posonlyargs, defaults, kwonlyargs, kwonlydefaults
  203. def object_build_function(
  204. node: nodes.Module | nodes.ClassDef, member: _FunctionTypes, localname: str
  205. ) -> None:
  206. """create astroid for a living function object"""
  207. (
  208. args,
  209. posonlyargs,
  210. defaults,
  211. kwonlyargs,
  212. kwonly_defaults,
  213. ) = _get_args_info_from_callable(member)
  214. func = build_function(
  215. getattr(member, "__name__", None) or localname,
  216. args,
  217. posonlyargs,
  218. defaults,
  219. member.__doc__,
  220. kwonlyargs=kwonlyargs,
  221. kwonlydefaults=kwonly_defaults,
  222. )
  223. node.add_local_node(func, localname)
  224. def object_build_datadescriptor(
  225. node: nodes.Module | nodes.ClassDef, member: type, name: str
  226. ) -> nodes.ClassDef:
  227. """create astroid for a living data descriptor object"""
  228. return _base_class_object_build(node, member, [], name)
  229. def object_build_methoddescriptor(
  230. node: nodes.Module | nodes.ClassDef,
  231. member: _FunctionTypes,
  232. localname: str,
  233. ) -> None:
  234. """create astroid for a living method descriptor object"""
  235. # FIXME get arguments ?
  236. func = build_function(
  237. getattr(member, "__name__", None) or localname, doc=member.__doc__
  238. )
  239. node.add_local_node(func, localname)
  240. _add_dunder_class(func, member)
  241. def _base_class_object_build(
  242. node: nodes.Module | nodes.ClassDef,
  243. member: type,
  244. basenames: list[str],
  245. name: str | None = None,
  246. localname: str | None = None,
  247. ) -> nodes.ClassDef:
  248. """create astroid for a living class object, with a given set of base names
  249. (e.g. ancestors)
  250. """
  251. class_name = name or getattr(member, "__name__", None) or localname
  252. assert isinstance(class_name, str)
  253. klass = build_class(
  254. class_name,
  255. basenames,
  256. member.__doc__,
  257. )
  258. klass._newstyle = isinstance(member, type)
  259. node.add_local_node(klass, localname)
  260. try:
  261. # limit the instantiation trick since it's too dangerous
  262. # (such as infinite test execution...)
  263. # this at least resolves common case such as Exception.args,
  264. # OSError.errno
  265. if issubclass(member, Exception):
  266. instdict = member().__dict__
  267. else:
  268. raise TypeError
  269. except TypeError:
  270. pass
  271. else:
  272. for item_name, obj in instdict.items():
  273. valnode = nodes.EmptyNode()
  274. valnode.object = obj
  275. valnode.parent = klass
  276. valnode.lineno = 1
  277. klass.instance_attrs[item_name] = [valnode]
  278. return klass
  279. def _build_from_function(
  280. node: nodes.Module | nodes.ClassDef,
  281. name: str,
  282. member: _FunctionTypes,
  283. module: types.ModuleType,
  284. ) -> None:
  285. # verify this is not an imported function
  286. try:
  287. code = member.__code__ # type: ignore[union-attr]
  288. except AttributeError:
  289. # Some implementations don't provide the code object,
  290. # such as Jython.
  291. code = None
  292. filename = getattr(code, "co_filename", None)
  293. if filename is None:
  294. assert isinstance(member, object)
  295. object_build_methoddescriptor(node, member, name)
  296. elif filename != getattr(module, "__file__", None):
  297. attach_dummy_node(node, name, member)
  298. else:
  299. object_build_function(node, member, name)
  300. def _safe_has_attribute(obj, member: str) -> bool:
  301. """Required because unexpected RunTimeError can be raised.
  302. See https://github.com/PyCQA/astroid/issues/1958
  303. """
  304. try:
  305. return hasattr(obj, member)
  306. except Exception: # pylint: disable=broad-except
  307. return False
  308. class InspectBuilder:
  309. """class for building nodes from living object
  310. this is actually a really minimal representation, including only Module,
  311. FunctionDef and ClassDef nodes and some others as guessed.
  312. """
  313. def __init__(self, manager_instance: AstroidManager | None = None) -> None:
  314. self._manager = manager_instance or AstroidManager()
  315. self._done: dict[types.ModuleType | type, nodes.Module | nodes.ClassDef] = {}
  316. self._module: types.ModuleType
  317. def inspect_build(
  318. self,
  319. module: types.ModuleType,
  320. modname: str | None = None,
  321. path: str | None = None,
  322. ) -> nodes.Module:
  323. """build astroid from a living module (i.e. using inspect)
  324. this is used when there is no python source code available (either
  325. because it's a built-in module or because the .py is not available)
  326. """
  327. self._module = module
  328. if modname is None:
  329. modname = module.__name__
  330. try:
  331. node = build_module(modname, module.__doc__)
  332. except AttributeError:
  333. # in jython, java modules have no __doc__ (see #109562)
  334. node = build_module(modname)
  335. if path is None:
  336. node.path = node.file = path
  337. else:
  338. node.path = [os.path.abspath(path)]
  339. node.file = node.path[0]
  340. node.name = modname
  341. self._manager.cache_module(node)
  342. node.package = hasattr(module, "__path__")
  343. self._done = {}
  344. self.object_build(node, module)
  345. return node
  346. def object_build(
  347. self, node: nodes.Module | nodes.ClassDef, obj: types.ModuleType | type
  348. ) -> None:
  349. """recursive method which create a partial ast from real objects
  350. (only function, class, and method are handled)
  351. """
  352. if obj in self._done:
  353. return None
  354. self._done[obj] = node
  355. for name in dir(obj):
  356. # inspect.ismethod() and inspect.isbuiltin() in PyPy return
  357. # the opposite of what they do in CPython for __class_getitem__.
  358. pypy__class_getitem__ = IS_PYPY and name == "__class_getitem__"
  359. try:
  360. with warnings.catch_warnings():
  361. warnings.simplefilter("ignore")
  362. member = getattr(obj, name)
  363. except AttributeError:
  364. # damned ExtensionClass.Base, I know you're there !
  365. attach_dummy_node(node, name)
  366. continue
  367. if inspect.ismethod(member) and not pypy__class_getitem__:
  368. member = member.__func__
  369. if inspect.isfunction(member):
  370. _build_from_function(node, name, member, self._module)
  371. elif inspect.isbuiltin(member) or pypy__class_getitem__:
  372. if self.imported_member(node, member, name):
  373. continue
  374. object_build_methoddescriptor(node, member, name)
  375. elif inspect.isclass(member):
  376. if self.imported_member(node, member, name):
  377. continue
  378. if member in self._done:
  379. class_node = self._done[member]
  380. assert isinstance(class_node, nodes.ClassDef)
  381. if class_node not in node.locals.get(name, ()):
  382. node.add_local_node(class_node, name)
  383. else:
  384. class_node = object_build_class(node, member, name)
  385. # recursion
  386. self.object_build(class_node, member)
  387. if name == "__class__" and class_node.parent is None:
  388. class_node.parent = self._done[self._module]
  389. elif inspect.ismethoddescriptor(member):
  390. object_build_methoddescriptor(node, member, name)
  391. elif inspect.isdatadescriptor(member):
  392. object_build_datadescriptor(node, member, name)
  393. elif isinstance(member, _CONSTANTS):
  394. attach_const_node(node, name, member)
  395. elif inspect.isroutine(member):
  396. # This should be called for Jython, where some builtin
  397. # methods aren't caught by isbuiltin branch.
  398. _build_from_function(node, name, member, self._module)
  399. elif _safe_has_attribute(member, "__all__"):
  400. module = build_module(name)
  401. _attach_local_node(node, module, name)
  402. # recursion
  403. self.object_build(module, member)
  404. else:
  405. # create an empty node so that the name is actually defined
  406. attach_dummy_node(node, name, member)
  407. return None
  408. def imported_member(self, node, member, name: str) -> bool:
  409. """verify this is not an imported class or handle it"""
  410. # /!\ some classes like ExtensionClass doesn't have a __module__
  411. # attribute ! Also, this may trigger an exception on badly built module
  412. # (see http://www.logilab.org/ticket/57299 for instance)
  413. try:
  414. modname = getattr(member, "__module__", None)
  415. except TypeError:
  416. modname = None
  417. if modname is None:
  418. if name in {"__new__", "__subclasshook__"}:
  419. # Python 2.5.1 (r251:54863, Sep 1 2010, 22:03:14)
  420. # >>> print object.__new__.__module__
  421. # None
  422. modname = builtins.__name__
  423. else:
  424. attach_dummy_node(node, name, member)
  425. return True
  426. # On PyPy during bootstrapping we infer _io while _module is
  427. # builtins. In CPython _io names itself io, see http://bugs.python.org/issue18602
  428. # Therefore, this basically checks whether we are not in PyPy.
  429. if modname == "_io" and not self._module.__name__ == "builtins":
  430. return False
  431. real_name = {"gtk": "gtk_gtk"}.get(modname, modname)
  432. if real_name != self._module.__name__:
  433. # check if it sounds valid and then add an import node, else use a
  434. # dummy node
  435. try:
  436. with redirect_stderr(io.StringIO()) as stderr, redirect_stdout(
  437. io.StringIO()
  438. ) as stdout:
  439. getattr(sys.modules[modname], name)
  440. stderr_value = stderr.getvalue()
  441. if stderr_value:
  442. logger.error(
  443. "Captured stderr while getting %s from %s:\n%s",
  444. name,
  445. sys.modules[modname],
  446. stderr_value,
  447. )
  448. stdout_value = stdout.getvalue()
  449. if stdout_value:
  450. logger.info(
  451. "Captured stdout while getting %s from %s:\n%s",
  452. name,
  453. sys.modules[modname],
  454. stdout_value,
  455. )
  456. except (KeyError, AttributeError):
  457. attach_dummy_node(node, name, member)
  458. else:
  459. attach_import_node(node, modname, name)
  460. return True
  461. return False
  462. # astroid bootstrapping ######################################################
  463. _CONST_PROXY: dict[type, nodes.ClassDef] = {}
  464. def _set_proxied(const) -> nodes.ClassDef:
  465. # TODO : find a nicer way to handle this situation;
  466. return _CONST_PROXY[const.value.__class__]
  467. def _astroid_bootstrapping() -> None:
  468. """astroid bootstrapping the builtins module"""
  469. # this boot strapping is necessary since we need the Const nodes to
  470. # inspect_build builtins, and then we can proxy Const
  471. builder = InspectBuilder()
  472. astroid_builtin = builder.inspect_build(builtins)
  473. for cls, node_cls in node_classes.CONST_CLS.items():
  474. if cls is TYPE_NONE:
  475. proxy = build_class("NoneType")
  476. proxy.parent = astroid_builtin
  477. elif cls is TYPE_NOTIMPLEMENTED:
  478. proxy = build_class("NotImplementedType")
  479. proxy.parent = astroid_builtin
  480. elif cls is TYPE_ELLIPSIS:
  481. proxy = build_class("Ellipsis")
  482. proxy.parent = astroid_builtin
  483. else:
  484. proxy = astroid_builtin.getattr(cls.__name__)[0]
  485. assert isinstance(proxy, nodes.ClassDef)
  486. if cls in (dict, list, set, tuple):
  487. node_cls._proxied = proxy
  488. else:
  489. _CONST_PROXY[cls] = proxy
  490. # Set the builtin module as parent for some builtins.
  491. nodes.Const._proxied = property(_set_proxied)
  492. _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__)
  493. _GeneratorType.parent = astroid_builtin
  494. generator_doc_node = (
  495. nodes.Const(value=types.GeneratorType.__doc__)
  496. if types.GeneratorType.__doc__
  497. else None
  498. )
  499. _GeneratorType.postinit(
  500. bases=[],
  501. body=[],
  502. decorators=None,
  503. doc_node=generator_doc_node,
  504. )
  505. bases.Generator._proxied = _GeneratorType
  506. builder.object_build(bases.Generator._proxied, types.GeneratorType)
  507. if hasattr(types, "AsyncGeneratorType"):
  508. _AsyncGeneratorType = nodes.ClassDef(types.AsyncGeneratorType.__name__)
  509. _AsyncGeneratorType.parent = astroid_builtin
  510. async_generator_doc_node = (
  511. nodes.Const(value=types.AsyncGeneratorType.__doc__)
  512. if types.AsyncGeneratorType.__doc__
  513. else None
  514. )
  515. _AsyncGeneratorType.postinit(
  516. bases=[],
  517. body=[],
  518. decorators=None,
  519. doc_node=async_generator_doc_node,
  520. )
  521. bases.AsyncGenerator._proxied = _AsyncGeneratorType
  522. builder.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType)
  523. if hasattr(types, "UnionType"):
  524. _UnionTypeType = nodes.ClassDef(types.UnionType.__name__)
  525. _UnionTypeType.parent = astroid_builtin
  526. union_type_doc_node = (
  527. nodes.Const(value=types.UnionType.__doc__)
  528. if types.UnionType.__doc__
  529. else None
  530. )
  531. _UnionTypeType.postinit(
  532. bases=[],
  533. body=[],
  534. decorators=None,
  535. doc_node=union_type_doc_node,
  536. )
  537. bases.UnionType._proxied = _UnionTypeType
  538. builder.object_build(bases.UnionType._proxied, types.UnionType)
  539. builtin_types = (
  540. types.GetSetDescriptorType,
  541. types.GeneratorType,
  542. types.MemberDescriptorType,
  543. TYPE_NONE,
  544. TYPE_NOTIMPLEMENTED,
  545. types.FunctionType,
  546. types.MethodType,
  547. types.BuiltinFunctionType,
  548. types.ModuleType,
  549. types.TracebackType,
  550. )
  551. for _type in builtin_types:
  552. if _type.__name__ not in astroid_builtin:
  553. klass = nodes.ClassDef(_type.__name__)
  554. klass.parent = astroid_builtin
  555. klass.postinit(
  556. bases=[],
  557. body=[],
  558. decorators=None,
  559. doc_node=nodes.Const(value=_type.__doc__) if _type.__doc__ else None,
  560. )
  561. builder.object_build(klass, _type)
  562. astroid_builtin[_type.__name__] = klass
  563. _astroid_bootstrapping()