brain_collections.py 4.7 KB

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