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
4
5"""
6Data object model, as per https://docs.python.org/3/reference/datamodel.html.
7
8This module describes, at least partially, a data object model for some
9of astroid's nodes. The model contains special attributes that nodes such
10as functions, classes, modules etc have, such as __doc__, __class__,
11__module__ etc, being used when doing attribute lookups over nodes.
12
13For instance, inferring `obj.__class__` will first trigger an inference
14of the `obj` variable. If it was successfully inferred, then an attribute
15`__class__ will be looked for in the inferred object. This is the part
16where the data model occurs. The model is attached to those nodes
17and the lookup mechanism will try to see if attributes such as
18`__class__` are defined by the model or not. If they are defined,
19the model will be requested to return the corresponding value of that
20attribute. Thus the model can be viewed as a special part of the lookup
21mechanism.
22"""
23
24from __future__ import annotations
25
26import itertools
27import os
28import types
29from collections.abc import Iterator
30from functools import lru_cache
31from typing import TYPE_CHECKING, Any, Literal
32
33import astroid
34from astroid import bases, nodes, util
35from astroid.context import InferenceContext, copy_context
36from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
37from astroid.manager import AstroidManager
38from astroid.nodes import node_classes
39from astroid.typing import InferenceResult, SuccessfulInferenceResult
40
41if TYPE_CHECKING:
42 from astroid.objects import Property
43
44IMPL_PREFIX = "attr_"
45LEN_OF_IMPL_PREFIX = len(IMPL_PREFIX)
46
47
48def _dunder_dict(instance, attributes):
49 obj = node_classes.Dict(
50 parent=instance,
51 lineno=instance.lineno,
52 col_offset=instance.col_offset,
53 end_lineno=instance.end_lineno,
54 end_col_offset=instance.end_col_offset,
55 )
56
57 # Convert the keys to node strings
58 keys = [
59 node_classes.Const(value=value, parent=obj) for value in list(attributes.keys())
60 ]
61
62 # The original attribute has a list of elements for each key,
63 # but that is not useful for retrieving the special attribute's value.
64 # In this case, we're picking the last value from each list.
65 values = [elem[-1] for elem in attributes.values()]
66
67 obj.postinit(list(zip(keys, values)))
68 return obj
69
70
71def _get_bound_node(model: ObjectModel) -> Any:
72 # TODO: Use isinstance instead of try ... except after _instance has typing
73 try:
74 return model._instance._proxied
75 except AttributeError:
76 return model._instance
77
78
79class ObjectModel:
80 def __init__(self):
81 self._instance = None
82
83 def __repr__(self):
84 import pprint # pylint: disable=import-outside-toplevel
85
86 result = []
87 cname = type(self).__name__
88 string = "%(cname)s(%(fields)s)"
89 alignment = len(cname) + 1
90 for field in sorted(self.attributes()):
91 width = max(80 - len(field) - alignment, 1)
92 try:
93 lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
94 except ValueError:
95 lines = [f"<{type(field).__name__}>"]
96
97 inner = [lines[0]]
98 for line in lines[1:]:
99 inner.append(" " * alignment + line)
100 result.append(field)
101
102 return string % {
103 "cname": cname,
104 "fields": (",\n" + " " * alignment).join(result),
105 }
106
107 def __call__(self, instance):
108 self._instance = instance
109 return self
110
111 def __get__(self, instance, cls=None):
112 # ObjectModel needs to be a descriptor so that just doing
113 # `special_attributes = SomeObjectModel` should be enough in the body of a node.
114 # But at the same time, node.special_attributes should return an object
115 # which can be used for manipulating the special attributes. That's the reason
116 # we pass the instance through which it got accessed to ObjectModel.__call__,
117 # returning itself afterwards, so we can still have access to the
118 # underlying data model and to the instance for which it got accessed.
119 return self(instance)
120
121 def __contains__(self, name) -> bool:
122 return name in self.attributes()
123
124 @lru_cache # noqa
125 def attributes(self) -> list[str]:
126 """Get the attributes which are exported by this object model."""
127 return [o[LEN_OF_IMPL_PREFIX:] for o in dir(self) if o.startswith(IMPL_PREFIX)]
128
129 def lookup(self, name):
130 """Look up the given *name* in the current model.
131
132 It should return an AST or an interpreter object,
133 but if the name is not found, then an AttributeInferenceError will be raised.
134 """
135 if name in self.attributes():
136 return getattr(self, IMPL_PREFIX + name)
137 raise AttributeInferenceError(target=self._instance, attribute=name)
138
139 @property
140 def attr___new__(self) -> bases.BoundMethod:
141 """Calling cls.__new__(type) on an object returns an instance of 'type'."""
142 from astroid import builder # pylint: disable=import-outside-toplevel
143
144 node: nodes.FunctionDef = builder.extract_node(
145 """def __new__(self, cls): return cls()"""
146 )
147 # We set the parent as being the ClassDef of 'object' as that
148 # triggers correct inference as a call to __new__ in bases.py
149 node.parent = AstroidManager().builtins_module["object"]
150
151 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
152
153 @property
154 def attr___init__(self) -> bases.BoundMethod:
155 """Calling cls.__init__() normally returns None."""
156 from astroid import builder # pylint: disable=import-outside-toplevel
157
158 # The *args and **kwargs are necessary not to trigger warnings about missing
159 # or extra parameters for '__init__' methods we don't infer correctly.
160 # This BoundMethod is the fallback value for those.
161 node: nodes.FunctionDef = builder.extract_node(
162 """def __init__(self, *args, **kwargs): return None"""
163 )
164 # We set the parent as being the ClassDef of 'object' as that
165 # is where this method originally comes from
166 node.parent = AstroidManager().builtins_module["object"]
167
168 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
169
170 # Base object attributes that return Unknown as fallback placeholders.
171 @property
172 def attr___ne__(self):
173 return node_classes.Unknown(parent=self._instance)
174
175 attr___class__ = attr___ne__
176 attr___delattr__ = attr___ne__
177 attr___dir__ = attr___ne__
178 attr___doc__ = attr___ne__
179 attr___eq__ = attr___ne__
180 attr___format__ = attr___ne__
181 attr___ge__ = attr___ne__
182 attr___getattribute__ = attr___ne__
183 attr___getstate__ = attr___ne__
184 attr___gt__ = attr___ne__
185 attr___hash__ = attr___ne__
186 attr___init_subclass__ = attr___ne__
187 attr___le__ = attr___ne__
188 attr___lt__ = attr___ne__
189 attr___reduce__ = attr___ne__
190 attr___reduce_ex__ = attr___ne__
191 attr___repr__ = attr___ne__
192 attr___setattr__ = attr___ne__
193 attr___sizeof__ = attr___ne__
194 attr___str__ = attr___ne__
195 attr___subclasshook__ = attr___ne__
196
197
198class ModuleModel(ObjectModel):
199 def _builtins(self):
200 builtins_ast_module = AstroidManager().builtins_module
201 return builtins_ast_module.special_attributes.lookup("__dict__")
202
203 @property
204 def attr_builtins(self):
205 return self._builtins()
206
207 @property
208 def attr___path__(self):
209 if not self._instance.package:
210 raise AttributeInferenceError(target=self._instance, attribute="__path__")
211
212 path_objs = [
213 node_classes.Const(
214 value=(
215 path if not path.endswith("__init__.py") else os.path.dirname(path)
216 ),
217 parent=self._instance,
218 )
219 for path in self._instance.path
220 ]
221
222 container = node_classes.List(parent=self._instance)
223 container.postinit(path_objs)
224
225 return container
226
227 @property
228 def attr___name__(self):
229 return node_classes.Const(value=self._instance.name, parent=self._instance)
230
231 @property
232 def attr___doc__(self):
233 return node_classes.Const(
234 value=getattr(self._instance.doc_node, "value", None),
235 parent=self._instance,
236 )
237
238 @property
239 def attr___file__(self):
240 return node_classes.Const(value=self._instance.file, parent=self._instance)
241
242 @property
243 def attr___dict__(self):
244 return _dunder_dict(self._instance, self._instance.globals)
245
246 @property
247 def attr___package__(self):
248 if not self._instance.package:
249 value = ""
250 else:
251 value = self._instance.name
252
253 return node_classes.Const(value=value, parent=self._instance)
254
255 # These are related to the Python 3 implementation of the
256 # import system,
257 # https://docs.python.org/3/reference/import.html#import-related-module-attributes
258
259 @property
260 def attr___spec__(self):
261 # No handling for now.
262 return node_classes.Unknown(parent=self._instance)
263
264 @property
265 def attr___loader__(self):
266 # No handling for now.
267 return node_classes.Unknown(parent=self._instance)
268
269 @property
270 def attr___cached__(self):
271 # No handling for now.
272 return node_classes.Unknown(parent=self._instance)
273
274
275class FunctionModel(ObjectModel):
276 def _is_builtin_func(self) -> bool:
277 func = self._instance
278 return isinstance(func.parent, nodes.Module) and not func.root().pure_python
279
280 @property
281 def attr___name__(self):
282 return node_classes.Const(value=self._instance.name, parent=self._instance)
283
284 @property
285 def attr___doc__(self):
286 return node_classes.Const(
287 value=getattr(self._instance.doc_node, "value", None),
288 parent=self._instance,
289 )
290
291 @property
292 def attr___qualname__(self):
293 return node_classes.Const(value=self._instance.qname(), parent=self._instance)
294
295 @property
296 def attr___defaults__(self):
297 func = self._instance
298 if self._is_builtin_func():
299 raise AttributeInferenceError(target=func, attribute="__defaults__")
300 if not func.args.defaults:
301 return node_classes.Const(value=None, parent=func)
302
303 defaults_obj = node_classes.Tuple(parent=func)
304 defaults_obj.postinit(func.args.defaults)
305 return defaults_obj
306
307 @property
308 def attr___annotations__(self):
309 if self._is_builtin_func():
310 raise AttributeInferenceError(
311 target=self._instance, attribute="__annotations__"
312 )
313 obj = node_classes.Dict(
314 parent=self._instance,
315 lineno=self._instance.lineno,
316 col_offset=self._instance.col_offset,
317 end_lineno=self._instance.end_lineno,
318 end_col_offset=self._instance.end_col_offset,
319 )
320
321 if not self._instance.returns:
322 returns = None
323 else:
324 returns = self._instance.returns
325
326 args = self._instance.args
327 pair_annotations = itertools.chain(
328 zip(args.args or [], args.annotations),
329 zip(args.kwonlyargs, args.kwonlyargs_annotations),
330 zip(args.posonlyargs or [], args.posonlyargs_annotations),
331 )
332
333 annotations = {
334 arg.name: annotation for (arg, annotation) in pair_annotations if annotation
335 }
336 if args.varargannotation:
337 annotations[args.vararg] = args.varargannotation
338 if args.kwargannotation:
339 annotations[args.kwarg] = args.kwargannotation
340 if returns:
341 annotations["return"] = returns
342
343 items = [
344 (node_classes.Const(key, parent=obj), value)
345 for (key, value) in annotations.items()
346 ]
347
348 obj.postinit(items)
349 return obj
350
351 @property
352 def attr___dict__(self):
353 if self._is_builtin_func():
354 raise AttributeInferenceError(target=self._instance, attribute="__dict__")
355 return node_classes.Dict(
356 parent=self._instance,
357 lineno=self._instance.lineno,
358 col_offset=self._instance.col_offset,
359 end_lineno=self._instance.end_lineno,
360 end_col_offset=self._instance.end_col_offset,
361 )
362
363 @property
364 def attr___globals__(self):
365 if self._is_builtin_func():
366 raise AttributeInferenceError(
367 target=self._instance, attribute="__globals__"
368 )
369 return node_classes.Dict(
370 parent=self._instance,
371 lineno=self._instance.lineno,
372 col_offset=self._instance.col_offset,
373 end_lineno=self._instance.end_lineno,
374 end_col_offset=self._instance.end_col_offset,
375 )
376
377 @property
378 def attr___kwdefaults__(self):
379 if self._is_builtin_func():
380 raise AttributeInferenceError(
381 target=self._instance, attribute="__kwdefaults__"
382 )
383
384 def _default_args(args, parent):
385 for arg in args.kwonlyargs:
386 try:
387 default = args.default_value(arg.name)
388 except NoDefault:
389 continue
390
391 name = node_classes.Const(arg.name, parent=parent)
392 yield name, default
393
394 args = self._instance.args
395 obj = node_classes.Dict(
396 parent=self._instance,
397 lineno=self._instance.lineno,
398 col_offset=self._instance.col_offset,
399 end_lineno=self._instance.end_lineno,
400 end_col_offset=self._instance.end_col_offset,
401 )
402 defaults = dict(_default_args(args, obj))
403
404 obj.postinit(list(defaults.items()))
405 return obj
406
407 @property
408 def attr___module__(self):
409 return node_classes.Const(self._instance.root().qname())
410
411 @property
412 def attr___get__(self):
413 func = self._instance
414
415 if self._is_builtin_func():
416 raise AttributeInferenceError(target=func, attribute="__get__")
417
418 class DescriptorBoundMethod(bases.BoundMethod):
419 """Bound method which knows how to understand calling descriptor
420 binding.
421 """
422
423 def implicit_parameters(self) -> Literal[0]:
424 # Different than BoundMethod since the signature
425 # is different.
426 return 0
427
428 def infer_call_result(
429 self,
430 caller: SuccessfulInferenceResult | None,
431 context: InferenceContext | None = None,
432 ) -> Iterator[bases.BoundMethod]:
433 if len(caller.args) > 2 or len(caller.args) < 1:
434 raise InferenceError(
435 "Invalid arguments for descriptor binding",
436 target=self,
437 context=context,
438 )
439
440 context = copy_context(context)
441 try:
442 cls = next(caller.args[0].infer(context=context))
443 except StopIteration as e:
444 raise InferenceError(context=context, node=caller.args[0]) from e
445
446 if isinstance(cls, util.UninferableBase):
447 raise InferenceError(
448 "Invalid class inferred", target=self, context=context
449 )
450
451 # The `func` can already be a Unbound or BoundMethod. If the former, make sure to
452 # wrap as a BoundMethod like we do below when constructing the function from scratch.
453 if isinstance(func, bases.UnboundMethod):
454 yield bases.BoundMethod(proxy=func, bound=cls)
455 return
456
457 if isinstance(func, bases.BoundMethod):
458 yield func
459 return
460
461 # Rebuild the original value, but with the parent set as the
462 # class where it will be bound.
463 new_func_kwargs = {
464 "name": func.name,
465 "lineno": func.lineno,
466 "col_offset": func.col_offset,
467 "parent": func.parent,
468 "end_lineno": func.end_lineno,
469 "end_col_offset": func.end_col_offset,
470 }
471 if isinstance(func, astroid.objects.PartialFunction):
472 new_func_kwargs["filled_args"] = func.filled_args
473 new_func_kwargs["filled_keywords"] = func.filled_keywords
474
475 new_func = func.__class__(**new_func_kwargs)
476 # pylint: disable=no-member
477 new_func.postinit(
478 func.args,
479 func.body,
480 func.decorators,
481 func.returns,
482 doc_node=func.doc_node,
483 )
484
485 # Build a proper bound method that points to our newly built function.
486 proxy = bases.UnboundMethod(new_func)
487 yield bases.BoundMethod(proxy=proxy, bound=cls)
488
489 @property
490 def args(self):
491 """Overwrite the underlying args to match those of the underlying func.
492
493 Usually the underlying *func* is a function/method, as in:
494
495 def test(self):
496 pass
497
498 This has only the *self* parameter but when we access test.__get__
499 we get a new object which has two parameters, *self* and *type*.
500 """
501 nonlocal func
502 arguments = nodes.Arguments(
503 parent=func.args.parent, vararg=None, kwarg=None
504 )
505
506 positional_or_keyword_params = func.args.args.copy()
507 positional_or_keyword_params.append(
508 nodes.AssignName(
509 name="type",
510 lineno=0,
511 col_offset=0,
512 parent=arguments,
513 end_lineno=None,
514 end_col_offset=None,
515 )
516 )
517
518 positional_only_params = func.args.posonlyargs.copy()
519
520 arguments.postinit(
521 args=positional_or_keyword_params,
522 posonlyargs=positional_only_params,
523 defaults=[],
524 kwonlyargs=[],
525 kw_defaults=[],
526 annotations=[],
527 kwonlyargs_annotations=[],
528 posonlyargs_annotations=[],
529 )
530 return arguments
531
532 return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
533
534 # Function-specific attributes.
535 @property
536 def attr___call__(self):
537 return node_classes.Unknown(parent=self._instance)
538
539 attr___builtins__ = attr___call__
540 attr___closure__ = attr___call__
541 attr___code__ = attr___call__
542 attr___type_params__ = attr___call__
543
544
545class ClassModel(ObjectModel):
546 def __init__(self):
547 # Add a context so that inferences called from an instance don't recurse endlessly
548 self.context = InferenceContext()
549
550 super().__init__()
551
552 @property
553 def attr___annotations__(self) -> node_classes.Unknown:
554 return node_classes.Unknown(parent=self._instance)
555
556 @property
557 def attr___module__(self):
558 return node_classes.Const(self._instance.root().qname())
559
560 @property
561 def attr___name__(self):
562 return node_classes.Const(self._instance.name)
563
564 @property
565 def attr___qualname__(self):
566 return node_classes.Const(self._instance.qname())
567
568 @property
569 def attr___doc__(self):
570 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
571
572 @property
573 def attr___mro__(self):
574 mro = self._instance.mro()
575 obj = node_classes.Tuple(parent=self._instance)
576 obj.postinit(mro)
577 return obj
578
579 @property
580 def attr_mro(self):
581 other_self = self
582
583 # Cls.mro is a method and we need to return one in order to have a proper inference.
584 # The method we're returning is capable of inferring the underlying MRO though.
585 class MroBoundMethod(bases.BoundMethod):
586 def infer_call_result(
587 self,
588 caller: SuccessfulInferenceResult | None,
589 context: InferenceContext | None = None,
590 ) -> Iterator[node_classes.Tuple]:
591 yield other_self.attr___mro__
592
593 implicit_metaclass = self._instance.implicit_metaclass()
594 mro_method = implicit_metaclass.locals["mro"][0]
595 return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
596
597 @property
598 def attr___bases__(self):
599 obj = node_classes.Tuple()
600 context = InferenceContext()
601 elts = list(self._instance._inferred_bases(context))
602 obj.postinit(elts=elts)
603 return obj
604
605 @property
606 def attr___class__(self):
607 # pylint: disable=import-outside-toplevel; circular import
608 from astroid import helpers
609
610 return helpers.object_type(self._instance)
611
612 @property
613 def attr___subclasses__(self):
614 """Get the subclasses of the underlying class.
615
616 This looks only in the current module for retrieving the subclasses,
617 thus it might miss a couple of them.
618 """
619
620 qname = self._instance.qname()
621 root = self._instance.root()
622 classes = [
623 cls
624 for cls in root.nodes_of_class(nodes.ClassDef)
625 if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
626 ]
627
628 obj = node_classes.List(parent=self._instance)
629 obj.postinit(classes)
630
631 class SubclassesBoundMethod(bases.BoundMethod):
632 def infer_call_result(
633 self,
634 caller: SuccessfulInferenceResult | None,
635 context: InferenceContext | None = None,
636 ) -> Iterator[node_classes.List]:
637 yield obj
638
639 implicit_metaclass = self._instance.implicit_metaclass()
640 subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
641 return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
642
643 @property
644 def attr___dict__(self):
645 return node_classes.Dict(
646 parent=self._instance,
647 lineno=self._instance.lineno,
648 col_offset=self._instance.col_offset,
649 end_lineno=self._instance.end_lineno,
650 end_col_offset=self._instance.end_col_offset,
651 )
652
653 @property
654 def attr___call__(self):
655 """Calling a class A() returns an instance of A."""
656 return self._instance.instantiate_class()
657
658
659class SuperModel(ObjectModel):
660 @property
661 def attr___thisclass__(self):
662 return self._instance.mro_pointer
663
664 @property
665 def attr___self_class__(self):
666 return self._instance._self_class
667
668 @property
669 def attr___self__(self):
670 return self._instance.type
671
672 @property
673 def attr___class__(self):
674 return self._instance._proxied
675
676
677class UnboundMethodModel(FunctionModel):
678 @property
679 def attr___class__(self):
680 # pylint: disable=import-outside-toplevel; circular import
681 from astroid import helpers
682
683 return helpers.object_type(self._instance)
684
685 @property
686 def attr___func__(self):
687 return self._instance._proxied
688
689 @property
690 def attr___self__(self):
691 return node_classes.Const(value=None, parent=self._instance)
692
693 attr_im_func = attr___func__
694 attr_im_class = attr___class__
695 attr_im_self = attr___self__
696
697
698class ContextManagerModel(ObjectModel):
699 """Model for context managers.
700
701 Based on 3.3.9 of the Data Model documentation:
702 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
703 """
704
705 @property
706 def attr___enter__(self) -> bases.BoundMethod:
707 """Representation of the base implementation of __enter__.
708
709 As per Python documentation:
710 Enter the runtime context related to this object. The with statement
711 will bind this method's return value to the target(s) specified in the
712 as clause of the statement, if any.
713 """
714 from astroid import builder # pylint: disable=import-outside-toplevel
715
716 node: nodes.FunctionDef = builder.extract_node("""def __enter__(self): ...""")
717 # We set the parent as being the ClassDef of 'object' as that
718 # is where this method originally comes from
719 node.parent = AstroidManager().builtins_module["object"]
720
721 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
722
723 @property
724 def attr___exit__(self) -> bases.BoundMethod:
725 """Representation of the base implementation of __exit__.
726
727 As per Python documentation:
728 Exit the runtime context related to this object. The parameters describe the
729 exception that caused the context to be exited. If the context was exited
730 without an exception, all three arguments will be None.
731 """
732 from astroid import builder # pylint: disable=import-outside-toplevel
733
734 node: nodes.FunctionDef = builder.extract_node(
735 """def __exit__(self, exc_type, exc_value, traceback): ..."""
736 )
737 # We set the parent as being the ClassDef of 'object' as that
738 # is where this method originally comes from
739 node.parent = AstroidManager().builtins_module["object"]
740
741 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
742
743
744class BoundMethodModel(FunctionModel):
745 @property
746 def attr___func__(self):
747 return self._instance._proxied._proxied
748
749 @property
750 def attr___self__(self):
751 return self._instance.bound
752
753
754class GeneratorBaseModel(FunctionModel, ContextManagerModel):
755 def __init__(self, gen_module: nodes.Module):
756 super().__init__()
757 for name, values in gen_module.locals.items():
758 method = values[0]
759 if isinstance(method, nodes.FunctionDef):
760 method = bases.BoundMethod(method, _get_bound_node(self))
761
762 def patched(cls, meth=method):
763 return meth
764
765 setattr(type(self), IMPL_PREFIX + name, property(patched))
766
767 @property
768 def attr___name__(self):
769 return node_classes.Const(
770 value=self._instance.parent.name, parent=self._instance
771 )
772
773 @property
774 def attr___doc__(self):
775 return node_classes.Const(
776 value=getattr(self._instance.parent.doc_node, "value", None),
777 parent=self._instance,
778 )
779
780
781class GeneratorModel(GeneratorBaseModel):
782 def __init__(self):
783 super().__init__(AstroidManager().builtins_module["generator"])
784
785
786class AsyncGeneratorModel(GeneratorBaseModel):
787 def __init__(self):
788 super().__init__(AstroidManager().builtins_module["async_generator"])
789
790
791class InstanceModel(ObjectModel):
792 @property
793 def attr___class__(self):
794 return self._instance._proxied
795
796 @property
797 def attr___module__(self):
798 return node_classes.Const(self._instance.root().qname())
799
800 @property
801 def attr___doc__(self):
802 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
803
804 @property
805 def attr___dict__(self):
806 return _dunder_dict(self._instance, self._instance.instance_attrs)
807
808
809# Exception instances
810
811
812class ExceptionInstanceModel(InstanceModel):
813 @property
814 def attr_args(self) -> nodes.Tuple:
815 return nodes.Tuple(parent=self._instance)
816
817 @property
818 def attr___traceback__(self):
819 builtins_ast_module = AstroidManager().builtins_module
820 traceback_type = builtins_ast_module[types.TracebackType.__name__]
821 return traceback_type.instantiate_class()
822
823
824class SyntaxErrorInstanceModel(ExceptionInstanceModel):
825 @property
826 def attr_text(self):
827 return node_classes.Const("")
828
829
830class GroupExceptionInstanceModel(ExceptionInstanceModel):
831 @property
832 def attr_exceptions(self) -> nodes.Tuple:
833 return node_classes.Tuple(parent=self._instance)
834
835
836class OSErrorInstanceModel(ExceptionInstanceModel):
837 @property
838 def attr_filename(self):
839 return node_classes.Const("")
840
841 @property
842 def attr_errno(self):
843 return node_classes.Const(0)
844
845 @property
846 def attr_strerror(self):
847 return node_classes.Const("")
848
849 attr_filename2 = attr_filename
850
851
852class ImportErrorInstanceModel(ExceptionInstanceModel):
853 @property
854 def attr_name(self):
855 return node_classes.Const("")
856
857 @property
858 def attr_path(self):
859 return node_classes.Const("")
860
861
862class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
863 @property
864 def attr_object(self):
865 return node_classes.Const(b"")
866
867
868BUILTIN_EXCEPTIONS = {
869 "builtins.SyntaxError": SyntaxErrorInstanceModel,
870 "builtins.ExceptionGroup": GroupExceptionInstanceModel,
871 "builtins.ImportError": ImportErrorInstanceModel,
872 "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
873 # These are all similar to OSError in terms of attributes
874 "builtins.OSError": OSErrorInstanceModel,
875 "builtins.BlockingIOError": OSErrorInstanceModel,
876 "builtins.BrokenPipeError": OSErrorInstanceModel,
877 "builtins.ChildProcessError": OSErrorInstanceModel,
878 "builtins.ConnectionAbortedError": OSErrorInstanceModel,
879 "builtins.ConnectionError": OSErrorInstanceModel,
880 "builtins.ConnectionRefusedError": OSErrorInstanceModel,
881 "builtins.ConnectionResetError": OSErrorInstanceModel,
882 "builtins.FileExistsError": OSErrorInstanceModel,
883 "builtins.FileNotFoundError": OSErrorInstanceModel,
884 "builtins.InterruptedError": OSErrorInstanceModel,
885 "builtins.IsADirectoryError": OSErrorInstanceModel,
886 "builtins.NotADirectoryError": OSErrorInstanceModel,
887 "builtins.PermissionError": OSErrorInstanceModel,
888 "builtins.ProcessLookupError": OSErrorInstanceModel,
889 "builtins.TimeoutError": OSErrorInstanceModel,
890}
891
892
893class DictModel(ObjectModel):
894 @property
895 def attr___class__(self):
896 return self._instance._proxied
897
898 def _generic_dict_attribute(self, obj, name):
899 """Generate a bound method that can infer the given *obj*."""
900
901 class DictMethodBoundMethod(astroid.BoundMethod):
902 def infer_call_result(
903 self,
904 caller: SuccessfulInferenceResult | None,
905 context: InferenceContext | None = None,
906 ) -> Iterator[InferenceResult]:
907 yield obj
908
909 meth = next(self._instance._proxied.igetattr(name), None)
910 return DictMethodBoundMethod(proxy=meth, bound=self._instance)
911
912 @property
913 def attr_items(self):
914 from astroid import objects # pylint: disable=import-outside-toplevel
915
916 elems = []
917 obj = node_classes.List(parent=self._instance)
918 for key, value in self._instance.items:
919 elem = node_classes.Tuple(parent=obj)
920 elem.postinit((key, value))
921 elems.append(elem)
922 obj.postinit(elts=elems)
923
924 items_obj = objects.DictItems(obj)
925 return self._generic_dict_attribute(items_obj, "items")
926
927 @property
928 def attr_keys(self):
929 from astroid import objects # pylint: disable=import-outside-toplevel
930
931 keys = [key for (key, _) in self._instance.items]
932 obj = node_classes.List(parent=self._instance)
933 obj.postinit(elts=keys)
934
935 keys_obj = objects.DictKeys(obj)
936 return self._generic_dict_attribute(keys_obj, "keys")
937
938 @property
939 def attr_values(self):
940 from astroid import objects # pylint: disable=import-outside-toplevel
941
942 values = [value for (_, value) in self._instance.items]
943 obj = node_classes.List(parent=self._instance)
944 obj.postinit(values)
945
946 values_obj = objects.DictValues(obj)
947 return self._generic_dict_attribute(values_obj, "values")
948
949
950class PropertyModel(ObjectModel):
951 """Model for a builtin property."""
952
953 def _init_function(self, name):
954 function = nodes.FunctionDef(
955 name=name,
956 parent=self._instance,
957 lineno=self._instance.lineno,
958 col_offset=self._instance.col_offset,
959 end_lineno=self._instance.end_lineno,
960 end_col_offset=self._instance.end_col_offset,
961 )
962
963 args = nodes.Arguments(parent=function, vararg=None, kwarg=None)
964 args.postinit(
965 args=[],
966 defaults=[],
967 kwonlyargs=[],
968 kw_defaults=[],
969 annotations=[],
970 posonlyargs=[],
971 posonlyargs_annotations=[],
972 kwonlyargs_annotations=[],
973 )
974
975 function.postinit(args=args, body=[])
976 return function
977
978 @property
979 def attr_fget(self):
980 func = self._instance
981
982 class PropertyFuncAccessor(nodes.FunctionDef):
983 def infer_call_result(
984 self,
985 caller: SuccessfulInferenceResult | None,
986 context: InferenceContext | None = None,
987 ) -> Iterator[InferenceResult]:
988 nonlocal func
989 if caller and len(caller.args) != 1:
990 raise InferenceError(
991 "fget() needs a single argument", target=self, context=context
992 )
993
994 yield from func.function.infer_call_result(
995 caller=caller, context=context
996 )
997
998 property_accessor = PropertyFuncAccessor(
999 name="fget",
1000 parent=self._instance,
1001 lineno=self._instance.lineno,
1002 col_offset=self._instance.col_offset,
1003 end_lineno=self._instance.end_lineno,
1004 end_col_offset=self._instance.end_col_offset,
1005 )
1006 property_accessor.postinit(args=func.args, body=func.body)
1007 return property_accessor
1008
1009 @property
1010 def attr_fset(self):
1011 func = self._instance
1012
1013 def find_setter(func: Property) -> nodes.FunctionDef | None:
1014 """
1015 Given a property, find the corresponding setter function and returns it.
1016
1017 :param func: property for which the setter has to be found
1018 :return: the setter function or None
1019 """
1020 for node in func.parent.body:
1021 if isinstance(node, (nodes.FunctionDef, nodes.AsyncFunctionDef)):
1022 for dec_name in node.decoratornames():
1023 if dec_name.endswith(func.function.name + ".setter"):
1024 return node
1025 return None
1026
1027 func_setter = find_setter(func)
1028 if not func_setter:
1029 raise InferenceError(
1030 f"Unable to find the setter of property {func.function.name}"
1031 )
1032
1033 class PropertyFuncAccessor(nodes.FunctionDef):
1034 def infer_call_result(
1035 self,
1036 caller: SuccessfulInferenceResult | None,
1037 context: InferenceContext | None = None,
1038 ) -> Iterator[InferenceResult]:
1039 nonlocal func_setter
1040 if caller and len(caller.args) != 2:
1041 raise InferenceError(
1042 "fset() needs two arguments", target=self, context=context
1043 )
1044 yield from func_setter.infer_call_result(caller=caller, context=context)
1045
1046 property_accessor = PropertyFuncAccessor(
1047 name="fset",
1048 parent=self._instance,
1049 lineno=self._instance.lineno,
1050 col_offset=self._instance.col_offset,
1051 end_lineno=self._instance.end_lineno,
1052 end_col_offset=self._instance.end_col_offset,
1053 )
1054 property_accessor.postinit(args=func_setter.args, body=func_setter.body)
1055 return property_accessor
1056
1057 @property
1058 def attr_setter(self):
1059 return self._init_function("setter")
1060
1061 @property
1062 def attr_deleter(self):
1063 return self._init_function("deleter")
1064
1065 @property
1066 def attr_getter(self):
1067 return self._init_function("getter")
1068
1069 # pylint: enable=import-outside-toplevel