objects.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  2. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
  4. """
  5. Inference objects are a way to represent composite AST nodes,
  6. which are used only as inference results, so they can't be found in the
  7. original AST tree. For instance, inferring the following frozenset use,
  8. leads to an inferred FrozenSet:
  9. Call(func=Name('frozenset'), args=Tuple(...))
  10. """
  11. from __future__ import annotations
  12. import sys
  13. from collections.abc import Generator
  14. from typing import Any, TypeVar
  15. from astroid import bases, decorators, util
  16. from astroid.context import InferenceContext
  17. from astroid.exceptions import (
  18. AttributeInferenceError,
  19. InferenceError,
  20. MroError,
  21. SuperError,
  22. )
  23. from astroid.manager import AstroidManager
  24. from astroid.nodes import node_classes, scoped_nodes
  25. objectmodel = util.lazy_import("interpreter.objectmodel")
  26. if sys.version_info >= (3, 8):
  27. from functools import cached_property
  28. from typing import Literal
  29. else:
  30. from typing_extensions import Literal
  31. from astroid.decorators import cachedproperty as cached_property
  32. _T = TypeVar("_T")
  33. class FrozenSet(node_classes.BaseContainer):
  34. """Class representing a FrozenSet composite node."""
  35. def pytype(self) -> Literal["builtins.frozenset"]:
  36. return "builtins.frozenset"
  37. def _infer(self, context: InferenceContext | None = None, **kwargs: Any):
  38. yield self
  39. @cached_property
  40. def _proxied(self): # pylint: disable=method-hidden
  41. ast_builtins = AstroidManager().builtins_module
  42. return ast_builtins.getattr("frozenset")[0]
  43. class Super(node_classes.NodeNG):
  44. """Proxy class over a super call.
  45. This class offers almost the same behaviour as Python's super,
  46. which is MRO lookups for retrieving attributes from the parents.
  47. The *mro_pointer* is the place in the MRO from where we should
  48. start looking, not counting it. *mro_type* is the object which
  49. provides the MRO, it can be both a type or an instance.
  50. *self_class* is the class where the super call is, while
  51. *scope* is the function where the super call is.
  52. """
  53. # pylint: disable=unnecessary-lambda
  54. special_attributes = util.lazy_descriptor(lambda: objectmodel.SuperModel())
  55. def __init__(self, mro_pointer, mro_type, self_class, scope):
  56. self.type = mro_type
  57. self.mro_pointer = mro_pointer
  58. self._class_based = False
  59. self._self_class = self_class
  60. self._scope = scope
  61. super().__init__()
  62. def _infer(self, context: InferenceContext | None = None, **kwargs: Any):
  63. yield self
  64. def super_mro(self):
  65. """Get the MRO which will be used to lookup attributes in this super."""
  66. if not isinstance(self.mro_pointer, scoped_nodes.ClassDef):
  67. raise SuperError(
  68. "The first argument to super must be a subtype of "
  69. "type, not {mro_pointer}.",
  70. super_=self,
  71. )
  72. if isinstance(self.type, scoped_nodes.ClassDef):
  73. # `super(type, type)`, most likely in a class method.
  74. self._class_based = True
  75. mro_type = self.type
  76. else:
  77. mro_type = getattr(self.type, "_proxied", None)
  78. if not isinstance(mro_type, (bases.Instance, scoped_nodes.ClassDef)):
  79. raise SuperError(
  80. "The second argument to super must be an "
  81. "instance or subtype of type, not {type}.",
  82. super_=self,
  83. )
  84. if not mro_type.newstyle:
  85. raise SuperError("Unable to call super on old-style classes.", super_=self)
  86. mro = mro_type.mro()
  87. if self.mro_pointer not in mro:
  88. raise SuperError(
  89. "The second argument to super must be an "
  90. "instance or subtype of type, not {type}.",
  91. super_=self,
  92. )
  93. index = mro.index(self.mro_pointer)
  94. return mro[index + 1 :]
  95. @cached_property
  96. def _proxied(self):
  97. ast_builtins = AstroidManager().builtins_module
  98. return ast_builtins.getattr("super")[0]
  99. def pytype(self) -> Literal["builtins.super"]:
  100. return "builtins.super"
  101. def display_type(self) -> str:
  102. return "Super of"
  103. @property
  104. def name(self):
  105. """Get the name of the MRO pointer."""
  106. return self.mro_pointer.name
  107. def qname(self) -> Literal["super"]:
  108. return "super"
  109. def igetattr( # noqa: C901
  110. self, name: str, context: InferenceContext | None = None
  111. ):
  112. """Retrieve the inferred values of the given attribute name."""
  113. # '__class__' is a special attribute that should be taken directly
  114. # from the special attributes dict
  115. if name == "__class__":
  116. yield self.special_attributes.lookup(name)
  117. return
  118. try:
  119. mro = self.super_mro()
  120. # Don't let invalid MROs or invalid super calls
  121. # leak out as is from this function.
  122. except SuperError as exc:
  123. raise AttributeInferenceError(
  124. (
  125. "Lookup for {name} on {target!r} because super call {super!r} "
  126. "is invalid."
  127. ),
  128. target=self,
  129. attribute=name,
  130. context=context,
  131. super_=exc.super_,
  132. ) from exc
  133. except MroError as exc:
  134. raise AttributeInferenceError(
  135. (
  136. "Lookup for {name} on {target!r} failed because {cls!r} has an "
  137. "invalid MRO."
  138. ),
  139. target=self,
  140. attribute=name,
  141. context=context,
  142. mros=exc.mros,
  143. cls=exc.cls,
  144. ) from exc
  145. found = False
  146. for cls in mro:
  147. if name not in cls.locals:
  148. continue
  149. found = True
  150. for inferred in bases._infer_stmts([cls[name]], context, frame=self):
  151. if not isinstance(inferred, scoped_nodes.FunctionDef):
  152. yield inferred
  153. continue
  154. # We can obtain different descriptors from a super depending
  155. # on what we are accessing and where the super call is.
  156. if inferred.type == "classmethod":
  157. yield bases.BoundMethod(inferred, cls)
  158. elif self._scope.type == "classmethod" and inferred.type == "method":
  159. yield inferred
  160. elif self._class_based or inferred.type == "staticmethod":
  161. yield inferred
  162. elif isinstance(inferred, Property):
  163. function = inferred.function
  164. try:
  165. yield from function.infer_call_result(
  166. caller=self, context=context
  167. )
  168. except InferenceError:
  169. yield util.Uninferable
  170. elif bases._is_property(inferred):
  171. # TODO: support other descriptors as well.
  172. try:
  173. yield from inferred.infer_call_result(self, context)
  174. except InferenceError:
  175. yield util.Uninferable
  176. else:
  177. yield bases.BoundMethod(inferred, cls)
  178. # Only if we haven't found any explicit overwrites for the
  179. # attribute we look it up in the special attributes
  180. if not found and name in self.special_attributes:
  181. yield self.special_attributes.lookup(name)
  182. return
  183. if not found:
  184. raise AttributeInferenceError(target=self, attribute=name, context=context)
  185. def getattr(self, name, context: InferenceContext | None = None):
  186. return list(self.igetattr(name, context=context))
  187. class ExceptionInstance(bases.Instance):
  188. """Class for instances of exceptions.
  189. It has special treatment for some of the exceptions's attributes,
  190. which are transformed at runtime into certain concrete objects, such as
  191. the case of .args.
  192. """
  193. @cached_property
  194. def special_attributes(self):
  195. qname = self.qname()
  196. instance = objectmodel.BUILTIN_EXCEPTIONS.get(
  197. qname, objectmodel.ExceptionInstanceModel
  198. )
  199. return instance()(self)
  200. class DictInstance(bases.Instance):
  201. """Special kind of instances for dictionaries.
  202. This instance knows the underlying object model of the dictionaries, which means
  203. that methods such as .values or .items can be properly inferred.
  204. """
  205. # pylint: disable=unnecessary-lambda
  206. special_attributes = util.lazy_descriptor(lambda: objectmodel.DictModel())
  207. # Custom objects tailored for dictionaries, which are used to
  208. # disambiguate between the types of Python 2 dict's method returns
  209. # and Python 3 (where they return set like objects).
  210. class DictItems(bases.Proxy):
  211. __str__ = node_classes.NodeNG.__str__
  212. __repr__ = node_classes.NodeNG.__repr__
  213. class DictKeys(bases.Proxy):
  214. __str__ = node_classes.NodeNG.__str__
  215. __repr__ = node_classes.NodeNG.__repr__
  216. class DictValues(bases.Proxy):
  217. __str__ = node_classes.NodeNG.__str__
  218. __repr__ = node_classes.NodeNG.__repr__
  219. class PartialFunction(scoped_nodes.FunctionDef):
  220. """A class representing partial function obtained via functools.partial."""
  221. @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
  222. def __init__(
  223. self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None
  224. ):
  225. # TODO: Pass end_lineno and end_col_offset as well
  226. super().__init__(name, lineno=lineno, col_offset=col_offset, parent=None)
  227. # Assigned directly to prevent triggering the DeprecationWarning.
  228. self._doc = doc
  229. # A typical FunctionDef automatically adds its name to the parent scope,
  230. # but a partial should not, so defer setting parent until after init
  231. self.parent = parent
  232. self.filled_args = call.positional_arguments[1:]
  233. self.filled_keywords = call.keyword_arguments
  234. wrapped_function = call.positional_arguments[0]
  235. inferred_wrapped_function = next(wrapped_function.infer())
  236. if isinstance(inferred_wrapped_function, PartialFunction):
  237. self.filled_args = inferred_wrapped_function.filled_args + self.filled_args
  238. self.filled_keywords = {
  239. **inferred_wrapped_function.filled_keywords,
  240. **self.filled_keywords,
  241. }
  242. self.filled_positionals = len(self.filled_args)
  243. def infer_call_result(self, caller=None, context: InferenceContext | None = None):
  244. if context:
  245. current_passed_keywords = {
  246. keyword for (keyword, _) in context.callcontext.keywords
  247. }
  248. for keyword, value in self.filled_keywords.items():
  249. if keyword not in current_passed_keywords:
  250. context.callcontext.keywords.append((keyword, value))
  251. call_context_args = context.callcontext.args or []
  252. context.callcontext.args = self.filled_args + call_context_args
  253. return super().infer_call_result(caller=caller, context=context)
  254. def qname(self) -> str:
  255. return self.__class__.__name__
  256. # TODO: Hack to solve the circular import problem between node_classes and objects
  257. # This is not needed in 2.0, which has a cleaner design overall
  258. node_classes.Dict.__bases__ = (node_classes.NodeNG, DictInstance)
  259. class Property(scoped_nodes.FunctionDef):
  260. """Class representing a Python property."""
  261. @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
  262. def __init__(
  263. self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None
  264. ):
  265. self.function = function
  266. super().__init__(name, lineno=lineno, col_offset=col_offset, parent=parent)
  267. # Assigned directly to prevent triggering the DeprecationWarning.
  268. self._doc = doc
  269. # pylint: disable=unnecessary-lambda
  270. special_attributes = util.lazy_descriptor(lambda: objectmodel.PropertyModel())
  271. type = "property"
  272. def pytype(self) -> Literal["builtins.property"]:
  273. return "builtins.property"
  274. def infer_call_result(self, caller=None, context: InferenceContext | None = None):
  275. raise InferenceError("Properties are not callable")
  276. def _infer(
  277. self: _T, context: InferenceContext | None = None, **kwargs: Any
  278. ) -> Generator[_T, None, None]:
  279. yield self