Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/bases.py: 67%
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, None, None]:
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, None, None]:
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[
495 nodes.Const | Instance | UninferableBase, None, None
496 ]:
497 if not isinstance(caller, nodes.Call):
498 return
499 if not caller.args:
500 return
501 # Attempt to create a constant
502 if len(caller.args) > 1:
503 value = None
504 if isinstance(caller.args[1], nodes.Const):
505 value = caller.args[1].value
506 else:
507 inferred_arg = next(caller.args[1].infer(), None)
508 if isinstance(inferred_arg, nodes.Const):
509 value = inferred_arg.value
510 if value is not None:
511 const = nodes.const_factory(value)
512 assert not isinstance(const, nodes.EmptyNode)
513 yield const
514 return
516 node_context = context.extra_context.get(caller.args[0])
517 for inferred in caller.args[0].infer(context=node_context):
518 if isinstance(inferred, UninferableBase):
519 yield inferred
520 if isinstance(inferred, nodes.ClassDef):
521 yield Instance(inferred)
522 raise InferenceError
524 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
525 return True
528class BoundMethod(UnboundMethod):
529 """A special node representing a method bound to an instance."""
531 special_attributes = objectmodel.BoundMethodModel()
533 def __init__(
534 self,
535 proxy: nodes.FunctionDef | nodes.Lambda | UnboundMethod,
536 bound: SuccessfulInferenceResult,
537 ) -> None:
538 super().__init__(proxy)
539 self.bound = bound
541 def implicit_parameters(self) -> Literal[0, 1]:
542 if self.name == "__new__":
543 # __new__ acts as a classmethod but the class argument is not implicit.
544 return 0
545 return 1
547 def is_bound(self) -> Literal[True]:
548 return True
550 def _infer_type_new_call(
551 self, caller: nodes.Call, context: InferenceContext
552 ) -> nodes.ClassDef | None: # noqa: C901
553 """Try to infer what type.__new__(mcs, name, bases, attrs) returns.
555 In order for such call to be valid, the metaclass needs to be
556 a subtype of ``type``, the name needs to be a string, the bases
557 needs to be a tuple of classes
558 """
559 # pylint: disable=import-outside-toplevel; circular import
560 from astroid.nodes import Pass
562 # Verify the metaclass
563 try:
564 mcs = next(caller.args[0].infer(context=context))
565 except StopIteration as e:
566 raise InferenceError(context=context) from e
567 if not isinstance(mcs, nodes.ClassDef):
568 # Not a valid first argument.
569 return None
570 if not mcs.is_subtype_of("builtins.type"):
571 # Not a valid metaclass.
572 return None
574 # Verify the name
575 try:
576 name = next(caller.args[1].infer(context=context))
577 except StopIteration as e:
578 raise InferenceError(context=context) from e
579 if not isinstance(name, nodes.Const):
580 # Not a valid name, needs to be a const.
581 return None
582 if not isinstance(name.value, str):
583 # Needs to be a string.
584 return None
586 # Verify the bases
587 try:
588 bases = next(caller.args[2].infer(context=context))
589 except StopIteration as e:
590 raise InferenceError(context=context) from e
591 if not isinstance(bases, nodes.Tuple):
592 # Needs to be a tuple.
593 return None
594 try:
595 inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
596 except StopIteration as e:
597 raise InferenceError(context=context) from e
598 if any(not isinstance(base, nodes.ClassDef) for base in inferred_bases):
599 # All the bases needs to be Classes
600 return None
602 # Verify the attributes.
603 try:
604 attrs = next(caller.args[3].infer(context=context))
605 except StopIteration as e:
606 raise InferenceError(context=context) from e
607 if not isinstance(attrs, nodes.Dict):
608 # Needs to be a dictionary.
609 return None
610 cls_locals: dict[str, list[InferenceResult]] = collections.defaultdict(list)
611 for key, value in attrs.items:
612 try:
613 key = next(key.infer(context=context))
614 except StopIteration as e:
615 raise InferenceError(context=context) from e
616 try:
617 value = next(value.infer(context=context))
618 except StopIteration as e:
619 raise InferenceError(context=context) from e
620 # Ignore non string keys
621 if isinstance(key, nodes.Const) and isinstance(key.value, str):
622 cls_locals[key.value].append(value)
624 # Build the class from now.
625 cls = mcs.__class__(
626 name=name.value,
627 lineno=caller.lineno or 0,
628 col_offset=caller.col_offset or 0,
629 parent=caller,
630 end_lineno=caller.end_lineno,
631 end_col_offset=caller.end_col_offset,
632 )
633 empty = Pass(
634 parent=cls,
635 lineno=caller.lineno,
636 col_offset=caller.col_offset,
637 end_lineno=caller.end_lineno,
638 end_col_offset=caller.end_col_offset,
639 )
640 cls.postinit(
641 bases=bases.elts,
642 body=[empty],
643 decorators=None,
644 newstyle=True,
645 metaclass=mcs,
646 keywords=[],
647 )
648 cls.locals = cls_locals
649 return cls
651 def infer_call_result(
652 self,
653 caller: SuccessfulInferenceResult | None,
654 context: InferenceContext | None = None,
655 ) -> Iterator[InferenceResult]:
656 context = bind_context_to_node(context, self.bound)
657 if (
658 isinstance(self.bound, nodes.ClassDef)
659 and self.bound.name == "type"
660 and self.name == "__new__"
661 and isinstance(caller, nodes.Call)
662 and len(caller.args) == 4
663 ):
664 # Check if we have a ``type.__new__(mcs, name, bases, attrs)`` call.
665 new_cls = self._infer_type_new_call(caller, context)
666 if new_cls:
667 return iter((new_cls,))
669 return super().infer_call_result(caller, context)
671 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
672 return True
675class Generator(BaseInstance):
676 """A special node representing a generator.
678 Proxied class is set once for all in raw_building.
679 """
681 # We defer initialization of special_attributes to the __init__ method since the constructor
682 # of GeneratorModel requires the raw_building to be complete
683 # TODO: This should probably be refactored.
684 special_attributes: objectmodel.GeneratorModel
686 def __init__(
687 self,
688 parent: nodes.FunctionDef,
689 generator_initial_context: InferenceContext | None = None,
690 ) -> None:
691 super().__init__()
692 self.parent = parent
693 self._call_context = copy_context(generator_initial_context)
695 # See comment above: this is a deferred initialization.
696 Generator.special_attributes = objectmodel.GeneratorModel()
698 def infer_yield_types(self) -> Iterator[InferenceResult]:
699 yield from self.parent.infer_yield_result(self._call_context)
701 def callable(self) -> Literal[False]:
702 return False
704 def pytype(self) -> str:
705 return "builtins.generator"
707 def display_type(self) -> str:
708 return "Generator"
710 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
711 return True
713 def __repr__(self) -> str:
714 return f"<Generator({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
716 def __str__(self) -> str:
717 return f"Generator({self._proxied.name})"
720class AsyncGenerator(Generator):
721 """Special node representing an async generator."""
723 def pytype(self) -> Literal["builtins.async_generator"]:
724 return "builtins.async_generator"
726 def display_type(self) -> str:
727 return "AsyncGenerator"
729 def __repr__(self) -> str:
730 return f"<AsyncGenerator({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
732 def __str__(self) -> str:
733 return f"AsyncGenerator({self._proxied.name})"
736class UnionType(BaseInstance):
737 """Special node representing new style typing unions.
739 Proxied class is set once for all in raw_building.
740 """
742 def __init__(
743 self,
744 left: UnionType | nodes.ClassDef | nodes.Const,
745 right: UnionType | nodes.ClassDef | nodes.Const,
746 parent: nodes.NodeNG | None = None,
747 ) -> None:
748 super().__init__()
749 self.parent = parent
750 self.left = left
751 self.right = right
753 def callable(self) -> Literal[False]:
754 return False
756 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
757 return True
759 def pytype(self) -> Literal["types.UnionType"]:
760 return "types.UnionType"
762 def display_type(self) -> str:
763 return "UnionType"
765 def __repr__(self) -> str:
766 return f"<UnionType({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>"
768 def __str__(self) -> str:
769 return f"UnionType({self._proxied.name})"