Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/astroid/bases.py: 57%
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 base classes and functions for the nodes and some
6inference utils.
7"""
8from __future__ import annotations
10import collections
11import collections.abc
12from collections.abc import Iterable, Iterator
13from typing import TYPE_CHECKING, Any, Literal
15from astroid import decorators, nodes
16from astroid.const import PY310_PLUS
17from astroid.context import (
18 CallContext,
19 InferenceContext,
20 bind_context_to_node,
21 copy_context,
22)
23from astroid.exceptions import (
24 AstroidTypeError,
25 AttributeInferenceError,
26 InferenceError,
27 NameInferenceError,
28)
29from astroid.interpreter import objectmodel
30from astroid.typing import (
31 InferenceErrorInfo,
32 InferenceResult,
33 SuccessfulInferenceResult,
34)
35from astroid.util import Uninferable, UninferableBase, safe_infer
37if TYPE_CHECKING:
38 from astroid.constraint import Constraint
41PROPERTIES = {"builtins.property", "abc.abstractproperty"}
42if PY310_PLUS:
43 PROPERTIES.add("enum.property")
45# List of possible property names. We use this list in order
46# to see if a method is a property or not. This should be
47# pretty reliable and fast, the alternative being to check each
48# decorator to see if its a real property-like descriptor, which
49# can be too complicated.
50# Also, these aren't qualified, because each project can
51# define them, we shouldn't expect to know every possible
52# property-like decorator!
53POSSIBLE_PROPERTIES = {
54 "cached_property",
55 "cachedproperty",
56 "lazyproperty",
57 "lazy_property",
58 "reify",
59 "lazyattribute",
60 "lazy_attribute",
61 "LazyProperty",
62 "lazy",
63 "cache_readonly",
64 "DynamicClassAttribute",
65}
68def _is_property(
69 meth: nodes.FunctionDef | UnboundMethod, context: InferenceContext | None = None
70) -> bool:
71 decoratornames = meth.decoratornames(context=context)
72 if PROPERTIES.intersection(decoratornames):
73 return True
74 stripped = {
75 name.split(".")[-1]
76 for name in decoratornames
77 if not isinstance(name, UninferableBase)
78 }
79 if any(name in stripped for name in POSSIBLE_PROPERTIES):
80 return True
82 # Lookup for subclasses of *property*
83 if not meth.decorators:
84 return False
85 for decorator in meth.decorators.nodes or ():
86 inferred = safe_infer(decorator, context=context)
87 if inferred is None or isinstance(inferred, UninferableBase):
88 continue
89 if isinstance(inferred, nodes.ClassDef):
90 for base_class in inferred.bases:
91 if not isinstance(base_class, nodes.Name):
92 continue
93 module, _ = base_class.lookup(base_class.name)
94 if (
95 isinstance(module, nodes.Module)
96 and module.name == "builtins"
97 and base_class.name == "property"
98 ):
99 return True
101 return False
104class Proxy:
105 """A simple proxy object.
107 Note:
109 Subclasses of this object will need a custom __getattr__
110 if new instance attributes are created. See the Const class
111 """
113 _proxied: nodes.ClassDef | nodes.FunctionDef | nodes.Lambda | UnboundMethod
115 def __init__(
116 self,
117 proxied: (
118 nodes.ClassDef | nodes.FunctionDef | nodes.Lambda | UnboundMethod | None
119 ) = None,
120 ) -> None:
121 if proxied is None:
122 # This is a hack to allow calling this __init__ during bootstrapping of
123 # builtin classes and their docstrings.
124 # For Const, Generator, and UnionType nodes the _proxied attribute
125 # is set during bootstrapping
126 # as we first need to build the ClassDef that they can proxy.
127 # Thus, if proxied is None self should be a Const or Generator
128 # as that is the only way _proxied will be correctly set as a ClassDef.
129 assert isinstance(self, (nodes.Const, Generator, UnionType))
130 else:
131 self._proxied = proxied
133 def __getattr__(self, name: str) -> Any:
134 if name == "_proxied":
135 return self.__class__._proxied
136 if name in self.__dict__:
137 return self.__dict__[name]
138 return getattr(self._proxied, name)
140 def infer( # type: ignore[return]
141 self, context: InferenceContext | None = None, **kwargs: Any
142 ) -> collections.abc.Generator[InferenceResult, None, InferenceErrorInfo | None]:
143 yield self
146def _infer_stmts(
147 stmts: Iterable[InferenceResult],
148 context: InferenceContext | None,
149 frame: nodes.NodeNG | BaseInstance | None = None,
150) -> collections.abc.Generator[InferenceResult]:
151 """Return an iterator on statements inferred by each statement in *stmts*."""
152 inferred = False
153 constraint_failed = False
154 if context is not None:
155 name = context.lookupname
156 context = context.clone()
157 if name is not None:
158 constraints = context.constraints.get(name, {})
159 else:
160 constraints = {}
161 else:
162 name = None
163 constraints = {}
164 context = InferenceContext()
166 for stmt in stmts:
167 if isinstance(stmt, UninferableBase):
168 yield stmt
169 inferred = True
170 continue
171 context.lookupname = stmt._infer_name(frame, name)
172 try:
173 stmt_constraints: set[Constraint] = set()
174 for constraint_stmt, potential_constraints in constraints.items():
175 if not constraint_stmt.parent_of(stmt):
176 stmt_constraints.update(potential_constraints)
177 for inf in stmt.infer(context=context):
178 if all(constraint.satisfied_by(inf) for constraint in stmt_constraints):
179 yield inf
180 inferred = True
181 else:
182 constraint_failed = True
183 except NameInferenceError:
184 continue
185 except InferenceError:
186 yield Uninferable
187 inferred = True
189 if not inferred and constraint_failed:
190 yield Uninferable
191 elif not inferred:
192 raise InferenceError(
193 "Inference failed for all members of {stmts!r}.",
194 stmts=stmts,
195 frame=frame,
196 context=context,
197 )
200def _infer_method_result_truth(
201 instance: Instance, method_name: str, context: InferenceContext
202) -> bool | UninferableBase:
203 # Get the method from the instance and try to infer
204 # its return's truth value.
205 meth = next(instance.igetattr(method_name, context=context), None)
206 if meth and hasattr(meth, "infer_call_result"):
207 if not meth.callable():
208 return Uninferable
209 try:
210 context.callcontext = CallContext(args=[], callee=meth)
211 for value in meth.infer_call_result(instance, context=context):
212 if isinstance(value, UninferableBase):
213 return value
214 try:
215 inferred = next(value.infer(context=context))
216 except StopIteration as e:
217 raise InferenceError(context=context) from e
218 return inferred.bool_value()
219 except InferenceError:
220 pass
221 return Uninferable
224class BaseInstance(Proxy):
225 """An instance base class, which provides lookup methods for potential
226 instances.
227 """
229 _proxied: nodes.ClassDef
231 special_attributes: objectmodel.ObjectModel
233 def display_type(self) -> str:
234 return "Instance of"
236 def getattr(
237 self,
238 name: str,
239 context: InferenceContext | None = None,
240 lookupclass: bool = True,
241 ) -> list[InferenceResult]:
242 try:
243 values = self._proxied.instance_attr(name, context)
244 except AttributeInferenceError as exc:
245 if self.special_attributes and name in self.special_attributes:
246 return [self.special_attributes.lookup(name)]
248 if lookupclass:
249 # Class attributes not available through the instance
250 # unless they are explicitly defined.
251 return self._proxied.getattr(name, context, class_context=False)
253 raise AttributeInferenceError(
254 target=self, attribute=name, context=context
255 ) from exc
256 # since we've no context information, return matching class members as
257 # well
258 if lookupclass:
259 try:
260 return values + self._proxied.getattr(
261 name, context, class_context=False
262 )
263 except AttributeInferenceError:
264 pass
265 return values
267 def igetattr(
268 self, name: str, context: InferenceContext | None = None
269 ) -> Iterator[InferenceResult]:
270 """Inferred getattr."""
271 if not context:
272 context = InferenceContext()
273 try:
274 context.lookupname = name
275 # XXX frame should be self._proxied, or not ?
276 get_attr = self.getattr(name, context, lookupclass=False)
277 yield from _infer_stmts(
278 self._wrap_attr(get_attr, context), context, frame=self
279 )
280 except AttributeInferenceError:
281 try:
282 # fallback to class.igetattr since it has some logic to handle
283 # descriptors
284 # But only if the _proxied is the Class.
285 if self._proxied.__class__.__name__ != "ClassDef":
286 raise
287 attrs = self._proxied.igetattr(name, context, class_context=False)
288 yield from self._wrap_attr(attrs, context)
289 except AttributeInferenceError as error:
290 raise InferenceError(**vars(error)) from error
292 def _wrap_attr(
293 self, attrs: Iterable[InferenceResult], context: InferenceContext | None = None
294 ) -> Iterator[InferenceResult]:
295 """Wrap bound methods of attrs in a InstanceMethod proxies."""
296 for attr in attrs:
297 if isinstance(attr, UnboundMethod):
298 if _is_property(attr):
299 yield from attr.infer_call_result(self, context)
300 else:
301 yield BoundMethod(attr, self)
302 elif isinstance(attr, nodes.Lambda):
303 if attr.args.arguments and attr.args.arguments[0].name == "self":
304 yield BoundMethod(attr, self)
305 continue
306 yield attr
307 else:
308 yield attr
310 def infer_call_result(
311 self,
312 caller: SuccessfulInferenceResult | None,
313 context: InferenceContext | None = None,
314 ) -> Iterator[InferenceResult]:
315 """Infer what a class instance is returning when called."""
316 context = bind_context_to_node(context, self)
317 inferred = False
319 # If the call is an attribute on the instance, we infer the attribute itself
320 if isinstance(caller, nodes.Call) and isinstance(caller.func, nodes.Attribute):
321 for res in self.igetattr(caller.func.attrname, context):
322 inferred = True
323 yield res
325 # Otherwise we infer the call to the __call__ dunder normally
326 for node in self._proxied.igetattr("__call__", context):
327 if isinstance(node, UninferableBase) or not node.callable():
328 continue
329 if isinstance(node, BaseInstance) and node._proxied is self._proxied:
330 inferred = True
331 yield node
332 # Prevent recursion.
333 continue
334 for res in node.infer_call_result(caller, context):
335 inferred = True
336 yield res
337 if not inferred:
338 raise InferenceError(node=self, caller=caller, context=context)
341class Instance(BaseInstance):
342 """A special node representing a class instance."""
344 special_attributes = objectmodel.InstanceModel()
346 def __init__(self, proxied: nodes.ClassDef | None) -> None:
347 super().__init__(proxied)
349 @decorators.yes_if_nothing_inferred
350 def infer_binary_op(
351 self,
352 opnode: nodes.AugAssign | nodes.BinOp,
353 operator: str,
354 other: InferenceResult,
355 context: InferenceContext,
356 method: SuccessfulInferenceResult,
357 ) -> Generator[InferenceResult]:
358 return method.infer_call_result(self, context)
360 def __repr__(self) -> str:
361 return "<Instance of {}.{} at 0x{}>".format(
362 self._proxied.root().name, self._proxied.name, id(self)
363 )
365 def __str__(self) -> str:
366 return f"Instance of {self._proxied.root().name}.{self._proxied.name}"
368 def callable(self) -> bool:
369 try:
370 self._proxied.getattr("__call__", class_context=False)
371 return True
372 except AttributeInferenceError:
373 return False
375 def pytype(self) -> str:
376 return self._proxied.qname()
378 def display_type(self) -> str:
379 return "Instance of"
381 def bool_value(
382 self, context: InferenceContext | None = None
383 ) -> bool | UninferableBase:
384 """Infer the truth value for an Instance.
386 The truth value of an instance is determined by these conditions:
388 * if it implements __bool__ on Python 3 or __nonzero__
389 on Python 2, then its bool value will be determined by
390 calling this special method and checking its result.
391 * when this method is not defined, __len__() is called, if it
392 is defined, and the object is considered true if its result is
393 nonzero. If a class defines neither __len__() nor __bool__(),
394 all its instances are considered true.
395 """
396 context = context or InferenceContext()
397 context.boundnode = self
399 try:
400 result = _infer_method_result_truth(self, "__bool__", context)
401 except (InferenceError, AttributeInferenceError):
402 # Fallback to __len__.
403 try:
404 result = _infer_method_result_truth(self, "__len__", context)
405 except (AttributeInferenceError, InferenceError):
406 return True
407 return result
409 def getitem(
410 self, index: nodes.Const, context: InferenceContext | None = None
411 ) -> InferenceResult | None:
412 new_context = bind_context_to_node(context, self)
413 if not context:
414 context = new_context
415 method = next(self.igetattr("__getitem__", context=context), None)
416 # Create a new CallContext for providing index as an argument.
417 new_context.callcontext = CallContext(args=[index], callee=method)
418 if not isinstance(method, BoundMethod):
419 raise InferenceError(
420 "Could not find __getitem__ for {node!r}.", node=self, context=context
421 )
422 if len(method.args.arguments) != 2: # (self, index)
423 raise AstroidTypeError(
424 "__getitem__ for {node!r} does not have correct signature",
425 node=self,
426 context=context,
427 )
428 return next(method.infer_call_result(self, new_context), None)
431class UnboundMethod(Proxy):
432 """A special node representing a method not bound to an instance."""
434 _proxied: nodes.FunctionDef | UnboundMethod
436 special_attributes: (
437 objectmodel.BoundMethodModel | objectmodel.UnboundMethodModel
438 ) = objectmodel.UnboundMethodModel()
440 def __repr__(self) -> str:
441 assert self._proxied.parent, "Expected a parent node"
442 frame = self._proxied.parent.frame()
443 return "<{} {} of {} at 0x{}".format(
444 self.__class__.__name__, self._proxied.name, frame.qname(), id(self)
445 )
447 def implicit_parameters(self) -> Literal[0, 1]:
448 return 0
450 def is_bound(self) -> bool:
451 return False
453 def getattr(self, name: str, context: InferenceContext | None = None):
454 if name in self.special_attributes:
455 return [self.special_attributes.lookup(name)]
456 return self._proxied.getattr(name, context)
458 def igetattr(
459 self, name: str, context: InferenceContext | None = None
460 ) -> Iterator[InferenceResult]:
461 if name in self.special_attributes:
462 return iter((self.special_attributes.lookup(name),))
463 return self._proxied.igetattr(name, context)
465 def infer_call_result(
466 self,
467 caller: SuccessfulInferenceResult | None,
468 context: InferenceContext | None = None,
469 ) -> Iterator[InferenceResult]:
470 """
471 The boundnode of the regular context with a function called
472 on ``object.__new__`` will be of type ``object``,
473 which is incorrect for the argument in general.
474 If no context is given the ``object.__new__`` call argument will
475 be correctly inferred except when inside a call that requires
476 the additional context (such as a classmethod) of the boundnode
477 to determine which class the method was called from
478 """
480 # If we're unbound method __new__ of a builtin, the result is an
481 # instance of the class given as first argument.
482 if self._proxied.name == "__new__":
483 assert self._proxied.parent, "Expected a parent node"
484 qname = self._proxied.parent.frame().qname()
485 # Avoid checking builtins.type: _infer_type_new_call() does more validation
486 if qname.startswith("builtins.") and qname != "builtins.type":
487 return self._infer_builtin_new(caller, context or InferenceContext())
488 return self._proxied.infer_call_result(caller, context)
490 def _infer_builtin_new(
491 self,
492 caller: SuccessfulInferenceResult | None,
493 context: InferenceContext,
494 ) -> collections.abc.Generator[nodes.Const | Instance | UninferableBase]:
495 if not isinstance(caller, nodes.Call):
496 return
497 if not caller.args:
498 return
499 # Attempt to create a constant
500 if len(caller.args) > 1:
501 value = None
502 if isinstance(caller.args[1], nodes.Const):
503 value = caller.args[1].value
504 else:
505 inferred_arg = next(caller.args[1].infer(), None)
506 if isinstance(inferred_arg, nodes.Const):
507 value = inferred_arg.value
508 if value is not None:
509 const = nodes.const_factory(value)
510 assert not isinstance(const, nodes.EmptyNode)
511 yield const
512 return
514 node_context = context.extra_context.get(caller.args[0])
515 for inferred in caller.args[0].infer(context=node_context):
516 if isinstance(inferred, UninferableBase):
517 yield inferred
518 if isinstance(inferred, nodes.ClassDef):
519 yield Instance(inferred)
520 raise InferenceError
522 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
523 return True
526class BoundMethod(UnboundMethod):
527 """A special node representing a method bound to an instance."""
529 special_attributes = objectmodel.BoundMethodModel()
531 def __init__(
532 self,
533 proxy: nodes.FunctionDef | nodes.Lambda | UnboundMethod,
534 bound: SuccessfulInferenceResult,
535 ) -> None:
536 super().__init__(proxy)
537 self.bound = bound
539 def implicit_parameters(self) -> Literal[0, 1]:
540 if self.name == "__new__":
541 # __new__ acts as a classmethod but the class argument is not implicit.
542 return 0
543 return 1
545 def is_bound(self) -> Literal[True]:
546 return True
548 def _infer_type_new_call(
549 self, caller: nodes.Call, context: InferenceContext
550 ) -> nodes.ClassDef | None: # noqa: C901
551 """Try to infer what type.__new__(mcs, name, bases, attrs) returns.
553 In order for such call to be valid, the metaclass needs to be
554 a subtype of ``type``, the name needs to be a string, the bases
555 needs to be a tuple of classes
556 """
557 # pylint: disable=import-outside-toplevel; circular import
558 from astroid.nodes import Pass
560 # Verify the metaclass
561 try:
562 mcs = next(caller.args[0].infer(context=context))
563 except StopIteration as e:
564 raise InferenceError(context=context) from e
565 if not isinstance(mcs, nodes.ClassDef):
566 # Not a valid first argument.
567 return None
568 if not mcs.is_subtype_of("builtins.type"):
569 # Not a valid metaclass.
570 return None
572 # Verify the name
573 try:
574 name = next(caller.args[1].infer(context=context))
575 except StopIteration as e:
576 raise InferenceError(context=context) from e
577 if not isinstance(name, nodes.Const):
578 # Not a valid name, needs to be a const.
579 return None
580 if not isinstance(name.value, str):
581 # Needs to be a string.
582 return None
584 # Verify the bases
585 try:
586 bases = next(caller.args[2].infer(context=context))
587 except StopIteration as e:
588 raise InferenceError(context=context) from e
589 if not isinstance(bases, nodes.Tuple):
590 # Needs to be a tuple.
591 return None
592 try:
593 inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
594 except StopIteration as e:
595 raise InferenceError(context=context) from e
596 if any(not isinstance(base, nodes.ClassDef) for base in inferred_bases):
597 # All the bases needs to be Classes
598 return None
600 # Verify the attributes.
601 try:
602 attrs = next(caller.args[3].infer(context=context))
603 except StopIteration as e:
604 raise InferenceError(context=context) from e
605 if not isinstance(attrs, nodes.Dict):
606 # Needs to be a dictionary.
607 return None
608 cls_locals: dict[str, list[InferenceResult]] = collections.defaultdict(list)
609 for key, value in attrs.items:
610 try:
611 key = next(key.infer(context=context))
612 except StopIteration as e:
613 raise InferenceError(context=context) from e
614 try:
615 value = next(value.infer(context=context))
616 except StopIteration as e:
617 raise InferenceError(context=context) from e
618 # Ignore non string keys
619 if isinstance(key, nodes.Const) and isinstance(key.value, str):
620 cls_locals[key.value].append(value)
622 # Build the class from now.
623 cls = mcs.__class__(
624 name=name.value,
625 lineno=caller.lineno or 0,
626 col_offset=caller.col_offset or 0,
627 parent=caller,
628 end_lineno=caller.end_lineno,
629 end_col_offset=caller.end_col_offset,
630 )
631 empty = Pass(
632 parent=cls,
633 lineno=caller.lineno,
634 col_offset=caller.col_offset,
635 end_lineno=caller.end_lineno,
636 end_col_offset=caller.end_col_offset,
637 )
638 cls.postinit(
639 bases=bases.elts,
640 body=[empty],
641 decorators=None,
642 newstyle=True,
643 metaclass=mcs,
644 keywords=[],
645 )
646 cls.locals = cls_locals
647 return cls
649 def infer_call_result(
650 self,
651 caller: SuccessfulInferenceResult | None,
652 context: InferenceContext | None = None,
653 ) -> Iterator[InferenceResult]:
654 context = bind_context_to_node(context, self.bound)
655 if (
656 isinstance(self.bound, nodes.ClassDef)
657 and self.bound.name == "type"
658 and self.name == "__new__"
659 and isinstance(caller, nodes.Call)
660 and len(caller.args) == 4
661 ):
662 # Check if we have a ``type.__new__(mcs, name, bases, attrs)`` call.
663 new_cls = self._infer_type_new_call(caller, context)
664 if new_cls:
665 return iter((new_cls,))
667 return super().infer_call_result(caller, context)
669 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
670 return True
673class Generator(BaseInstance):
674 """A special node representing a generator.
676 Proxied class is set once for all in raw_building.
677 """
679 # We defer initialization of special_attributes to the __init__ method since the constructor
680 # of GeneratorModel requires the raw_building to be complete
681 # TODO: This should probably be refactored.
682 special_attributes: objectmodel.GeneratorBaseModel
684 def __init__(
685 self,
686 parent: nodes.FunctionDef,
687 generator_initial_context: InferenceContext | None = None,
688 ) -> None:
689 super().__init__()
690 self.parent = parent
691 self._call_context = copy_context(generator_initial_context)
693 # See comment above: this is a deferred initialization.
694 Generator.special_attributes = objectmodel.GeneratorModel()
696 def infer_yield_types(self) -> Iterator[InferenceResult]:
697 yield from self.parent.infer_yield_result(self._call_context)
699 def callable(self) -> Literal[False]:
700 return False
702 def pytype(self) -> str:
703 return "builtins.generator"
705 def display_type(self) -> str:
706 return "Generator"
708 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
709 return True
711 def __repr__(self) -> str:
712 return f"<Generator({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
714 def __str__(self) -> str:
715 return f"Generator({self._proxied.name})"
718class AsyncGenerator(Generator):
719 """Special node representing an async generator."""
721 def __init__(self, *args, **kwargs):
722 super().__init__(*args, **kwargs)
723 AsyncGenerator.special_attributes = objectmodel.AsyncGeneratorModel()
725 def pytype(self) -> Literal["builtins.async_generator"]:
726 return "builtins.async_generator"
728 def display_type(self) -> str:
729 return "AsyncGenerator"
731 def __repr__(self) -> str:
732 return f"<AsyncGenerator({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
734 def __str__(self) -> str:
735 return f"AsyncGenerator({self._proxied.name})"
738class UnionType(BaseInstance):
739 """Special node representing new style typing unions.
741 Proxied class is set once for all in raw_building.
742 """
744 def __init__(
745 self,
746 left: UnionType | nodes.ClassDef | nodes.Const,
747 right: UnionType | nodes.ClassDef | nodes.Const,
748 parent: nodes.NodeNG | None = None,
749 ) -> None:
750 super().__init__()
751 self.parent = parent
752 self.left = left
753 self.right = right
755 def callable(self) -> Literal[False]:
756 return False
758 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
759 return True
761 def pytype(self) -> Literal["types.UnionType"]:
762 return "types.UnionType"
764 def display_type(self) -> str:
765 return "UnionType"
767 def __repr__(self) -> str:
768 return f"<UnionType({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
770 def __str__(self) -> str:
771 return f"UnionType({self._proxied.name})"