helpers.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. # Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
  3. # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
  4. # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
  5. # Copyright (c) 2020 Simon Hewitt <si@sjhewitt.co.uk>
  6. # Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
  7. # Copyright (c) 2020 Ram Rachum <ram@rachum.com>
  8. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  9. # Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
  10. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  11. # Copyright (c) 2021 David Liu <david@cs.toronto.edu>
  12. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  13. # Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
  14. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  15. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  16. """
  17. Various helper utilities.
  18. """
  19. from astroid import bases, manager, nodes, raw_building, util
  20. from astroid.context import CallContext, InferenceContext
  21. from astroid.exceptions import (
  22. AstroidTypeError,
  23. AttributeInferenceError,
  24. InferenceError,
  25. MroError,
  26. _NonDeducibleTypeHierarchy,
  27. )
  28. from astroid.nodes import scoped_nodes
  29. def _build_proxy_class(cls_name, builtins):
  30. proxy = raw_building.build_class(cls_name)
  31. proxy.parent = builtins
  32. return proxy
  33. def _function_type(function, builtins):
  34. if isinstance(function, scoped_nodes.Lambda):
  35. if function.root().name == "builtins":
  36. cls_name = "builtin_function_or_method"
  37. else:
  38. cls_name = "function"
  39. elif isinstance(function, bases.BoundMethod):
  40. cls_name = "method"
  41. elif isinstance(function, bases.UnboundMethod):
  42. cls_name = "function"
  43. return _build_proxy_class(cls_name, builtins)
  44. def _object_type(node, context=None):
  45. astroid_manager = manager.AstroidManager()
  46. builtins = astroid_manager.builtins_module
  47. context = context or InferenceContext()
  48. for inferred in node.infer(context=context):
  49. if isinstance(inferred, scoped_nodes.ClassDef):
  50. if inferred.newstyle:
  51. metaclass = inferred.metaclass(context=context)
  52. if metaclass:
  53. yield metaclass
  54. continue
  55. yield builtins.getattr("type")[0]
  56. elif isinstance(inferred, (scoped_nodes.Lambda, bases.UnboundMethod)):
  57. yield _function_type(inferred, builtins)
  58. elif isinstance(inferred, scoped_nodes.Module):
  59. yield _build_proxy_class("module", builtins)
  60. else:
  61. yield inferred._proxied
  62. def object_type(node, context=None):
  63. """Obtain the type of the given node
  64. This is used to implement the ``type`` builtin, which means that it's
  65. used for inferring type calls, as well as used in a couple of other places
  66. in the inference.
  67. The node will be inferred first, so this function can support all
  68. sorts of objects, as long as they support inference.
  69. """
  70. try:
  71. types = set(_object_type(node, context))
  72. except InferenceError:
  73. return util.Uninferable
  74. if len(types) > 1 or not types:
  75. return util.Uninferable
  76. return list(types)[0]
  77. def _object_type_is_subclass(obj_type, class_or_seq, context=None):
  78. if not isinstance(class_or_seq, (tuple, list)):
  79. class_seq = (class_or_seq,)
  80. else:
  81. class_seq = class_or_seq
  82. if obj_type is util.Uninferable:
  83. return util.Uninferable
  84. # Instances are not types
  85. class_seq = [
  86. item if not isinstance(item, bases.Instance) else util.Uninferable
  87. for item in class_seq
  88. ]
  89. # strict compatibility with issubclass
  90. # issubclass(type, (object, 1)) evaluates to true
  91. # issubclass(object, (1, type)) raises TypeError
  92. for klass in class_seq:
  93. if klass is util.Uninferable:
  94. raise AstroidTypeError("arg 2 must be a type or tuple of types")
  95. for obj_subclass in obj_type.mro():
  96. if obj_subclass == klass:
  97. return True
  98. return False
  99. def object_isinstance(node, class_or_seq, context=None):
  100. """Check if a node 'isinstance' any node in class_or_seq
  101. :param node: A given node
  102. :param class_or_seq: Union[nodes.NodeNG, Sequence[nodes.NodeNG]]
  103. :rtype: bool
  104. :raises AstroidTypeError: if the given ``classes_or_seq`` are not types
  105. """
  106. obj_type = object_type(node, context)
  107. if obj_type is util.Uninferable:
  108. return util.Uninferable
  109. return _object_type_is_subclass(obj_type, class_or_seq, context=context)
  110. def object_issubclass(node, class_or_seq, context=None):
  111. """Check if a type is a subclass of any node in class_or_seq
  112. :param node: A given node
  113. :param class_or_seq: Union[Nodes.NodeNG, Sequence[nodes.NodeNG]]
  114. :rtype: bool
  115. :raises AstroidTypeError: if the given ``classes_or_seq`` are not types
  116. :raises AstroidError: if the type of the given node cannot be inferred
  117. or its type's mro doesn't work
  118. """
  119. if not isinstance(node, nodes.ClassDef):
  120. raise TypeError(f"{node} needs to be a ClassDef node")
  121. return _object_type_is_subclass(node, class_or_seq, context=context)
  122. def safe_infer(node, context=None):
  123. """Return the inferred value for the given node.
  124. Return None if inference failed or if there is some ambiguity (more than
  125. one node has been inferred).
  126. """
  127. try:
  128. inferit = node.infer(context=context)
  129. value = next(inferit)
  130. except (InferenceError, StopIteration):
  131. return None
  132. try:
  133. next(inferit)
  134. return None # None if there is ambiguity on the inferred node
  135. except InferenceError:
  136. return None # there is some kind of ambiguity
  137. except StopIteration:
  138. return value
  139. def has_known_bases(klass, context=None):
  140. """Return true if all base classes of a class could be inferred."""
  141. try:
  142. return klass._all_bases_known
  143. except AttributeError:
  144. pass
  145. for base in klass.bases:
  146. result = safe_infer(base, context=context)
  147. # TODO: check for A->B->A->B pattern in class structure too?
  148. if (
  149. not isinstance(result, scoped_nodes.ClassDef)
  150. or result is klass
  151. or not has_known_bases(result, context=context)
  152. ):
  153. klass._all_bases_known = False
  154. return False
  155. klass._all_bases_known = True
  156. return True
  157. def _type_check(type1, type2):
  158. if not all(map(has_known_bases, (type1, type2))):
  159. raise _NonDeducibleTypeHierarchy
  160. if not all([type1.newstyle, type2.newstyle]):
  161. return False
  162. try:
  163. return type1 in type2.mro()[:-1]
  164. except MroError as e:
  165. # The MRO is invalid.
  166. raise _NonDeducibleTypeHierarchy from e
  167. def is_subtype(type1, type2):
  168. """Check if *type1* is a subtype of *type2*."""
  169. return _type_check(type1=type2, type2=type1)
  170. def is_supertype(type1, type2):
  171. """Check if *type2* is a supertype of *type1*."""
  172. return _type_check(type1, type2)
  173. def class_instance_as_index(node):
  174. """Get the value as an index for the given instance.
  175. If an instance provides an __index__ method, then it can
  176. be used in some scenarios where an integer is expected,
  177. for instance when multiplying or subscripting a list.
  178. """
  179. context = InferenceContext()
  180. try:
  181. for inferred in node.igetattr("__index__", context=context):
  182. if not isinstance(inferred, bases.BoundMethod):
  183. continue
  184. context.boundnode = node
  185. context.callcontext = CallContext(args=[], callee=inferred)
  186. for result in inferred.infer_call_result(node, context=context):
  187. if isinstance(result, nodes.Const) and isinstance(result.value, int):
  188. return result
  189. except InferenceError:
  190. pass
  191. return None
  192. def object_len(node, context=None):
  193. """Infer length of given node object
  194. :param Union[nodes.ClassDef, nodes.Instance] node:
  195. :param node: Node to infer length of
  196. :raises AstroidTypeError: If an invalid node is returned
  197. from __len__ method or no __len__ method exists
  198. :raises InferenceError: If the given node cannot be inferred
  199. or if multiple nodes are inferred or if the code executed in python
  200. would result in a infinite recursive check for length
  201. :rtype int: Integer length of node
  202. """
  203. # pylint: disable=import-outside-toplevel; circular import
  204. from astroid.objects import FrozenSet
  205. inferred_node = safe_infer(node, context=context)
  206. # prevent self referential length calls from causing a recursion error
  207. # see https://github.com/PyCQA/astroid/issues/777
  208. node_frame = node.frame(future=True)
  209. if (
  210. isinstance(node_frame, scoped_nodes.FunctionDef)
  211. and node_frame.name == "__len__"
  212. and hasattr(inferred_node, "_proxied")
  213. and inferred_node._proxied == node_frame.parent
  214. ):
  215. message = (
  216. "Self referential __len__ function will "
  217. "cause a RecursionError on line {} of {}".format(
  218. node.lineno, node.root().file
  219. )
  220. )
  221. raise InferenceError(message)
  222. if inferred_node is None or inferred_node is util.Uninferable:
  223. raise InferenceError(node=node)
  224. if isinstance(inferred_node, nodes.Const) and isinstance(
  225. inferred_node.value, (bytes, str)
  226. ):
  227. return len(inferred_node.value)
  228. if isinstance(inferred_node, (nodes.List, nodes.Set, nodes.Tuple, FrozenSet)):
  229. return len(inferred_node.elts)
  230. if isinstance(inferred_node, nodes.Dict):
  231. return len(inferred_node.items)
  232. node_type = object_type(inferred_node, context=context)
  233. if not node_type:
  234. raise InferenceError(node=node)
  235. try:
  236. len_call = next(node_type.igetattr("__len__", context=context))
  237. except StopIteration as e:
  238. raise AstroidTypeError(str(e)) from e
  239. except AttributeInferenceError as e:
  240. raise AstroidTypeError(
  241. f"object of type '{node_type.pytype()}' has no len()"
  242. ) from e
  243. inferred = len_call.infer_call_result(node, context)
  244. if inferred is util.Uninferable:
  245. raise InferenceError(node=node, context=context)
  246. result_of_len = next(inferred, None)
  247. if (
  248. isinstance(result_of_len, nodes.Const)
  249. and result_of_len.pytype() == "builtins.int"
  250. ):
  251. return result_of_len.value
  252. if (
  253. result_of_len is None
  254. or isinstance(result_of_len, bases.Instance)
  255. and result_of_len.is_subtype_of("builtins.int")
  256. ):
  257. # Fake a result as we don't know the arguments of the instance call.
  258. return 0
  259. raise AstroidTypeError(
  260. f"'{result_of_len}' object cannot be interpreted as an integer"
  261. )