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