Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/raw_building.py: 16%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
5"""this module contains a set of functions to create astroid trees from scratch
6(build_* functions) or from living object (object_build_* functions)
7"""
9from __future__ import annotations
11import builtins
12import inspect
13import io
14import logging
15import os
16import sys
17import types
18import warnings
19from collections.abc import Iterable
20from contextlib import redirect_stderr, redirect_stdout
21from typing import TYPE_CHECKING, Any
23from astroid import bases, nodes
24from astroid.const import _EMPTY_OBJECT_MARKER, IS_PYPY
25from astroid.nodes import node_classes
27if TYPE_CHECKING:
28 from astroid.manager import AstroidManager
30logger = logging.getLogger(__name__)
33_FunctionTypes = (
34 types.FunctionType
35 | types.MethodType
36 | types.BuiltinFunctionType
37 | types.WrapperDescriptorType
38 | types.MethodDescriptorType
39 | types.ClassMethodDescriptorType
40)
42TYPE_NONE = type(None)
43TYPE_NOTIMPLEMENTED = type(NotImplemented)
44TYPE_ELLIPSIS = type(...)
47def _attach_local_node(parent, node, name: str) -> None:
48 node.name = name # needed by add_local_node
49 parent.add_local_node(node)
52def _add_dunder_class(func, parent: nodes.NodeNG, member) -> None:
53 """Add a __class__ member to the given func node, if we can determine it."""
54 python_cls = member.__class__
55 cls_name = getattr(python_cls, "__name__", None)
56 if not cls_name:
57 return
58 cls_bases = [ancestor.__name__ for ancestor in python_cls.__bases__]
59 doc = python_cls.__doc__ if isinstance(python_cls.__doc__, str) else None
60 ast_klass = build_class(cls_name, parent, cls_bases, doc)
61 func.instance_attrs["__class__"] = [ast_klass]
64def build_dummy(runtime_object) -> nodes.EmptyNode:
65 enode = nodes.EmptyNode()
66 enode.object = runtime_object
67 return enode
70def attach_dummy_node(node, name: str, runtime_object=_EMPTY_OBJECT_MARKER) -> None:
71 """create a dummy node and register it in the locals of the given
72 node with the specified name
73 """
74 _attach_local_node(node, build_dummy(runtime_object), name)
77def attach_const_node(node, name: str, value) -> None:
78 """create a Const node and register it in the locals of the given
79 node with the specified name
80 """
81 # Special case: __hash__ = None overrides ObjectModel for unhashable types.
82 # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
83 if name == "__hash__" and value is None:
84 _attach_local_node(node, nodes.const_factory(value), name)
85 elif name not in node.special_attributes:
86 _attach_local_node(node, nodes.const_factory(value), name)
89def attach_import_node(node, modname: str, membername: str) -> None:
90 """create a ImportFrom node and register it in the locals of the given
91 node with the specified name
92 """
93 from_node = nodes.ImportFrom(modname, [(membername, None)])
94 _attach_local_node(node, from_node, membername)
97def build_module(name: str, doc: str | None = None) -> nodes.Module:
98 """create and initialize an astroid Module node"""
99 node = nodes.Module(name, pure_python=False, package=False)
100 node.postinit(
101 body=[],
102 doc_node=nodes.Const(value=doc) if doc else None,
103 )
104 return node
107def build_class(
108 name: str,
109 parent: nodes.NodeNG,
110 basenames: Iterable[str] = (),
111 doc: str | None = None,
112) -> nodes.ClassDef:
113 """Create and initialize an astroid ClassDef node."""
114 node = nodes.ClassDef(
115 name,
116 lineno=0,
117 col_offset=0,
118 end_lineno=0,
119 end_col_offset=0,
120 parent=parent,
121 )
122 node.postinit(
123 bases=[
124 nodes.Name(
125 name=base,
126 lineno=0,
127 col_offset=0,
128 parent=node,
129 end_lineno=None,
130 end_col_offset=None,
131 )
132 for base in basenames
133 ],
134 body=[],
135 decorators=None,
136 doc_node=nodes.Const(value=doc) if doc else None,
137 )
138 return node
141def build_function(
142 name: str,
143 parent: nodes.NodeNG,
144 args: list[str] | None = None,
145 posonlyargs: list[str] | None = None,
146 defaults: list[Any] | None = None,
147 doc: str | None = None,
148 kwonlyargs: list[str] | None = None,
149 kwonlydefaults: list[Any] | None = None,
150) -> nodes.FunctionDef:
151 """create and initialize an astroid FunctionDef node"""
152 # first argument is now a list of decorators
153 func = nodes.FunctionDef(
154 name,
155 lineno=0,
156 col_offset=0,
157 parent=parent,
158 end_col_offset=0,
159 end_lineno=0,
160 )
161 argsnode = nodes.Arguments(parent=func, vararg=None, kwarg=None)
163 # If args is None we don't have any information about the signature
164 # (in contrast to when there are no arguments and args == []). We pass
165 # this to the builder to indicate this.
166 if args is not None:
167 # We set the lineno and col_offset to 0 because we don't have any
168 # information about the location of the function definition.
169 arguments = [
170 nodes.AssignName(
171 name=arg,
172 parent=argsnode,
173 lineno=0,
174 col_offset=0,
175 end_lineno=None,
176 end_col_offset=None,
177 )
178 for arg in args
179 ]
180 else:
181 arguments = None
183 default_nodes: list[nodes.NodeNG] | None
184 if defaults is None:
185 default_nodes = None
186 else:
187 default_nodes = []
188 for default in defaults:
189 default_node = nodes.const_factory(default)
190 default_node.parent = argsnode
191 default_nodes.append(default_node)
193 kwonlydefault_nodes: list[nodes.NodeNG | None] | None
194 if kwonlydefaults is None:
195 kwonlydefault_nodes = None
196 else:
197 kwonlydefault_nodes = []
198 for kwonlydefault in kwonlydefaults:
199 kwonlydefault_node = nodes.const_factory(kwonlydefault)
200 kwonlydefault_node.parent = argsnode
201 kwonlydefault_nodes.append(kwonlydefault_node)
203 # We set the lineno and col_offset to 0 because we don't have any
204 # information about the location of the kwonly and posonlyargs.
205 argsnode.postinit(
206 args=arguments,
207 defaults=default_nodes,
208 kwonlyargs=[
209 nodes.AssignName(
210 name=arg,
211 parent=argsnode,
212 lineno=0,
213 col_offset=0,
214 end_lineno=None,
215 end_col_offset=None,
216 )
217 for arg in kwonlyargs or ()
218 ],
219 kw_defaults=kwonlydefault_nodes,
220 annotations=[],
221 posonlyargs=[
222 nodes.AssignName(
223 name=arg,
224 parent=argsnode,
225 lineno=0,
226 col_offset=0,
227 end_lineno=None,
228 end_col_offset=None,
229 )
230 for arg in posonlyargs or ()
231 ],
232 kwonlyargs_annotations=[],
233 posonlyargs_annotations=[],
234 )
235 func.postinit(
236 args=argsnode,
237 body=[],
238 doc_node=nodes.Const(value=doc) if doc else None,
239 )
240 if args:
241 register_arguments(func)
242 return func
245def build_from_import(fromname: str, names: list[str]) -> nodes.ImportFrom:
246 """create and initialize an astroid ImportFrom import statement"""
247 return nodes.ImportFrom(fromname, [(name, None) for name in names])
250def register_arguments(func: nodes.FunctionDef, args: list | None = None) -> None:
251 """add given arguments to local
253 args is a list that may contains nested lists
254 (i.e. def func(a, (b, c, d)): ...)
255 """
256 # If no args are passed in, get the args from the function.
257 if args is None:
258 if func.args.vararg:
259 func.set_local(func.args.vararg, func.args)
260 if func.args.kwarg:
261 func.set_local(func.args.kwarg, func.args)
262 args = func.args.args
263 # If the function has no args, there is nothing left to do.
264 if args is None:
265 return
266 for arg in args:
267 if isinstance(arg, nodes.AssignName):
268 func.set_local(arg.name, arg)
269 else:
270 register_arguments(func, arg.elts)
273def object_build_class(
274 node: nodes.Module | nodes.ClassDef, member: type
275) -> nodes.ClassDef:
276 """create astroid for a living class object"""
277 basenames = [base.__name__ for base in member.__bases__]
278 return _base_class_object_build(node, member, basenames)
281def _get_args_info_from_callable(
282 member: _FunctionTypes,
283) -> tuple[list[str], list[str], list[Any], list[str], list[Any]]:
284 """Returns args, posonlyargs, defaults, kwonlyargs.
286 :note: currently ignores the return annotation.
287 """
288 signature = inspect.signature(member)
289 args: list[str] = []
290 defaults: list[Any] = []
291 posonlyargs: list[str] = []
292 kwonlyargs: list[str] = []
293 kwonlydefaults: list[Any] = []
295 for param_name, param in signature.parameters.items():
296 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
297 posonlyargs.append(param_name)
298 elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
299 args.append(param_name)
300 elif param.kind == inspect.Parameter.VAR_POSITIONAL:
301 args.append(param_name)
302 elif param.kind == inspect.Parameter.VAR_KEYWORD:
303 args.append(param_name)
304 elif param.kind == inspect.Parameter.KEYWORD_ONLY:
305 kwonlyargs.append(param_name)
306 if param.default is not inspect.Parameter.empty:
307 kwonlydefaults.append(param.default)
308 continue
309 if param.default is not inspect.Parameter.empty:
310 defaults.append(param.default)
312 return args, posonlyargs, defaults, kwonlyargs, kwonlydefaults
315def object_build_function(
316 node: nodes.Module | nodes.ClassDef, member: _FunctionTypes
317) -> nodes.FunctionDef:
318 """create astroid for a living function object"""
319 (
320 args,
321 posonlyargs,
322 defaults,
323 kwonlyargs,
324 kwonly_defaults,
325 ) = _get_args_info_from_callable(member)
327 return build_function(
328 getattr(member, "__name__", "<no-name>"),
329 node,
330 args,
331 posonlyargs,
332 defaults,
333 member.__doc__ if isinstance(member.__doc__, str) else None,
334 kwonlyargs=kwonlyargs,
335 kwonlydefaults=kwonly_defaults,
336 )
339def object_build_datadescriptor(
340 node: nodes.Module | nodes.ClassDef, member: type
341) -> nodes.ClassDef:
342 """create astroid for a living data descriptor object"""
343 return _base_class_object_build(node, member, [])
346def object_build_methoddescriptor(
347 node: nodes.Module | nodes.ClassDef,
348 member: _FunctionTypes,
349) -> nodes.FunctionDef:
350 """create astroid for a living method descriptor object"""
351 # FIXME get arguments ?
352 name = getattr(member, "__name__", "<no-name>")
353 func = build_function(name, node, doc=member.__doc__)
354 _add_dunder_class(func, node, member)
355 return func
358def _base_class_object_build(
359 node: nodes.Module | nodes.ClassDef,
360 member: type,
361 basenames: list[str],
362) -> nodes.ClassDef:
363 """create astroid for a living class object, with a given set of base names
364 (e.g. ancestors)
365 """
366 name = getattr(member, "__name__", "<no-name>")
367 doc = member.__doc__ if isinstance(member.__doc__, str) else None
368 klass = build_class(name, node, basenames, doc)
369 klass._newstyle = isinstance(member, type)
370 try:
371 # limit the instantiation trick since it's too dangerous
372 # (such as infinite test execution...)
373 # this at least resolves common case such as Exception.args,
374 # OSError.errno
375 if issubclass(member, Exception):
376 member_object = member()
377 if hasattr(member_object, "__dict__"):
378 instdict = member_object.__dict__
379 else:
380 raise TypeError
381 else:
382 raise TypeError
383 except TypeError:
384 pass
385 else:
386 for item_name, obj in instdict.items():
387 valnode = nodes.EmptyNode()
388 valnode.object = obj
389 valnode.parent = klass
390 valnode.lineno = 1
391 klass.instance_attrs[item_name] = [valnode]
392 return klass
395def _build_from_function(
396 node: nodes.Module | nodes.ClassDef,
397 member: _FunctionTypes,
398 module: types.ModuleType,
399) -> nodes.FunctionDef | nodes.EmptyNode:
400 # verify this is not an imported function
401 try:
402 code = member.__code__ # type: ignore[union-attr]
403 except AttributeError:
404 # Some implementations don't provide the code object,
405 # such as Jython.
406 code = None
407 filename = getattr(code, "co_filename", None)
408 if filename is None:
409 return object_build_methoddescriptor(node, member)
410 if filename == getattr(module, "__file__", None):
411 return object_build_function(node, member)
412 return build_dummy(member)
415def _safe_has_attribute(obj, member: str) -> bool:
416 """Required because unexpected RunTimeError can be raised.
418 See https://github.com/pylint-dev/astroid/issues/1958
419 """
420 try:
421 return hasattr(obj, member)
422 except Exception: # pylint: disable=broad-except
423 return False
426class InspectBuilder:
427 """class for building nodes from living object
429 this is actually a really minimal representation, including only Module,
430 FunctionDef and ClassDef nodes and some others as guessed.
431 """
433 bootstrapped: bool = False
435 def __init__(self, manager_instance: AstroidManager) -> None:
436 self._manager = manager_instance
437 self._done: dict[types.ModuleType | type, nodes.Module | nodes.ClassDef] = {}
438 self._module: types.ModuleType
440 def inspect_build(
441 self,
442 module: types.ModuleType,
443 modname: str | None = None,
444 path: str | None = None,
445 ) -> nodes.Module:
446 """build astroid from a living module (i.e. using inspect)
447 this is used when there is no python source code available (either
448 because it's a built-in module or because the .py is not available)
449 """
450 self._module = module
451 if modname is None:
452 modname = module.__name__
453 try:
454 node = build_module(modname, module.__doc__)
455 except AttributeError:
456 # in jython, java modules have no __doc__ (see #109562)
457 node = build_module(modname)
458 if path is None:
459 node.path = node.file = path
460 else:
461 node.path = [os.path.abspath(path)]
462 node.file = node.path[0]
463 node.name = modname
464 self._manager.cache_module(node)
465 node.package = hasattr(module, "__path__")
466 self._done = {}
467 self.object_build(node, module)
468 return node
470 def object_build(
471 self, node: nodes.Module | nodes.ClassDef, obj: types.ModuleType | type
472 ) -> None:
473 """recursive method which create a partial ast from real objects
474 (only function, class, and method are handled)
475 """
476 if obj in self._done:
477 return None
478 self._done[obj] = node
479 for alias in dir(obj):
480 # inspect.ismethod() and inspect.isbuiltin() in PyPy return
481 # the opposite of what they do in CPython for __class_getitem__.
482 pypy__class_getitem__ = IS_PYPY and alias == "__class_getitem__"
483 try:
484 with warnings.catch_warnings():
485 warnings.simplefilter("ignore")
486 member = getattr(obj, alias)
487 except AttributeError:
488 # damned ExtensionClass.Base, I know you're there !
489 attach_dummy_node(node, alias)
490 continue
491 if inspect.ismethod(member) and not pypy__class_getitem__:
492 member = member.__func__
493 if inspect.isfunction(member):
494 child = _build_from_function(node, member, self._module)
495 elif inspect.isbuiltin(member) or pypy__class_getitem__:
496 if self.imported_member(node, member, alias):
497 continue
498 child = object_build_methoddescriptor(node, member)
499 elif inspect.isclass(member):
500 if self.imported_member(node, member, alias):
501 continue
502 if member in self._done:
503 child = self._done[member]
504 assert isinstance(child, nodes.ClassDef)
505 else:
506 child = object_build_class(node, member)
507 # recursion
508 self.object_build(child, member)
509 elif inspect.ismethoddescriptor(member):
510 child: nodes.NodeNG = object_build_methoddescriptor(node, member)
511 elif inspect.isdatadescriptor(member):
512 child = object_build_datadescriptor(node, member)
513 elif isinstance(member, tuple(node_classes.CONST_CLS)):
514 # Special case: __hash__ = None overrides ObjectModel for unhashable types.
515 # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
516 if alias in node.special_attributes and not (
517 alias == "__hash__" and member is None
518 ):
519 continue
520 child = nodes.const_factory(member)
521 elif inspect.isroutine(member):
522 # This should be called for Jython, where some builtin
523 # methods aren't caught by isbuiltin branch.
524 child = _build_from_function(node, member, self._module)
525 elif _safe_has_attribute(member, "__all__"):
526 child: nodes.NodeNG = build_module(alias)
527 # recursion
528 self.object_build(child, member)
529 else:
530 # create an empty node so that the name is actually defined
531 child: nodes.NodeNG = build_dummy(member)
532 if child not in node.locals.get(alias, ()):
533 node.add_local_node(child, alias)
534 return None
536 def imported_member(self, node, member, name: str) -> bool:
537 """verify this is not an imported class or handle it"""
538 # /!\ some classes like ExtensionClass doesn't have a __module__
539 # attribute ! Also, this may trigger an exception on badly built module
540 # (see http://www.logilab.org/ticket/57299 for instance)
541 try:
542 modname = getattr(member, "__module__", None)
543 except TypeError:
544 modname = None
545 if modname is None:
546 if name in {"__new__", "__subclasshook__"}:
547 # Python 2.5.1 (r251:54863, Sep 1 2010, 22:03:14)
548 # >>> print object.__new__.__module__
549 # None
550 modname = builtins.__name__
551 else:
552 attach_dummy_node(node, name, member)
553 return True
555 # On PyPy during bootstrapping we infer _io while _module is
556 # builtins. In CPython _io names itself io, see http://bugs.python.org/issue18602
557 # Therefore, this basically checks whether we are not in PyPy.
558 if modname == "_io" and not self._module.__name__ == "builtins":
559 return False
561 real_name = {"gtk": "gtk_gtk"}.get(modname, modname)
563 if real_name != self._module.__name__:
564 # check if it sounds valid and then add an import node, else use a
565 # dummy node
566 try:
567 with (
568 redirect_stderr(io.StringIO()) as stderr,
569 redirect_stdout(io.StringIO()) as stdout,
570 ):
571 getattr(sys.modules[modname], name)
572 stderr_value = stderr.getvalue()
573 if stderr_value:
574 logger.error(
575 "Captured stderr while getting %s from %s:\n%s",
576 name,
577 sys.modules[modname],
578 stderr_value,
579 )
580 stdout_value = stdout.getvalue()
581 if stdout_value:
582 logger.info(
583 "Captured stdout while getting %s from %s:\n%s",
584 name,
585 sys.modules[modname],
586 stdout_value,
587 )
588 except (KeyError, AttributeError):
589 attach_dummy_node(node, name, member)
590 else:
591 attach_import_node(node, modname, name)
592 return True
593 return False
596# astroid bootstrapping ######################################################
598_CONST_PROXY: dict[type, nodes.ClassDef] = {}
601def _set_proxied(const) -> nodes.ClassDef:
602 # TODO : find a nicer way to handle this situation;
603 return _CONST_PROXY[const.value.__class__]
606def _astroid_bootstrapping() -> None:
607 """astroid bootstrapping the builtins module"""
608 # this boot strapping is necessary since we need the Const nodes to
609 # inspect_build builtins, and then we can proxy Const
610 # pylint: disable-next=import-outside-toplevel
611 from astroid.manager import AstroidManager
613 builder = InspectBuilder(AstroidManager())
614 astroid_builtin = builder.inspect_build(builtins)
616 for cls, node_cls in node_classes.CONST_CLS.items():
617 if cls is TYPE_NONE:
618 proxy = build_class("NoneType", astroid_builtin)
619 elif cls is TYPE_NOTIMPLEMENTED:
620 proxy = build_class("NotImplementedType", astroid_builtin)
621 elif cls is TYPE_ELLIPSIS:
622 proxy = build_class("Ellipsis", astroid_builtin)
623 else:
624 proxy = astroid_builtin.getattr(cls.__name__)[0]
625 assert isinstance(proxy, nodes.ClassDef)
626 if cls in (dict, list, set, tuple):
627 node_cls._proxied = proxy
628 else:
629 _CONST_PROXY[cls] = proxy
631 # Set the builtin module as parent for some builtins.
632 nodes.Const._proxied = property(_set_proxied)
634 _GeneratorType = nodes.ClassDef(
635 types.GeneratorType.__name__,
636 lineno=0,
637 col_offset=0,
638 end_lineno=0,
639 end_col_offset=0,
640 parent=astroid_builtin,
641 )
642 astroid_builtin.set_local(_GeneratorType.name, _GeneratorType)
643 generator_doc_node = (
644 nodes.Const(value=types.GeneratorType.__doc__)
645 if types.GeneratorType.__doc__
646 else None
647 )
648 _GeneratorType.postinit(
649 bases=[],
650 body=[],
651 decorators=None,
652 doc_node=generator_doc_node,
653 )
654 bases.Generator._proxied = _GeneratorType
655 builder.object_build(bases.Generator._proxied, types.GeneratorType)
657 if hasattr(types, "AsyncGeneratorType"):
658 _AsyncGeneratorType = nodes.ClassDef(
659 types.AsyncGeneratorType.__name__,
660 lineno=0,
661 col_offset=0,
662 end_lineno=0,
663 end_col_offset=0,
664 parent=astroid_builtin,
665 )
666 astroid_builtin.set_local(_AsyncGeneratorType.name, _AsyncGeneratorType)
667 async_generator_doc_node = (
668 nodes.Const(value=types.AsyncGeneratorType.__doc__)
669 if types.AsyncGeneratorType.__doc__
670 else None
671 )
672 _AsyncGeneratorType.postinit(
673 bases=[],
674 body=[],
675 decorators=None,
676 doc_node=async_generator_doc_node,
677 )
678 bases.AsyncGenerator._proxied = _AsyncGeneratorType
679 builder.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType)
681 if hasattr(types, "UnionType"):
682 _UnionTypeType = nodes.ClassDef(
683 types.UnionType.__name__,
684 lineno=0,
685 col_offset=0,
686 end_lineno=0,
687 end_col_offset=0,
688 parent=astroid_builtin,
689 )
690 union_type_doc_node = (
691 nodes.Const(value=types.UnionType.__doc__)
692 if types.UnionType.__doc__
693 else None
694 )
695 _UnionTypeType.postinit(
696 bases=[],
697 body=[],
698 decorators=None,
699 doc_node=union_type_doc_node,
700 )
701 bases.UnionType._proxied = _UnionTypeType
702 builder.object_build(bases.UnionType._proxied, types.UnionType)
704 builtin_types = (
705 types.GetSetDescriptorType,
706 types.GeneratorType,
707 types.MemberDescriptorType,
708 TYPE_NONE,
709 TYPE_NOTIMPLEMENTED,
710 types.FunctionType,
711 types.MethodType,
712 types.BuiltinFunctionType,
713 types.ModuleType,
714 types.TracebackType,
715 )
716 for _type in builtin_types:
717 if _type.__name__ not in astroid_builtin:
718 klass = nodes.ClassDef(
719 _type.__name__,
720 lineno=0,
721 col_offset=0,
722 end_lineno=0,
723 end_col_offset=0,
724 parent=astroid_builtin,
725 )
726 doc = _type.__doc__ if isinstance(_type.__doc__, str) else None
727 klass.postinit(
728 bases=[],
729 body=[],
730 decorators=None,
731 doc_node=nodes.Const(doc) if doc else None,
732 )
733 builder.object_build(klass, _type)
734 astroid_builtin[_type.__name__] = klass
736 InspectBuilder.bootstrapped = True
738 # pylint: disable-next=import-outside-toplevel
739 from astroid.brain.brain_builtin_inference import on_bootstrap
741 # Instantiates an AstroidBuilder(), which is where
742 # InspectBuilder.bootstrapped is checked, so place after bootstrapped=True.
743 on_bootstrap()