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