objects.py 12 KB

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