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