helpers.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. """Various helper utilities."""
  5. from __future__ import annotations
  6. import warnings
  7. from collections.abc import Generator
  8. from astroid import bases, manager, nodes, objects, raw_building, util
  9. from astroid.context import CallContext, InferenceContext
  10. from astroid.exceptions import (
  11. AstroidTypeError,
  12. AttributeInferenceError,
  13. InferenceError,
  14. MroError,
  15. _NonDeducibleTypeHierarchy,
  16. )
  17. from astroid.nodes import scoped_nodes
  18. from astroid.typing import InferenceResult
  19. from astroid.util import safe_infer as real_safe_infer
  20. def safe_infer(
  21. node: nodes.NodeNG | bases.Proxy | util.UninferableBase,
  22. context: InferenceContext | None = None,
  23. ) -> InferenceResult | None:
  24. # When removing, also remove the real_safe_infer alias
  25. warnings.warn(
  26. "Import safe_infer from astroid.util; this shim in astroid.helpers will be removed.",
  27. DeprecationWarning,
  28. stacklevel=2,
  29. )
  30. return real_safe_infer(node, context=context)
  31. def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef:
  32. proxy = raw_building.build_class(cls_name, builtins)
  33. return proxy
  34. def _function_type(
  35. function: nodes.Lambda | nodes.FunctionDef | bases.UnboundMethod,
  36. builtins: nodes.Module,
  37. ) -> nodes.ClassDef:
  38. if isinstance(function, (scoped_nodes.Lambda, scoped_nodes.FunctionDef)):
  39. if function.root().name == "builtins":
  40. cls_name = "builtin_function_or_method"
  41. else:
  42. cls_name = "function"
  43. elif isinstance(function, bases.BoundMethod):
  44. cls_name = "method"
  45. else:
  46. cls_name = "function"
  47. return _build_proxy_class(cls_name, builtins)
  48. def _object_type(
  49. node: InferenceResult, context: InferenceContext | None = None
  50. ) -> Generator[InferenceResult | None]:
  51. astroid_manager = manager.AstroidManager()
  52. builtins = astroid_manager.builtins_module
  53. context = context or InferenceContext()
  54. for inferred in node.infer(context=context):
  55. if isinstance(inferred, scoped_nodes.ClassDef):
  56. metaclass = inferred.metaclass(context=context)
  57. if metaclass:
  58. yield metaclass
  59. continue
  60. yield builtins.getattr("type")[0]
  61. elif isinstance(
  62. inferred,
  63. (scoped_nodes.Lambda, bases.UnboundMethod, scoped_nodes.FunctionDef),
  64. ):
  65. yield _function_type(inferred, builtins)
  66. elif isinstance(inferred, scoped_nodes.Module):
  67. yield _build_proxy_class("module", builtins)
  68. elif isinstance(inferred, nodes.Unknown):
  69. raise InferenceError
  70. elif isinstance(inferred, util.UninferableBase):
  71. yield inferred
  72. elif isinstance(inferred, (bases.Proxy, nodes.Slice, objects.Super)):
  73. yield inferred._proxied
  74. else: # pragma: no cover
  75. raise AssertionError(f"We don't handle {type(inferred)} currently")
  76. def object_type(
  77. node: InferenceResult, context: InferenceContext | None = None
  78. ) -> InferenceResult | None:
  79. """Obtain the type of the given node.
  80. This is used to implement the ``type`` builtin, which means that it's
  81. used for inferring type calls, as well as used in a couple of other places
  82. in the inference.
  83. The node will be inferred first, so this function can support all
  84. sorts of objects, as long as they support inference.
  85. """
  86. try:
  87. types = set(_object_type(node, context))
  88. except InferenceError:
  89. return util.Uninferable
  90. if len(types) != 1:
  91. return util.Uninferable
  92. return next(iter(types))
  93. def _object_type_is_subclass(
  94. obj_type: InferenceResult | None,
  95. class_or_seq: list[InferenceResult],
  96. context: InferenceContext | None = None,
  97. ) -> util.UninferableBase | bool:
  98. if isinstance(obj_type, util.UninferableBase) or not isinstance(
  99. obj_type, nodes.ClassDef
  100. ):
  101. return util.Uninferable
  102. # Instances are not types
  103. class_seq = [
  104. item if not isinstance(item, bases.Instance) else util.Uninferable
  105. for item in class_or_seq
  106. ]
  107. # strict compatibility with issubclass
  108. # issubclass(type, (object, 1)) evaluates to true
  109. # issubclass(object, (1, type)) raises TypeError
  110. for klass in class_seq:
  111. if isinstance(klass, util.UninferableBase):
  112. raise AstroidTypeError(
  113. f"arg 2 must be a type or tuple of types, not {type(klass)!r}"
  114. )
  115. for obj_subclass in obj_type.mro():
  116. if obj_subclass == klass:
  117. return True
  118. return False
  119. def object_isinstance(
  120. node: InferenceResult,
  121. class_or_seq: list[InferenceResult],
  122. context: InferenceContext | None = None,
  123. ) -> util.UninferableBase | bool:
  124. """Check if a node 'isinstance' any node in class_or_seq.
  125. :raises AstroidTypeError: if the given ``classes_or_seq`` are not types
  126. """
  127. obj_type = object_type(node, context)
  128. if isinstance(obj_type, util.UninferableBase):
  129. return util.Uninferable
  130. return _object_type_is_subclass(obj_type, class_or_seq, context=context)
  131. def object_issubclass(
  132. node: nodes.NodeNG,
  133. class_or_seq: list[InferenceResult],
  134. context: InferenceContext | None = None,
  135. ) -> util.UninferableBase | bool:
  136. """Check if a type is a subclass of any node in class_or_seq.
  137. :raises AstroidTypeError: if the given ``classes_or_seq`` are not types
  138. :raises AstroidError: if the type of the given node cannot be inferred
  139. or its type's mro doesn't work
  140. """
  141. if not isinstance(node, nodes.ClassDef):
  142. raise TypeError(f"{node} needs to be a ClassDef node, not {type(node)!r}")
  143. return _object_type_is_subclass(node, class_or_seq, context=context)
  144. def has_known_bases(klass, context: InferenceContext | None = None) -> bool:
  145. """Return whether all base classes of a class could be inferred."""
  146. try:
  147. return klass._all_bases_known
  148. except AttributeError:
  149. pass
  150. for base in klass.bases:
  151. result = real_safe_infer(base, context=context)
  152. # TODO: check for A->B->A->B pattern in class structure too?
  153. if (
  154. not isinstance(result, scoped_nodes.ClassDef)
  155. or result is klass
  156. or not has_known_bases(result, context=context)
  157. ):
  158. klass._all_bases_known = False
  159. return False
  160. klass._all_bases_known = True
  161. return True
  162. def _type_check(type1, type2) -> bool:
  163. if not all(map(has_known_bases, (type1, type2))):
  164. raise _NonDeducibleTypeHierarchy
  165. try:
  166. return type1 in type2.mro()[:-1]
  167. except MroError as e:
  168. # The MRO is invalid.
  169. raise _NonDeducibleTypeHierarchy from e
  170. def is_subtype(type1, type2) -> bool:
  171. """Check if *type1* is a subtype of *type2*."""
  172. return _type_check(type1=type2, type2=type1)
  173. def is_supertype(type1, type2) -> bool:
  174. """Check if *type2* is a supertype of *type1*."""
  175. return _type_check(type1, type2)
  176. def class_instance_as_index(node: bases.Instance) -> nodes.Const | None:
  177. """Get the value as an index for the given instance.
  178. If an instance provides an __index__ method, then it can
  179. be used in some scenarios where an integer is expected,
  180. for instance when multiplying or subscripting a list.
  181. """
  182. context = InferenceContext()
  183. try:
  184. for inferred in node.igetattr("__index__", context=context):
  185. if not isinstance(inferred, bases.BoundMethod):
  186. continue
  187. context.boundnode = node
  188. context.callcontext = CallContext(args=[], callee=inferred)
  189. for result in inferred.infer_call_result(node, context=context):
  190. if isinstance(result, nodes.Const) and isinstance(result.value, int):
  191. return result
  192. except InferenceError:
  193. pass
  194. return None
  195. def object_len(node, context: InferenceContext | None = None):
  196. """Infer length of given node object.
  197. :param Union[nodes.ClassDef, nodes.Instance] node:
  198. :param node: Node to infer length of
  199. :raises AstroidTypeError: If an invalid node is returned
  200. from __len__ method or no __len__ method exists
  201. :raises InferenceError: If the given node cannot be inferred
  202. or if multiple nodes are inferred or if the code executed in python
  203. would result in a infinite recursive check for length
  204. :rtype int: Integer length of node
  205. """
  206. # pylint: disable=import-outside-toplevel; circular import
  207. from astroid.objects import FrozenSet
  208. inferred_node = real_safe_infer(node, context=context)
  209. # prevent self referential length calls from causing a recursion error
  210. # see https://github.com/pylint-dev/astroid/issues/777
  211. node_frame = node.frame()
  212. if (
  213. isinstance(node_frame, scoped_nodes.FunctionDef)
  214. and node_frame.name == "__len__"
  215. and isinstance(inferred_node, bases.Proxy)
  216. and inferred_node._proxied == node_frame.parent
  217. ):
  218. message = (
  219. "Self referential __len__ function will "
  220. "cause a RecursionError on line {} of {}".format(
  221. node.lineno, node.root().file
  222. )
  223. )
  224. raise InferenceError(message)
  225. if inferred_node is None or isinstance(inferred_node, util.UninferableBase):
  226. raise InferenceError(node=node)
  227. if isinstance(inferred_node, nodes.Const) and isinstance(
  228. inferred_node.value, (bytes, str)
  229. ):
  230. return len(inferred_node.value)
  231. if isinstance(inferred_node, (nodes.List, nodes.Set, nodes.Tuple, FrozenSet)):
  232. return len(inferred_node.elts)
  233. if isinstance(inferred_node, nodes.Dict):
  234. return len(inferred_node.items)
  235. node_type = object_type(inferred_node, context=context)
  236. if not node_type:
  237. raise InferenceError(node=node)
  238. try:
  239. len_call = next(node_type.igetattr("__len__", context=context))
  240. except StopIteration as e:
  241. raise AstroidTypeError(str(e)) from e
  242. except AttributeInferenceError as e:
  243. raise AstroidTypeError(
  244. f"object of type '{node_type.pytype()}' has no len()"
  245. ) from e
  246. inferred = len_call.infer_call_result(node, context)
  247. if isinstance(inferred, util.UninferableBase):
  248. raise InferenceError(node=node, context=context)
  249. result_of_len = next(inferred, None)
  250. if (
  251. isinstance(result_of_len, nodes.Const)
  252. and result_of_len.pytype() == "builtins.int"
  253. ):
  254. return result_of_len.value
  255. if result_of_len is None or (
  256. isinstance(result_of_len, bases.Instance)
  257. and result_of_len.is_subtype_of("builtins.int")
  258. ):
  259. # Fake a result as we don't know the arguments of the instance call.
  260. return 0
  261. raise AstroidTypeError(
  262. f"'{result_of_len}' object cannot be interpreted as an integer"
  263. )
  264. def _higher_function_scope(node: nodes.NodeNG) -> nodes.FunctionDef | None:
  265. """Search for the first function which encloses the given
  266. scope.
  267. This can be used for looking up in that function's
  268. scope, in case looking up in a lower scope for a particular
  269. name fails.
  270. :param node: A scope node.
  271. :returns:
  272. ``None``, if no parent function scope was found,
  273. otherwise an instance of :class:`astroid.nodes.scoped_nodes.Function`,
  274. which encloses the given node.
  275. """
  276. current = node
  277. while current.parent and not isinstance(current.parent, nodes.FunctionDef):
  278. current = current.parent
  279. if current and current.parent:
  280. return current.parent
  281. return None