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