brain_collections.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. from __future__ import annotations
  5. from astroid.brain.helpers import register_module_extender
  6. from astroid.builder import extract_node, parse
  7. from astroid.const import PY39_PLUS
  8. from astroid.context import InferenceContext
  9. from astroid.exceptions import AttributeInferenceError
  10. from astroid.manager import AstroidManager
  11. from astroid.nodes.scoped_nodes import ClassDef
  12. def _collections_transform():
  13. return parse(
  14. """
  15. class defaultdict(dict):
  16. default_factory = None
  17. def __missing__(self, key): pass
  18. def __getitem__(self, key): return default_factory
  19. """
  20. + _deque_mock()
  21. + _ordered_dict_mock()
  22. )
  23. def _deque_mock():
  24. base_deque_class = """
  25. class deque(object):
  26. maxlen = 0
  27. def __init__(self, iterable=None, maxlen=None):
  28. self.iterable = iterable or []
  29. def append(self, x): pass
  30. def appendleft(self, x): pass
  31. def clear(self): pass
  32. def count(self, x): return 0
  33. def extend(self, iterable): pass
  34. def extendleft(self, iterable): pass
  35. def pop(self): return self.iterable[0]
  36. def popleft(self): return self.iterable[0]
  37. def remove(self, value): pass
  38. def reverse(self): return reversed(self.iterable)
  39. def rotate(self, n=1): return self
  40. def __iter__(self): return self
  41. def __reversed__(self): return self.iterable[::-1]
  42. def __getitem__(self, index): return self.iterable[index]
  43. def __setitem__(self, index, value): pass
  44. def __delitem__(self, index): pass
  45. def __bool__(self): return bool(self.iterable)
  46. def __nonzero__(self): return bool(self.iterable)
  47. def __contains__(self, o): return o in self.iterable
  48. def __len__(self): return len(self.iterable)
  49. def __copy__(self): return deque(self.iterable)
  50. def copy(self): return deque(self.iterable)
  51. def index(self, x, start=0, end=0): return 0
  52. def insert(self, i, x): pass
  53. def __add__(self, other): pass
  54. def __iadd__(self, other): pass
  55. def __mul__(self, other): pass
  56. def __imul__(self, other): pass
  57. def __rmul__(self, other): pass"""
  58. if PY39_PLUS:
  59. base_deque_class += """
  60. @classmethod
  61. def __class_getitem__(self, item): return cls"""
  62. return base_deque_class
  63. def _ordered_dict_mock():
  64. base_ordered_dict_class = """
  65. class OrderedDict(dict):
  66. def __reversed__(self): return self[::-1]
  67. def move_to_end(self, key, last=False): pass"""
  68. if PY39_PLUS:
  69. base_ordered_dict_class += """
  70. @classmethod
  71. def __class_getitem__(cls, item): return cls"""
  72. return base_ordered_dict_class
  73. register_module_extender(AstroidManager(), "collections", _collections_transform)
  74. def _looks_like_subscriptable(node: ClassDef) -> bool:
  75. """
  76. Returns True if the node corresponds to a ClassDef of the Collections.abc module
  77. that supports subscripting.
  78. :param node: ClassDef node
  79. """
  80. if node.qname().startswith("_collections") or node.qname().startswith(
  81. "collections"
  82. ):
  83. try:
  84. node.getattr("__class_getitem__")
  85. return True
  86. except AttributeInferenceError:
  87. pass
  88. return False
  89. CLASS_GET_ITEM_TEMPLATE = """
  90. @classmethod
  91. def __class_getitem__(cls, item):
  92. return cls
  93. """
  94. def easy_class_getitem_inference(node, context: InferenceContext | None = None):
  95. # Here __class_getitem__ exists but is quite a mess to infer thus
  96. # put an easy inference tip
  97. func_to_add = extract_node(CLASS_GET_ITEM_TEMPLATE)
  98. node.locals["__class_getitem__"] = [func_to_add]
  99. if PY39_PLUS:
  100. # Starting with Python39 some objects of the collection module are subscriptable
  101. # thanks to the __class_getitem__ method but the way it is implemented in
  102. # _collection_abc makes it difficult to infer. (We would have to handle AssignName inference in the
  103. # getitem method of the ClassDef class) Instead we put here a mock of the __class_getitem__ method
  104. AstroidManager().register_transform(
  105. ClassDef, easy_class_getitem_inference, _looks_like_subscriptable
  106. )