-
-
Notifications
You must be signed in to change notification settings - Fork 276
/
helpers.py
338 lines (284 loc) · 11.6 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Various helper utilities."""
from __future__ import annotations
import warnings
from collections.abc import Generator
from astroid import bases, manager, nodes, objects, raw_building, util
from astroid.context import CallContext, InferenceContext
from astroid.exceptions import (
AstroidTypeError,
AttributeInferenceError,
InferenceError,
MroError,
_NonDeducibleTypeHierarchy,
)
from astroid.nodes import scoped_nodes
from astroid.typing import InferenceResult
from astroid.util import safe_infer as real_safe_infer
def safe_infer(
node: nodes.NodeNG | bases.Proxy | util.UninferableBase,
context: InferenceContext | None = None,
) -> InferenceResult | None:
# When removing, also remove the real_safe_infer alias
warnings.warn(
"Import safe_infer from astroid.util; this shim in astroid.helpers will be removed.",
DeprecationWarning,
stacklevel=2,
)
return real_safe_infer(node, context=context)
def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef:
proxy = raw_building.build_class(cls_name)
proxy.parent = builtins
return proxy
def _function_type(
function: nodes.Lambda | nodes.FunctionDef | bases.UnboundMethod,
builtins: nodes.Module,
) -> nodes.ClassDef:
if isinstance(function, (scoped_nodes.Lambda, scoped_nodes.FunctionDef)):
if function.root().name == "builtins":
cls_name = "builtin_function_or_method"
else:
cls_name = "function"
elif isinstance(function, bases.BoundMethod):
cls_name = "method"
else:
cls_name = "function"
return _build_proxy_class(cls_name, builtins)
def _object_type(
node: InferenceResult, context: InferenceContext | None = None
) -> Generator[InferenceResult | None, None, None]:
astroid_manager = manager.AstroidManager()
builtins = astroid_manager.builtins_module
context = context or InferenceContext()
for inferred in node.infer(context=context):
if isinstance(inferred, scoped_nodes.ClassDef):
if inferred.newstyle:
metaclass = inferred.metaclass(context=context)
if metaclass:
yield metaclass
continue
yield builtins.getattr("type")[0]
elif isinstance(
inferred,
(scoped_nodes.Lambda, bases.UnboundMethod, scoped_nodes.FunctionDef),
):
yield _function_type(inferred, builtins)
elif isinstance(inferred, scoped_nodes.Module):
yield _build_proxy_class("module", builtins)
elif isinstance(inferred, nodes.Unknown):
raise InferenceError
elif isinstance(inferred, util.UninferableBase):
yield inferred
elif isinstance(inferred, (bases.Proxy, nodes.Slice, objects.Super)):
yield inferred._proxied
else: # pragma: no cover
raise AssertionError(f"We don't handle {type(inferred)} currently")
def object_type(
node: InferenceResult, context: InferenceContext | None = None
) -> InferenceResult | None:
"""Obtain the type of the given node.
This is used to implement the ``type`` builtin, which means that it's
used for inferring type calls, as well as used in a couple of other places
in the inference.
The node will be inferred first, so this function can support all
sorts of objects, as long as they support inference.
"""
try:
types = set(_object_type(node, context))
except InferenceError:
return util.Uninferable
if len(types) > 1 or not types:
return util.Uninferable
return next(iter(types))
def _object_type_is_subclass(
obj_type: InferenceResult | None,
class_or_seq: list[InferenceResult],
context: InferenceContext | None = None,
) -> util.UninferableBase | bool:
if isinstance(obj_type, util.UninferableBase) or not isinstance(
obj_type, nodes.ClassDef
):
return util.Uninferable
# Instances are not types
class_seq = [
item if not isinstance(item, bases.Instance) else util.Uninferable
for item in class_or_seq
]
# strict compatibility with issubclass
# issubclass(type, (object, 1)) evaluates to true
# issubclass(object, (1, type)) raises TypeError
for klass in class_seq:
if isinstance(klass, util.UninferableBase):
raise AstroidTypeError("arg 2 must be a type or tuple of types")
for obj_subclass in obj_type.mro():
if obj_subclass == klass:
return True
return False
def object_isinstance(
node: InferenceResult,
class_or_seq: list[InferenceResult],
context: InferenceContext | None = None,
) -> util.UninferableBase | bool:
"""Check if a node 'isinstance' any node in class_or_seq.
:raises AstroidTypeError: if the given ``classes_or_seq`` are not types
"""
obj_type = object_type(node, context)
if isinstance(obj_type, util.UninferableBase):
return util.Uninferable
return _object_type_is_subclass(obj_type, class_or_seq, context=context)
def object_issubclass(
node: nodes.NodeNG,
class_or_seq: list[InferenceResult],
context: InferenceContext | None = None,
) -> util.UninferableBase | bool:
"""Check if a type is a subclass of any node in class_or_seq.
:raises AstroidTypeError: if the given ``classes_or_seq`` are not types
:raises AstroidError: if the type of the given node cannot be inferred
or its type's mro doesn't work
"""
if not isinstance(node, nodes.ClassDef):
raise TypeError(f"{node} needs to be a ClassDef node")
return _object_type_is_subclass(node, class_or_seq, context=context)
def has_known_bases(klass, context: InferenceContext | None = None) -> bool:
"""Return whether all base classes of a class could be inferred."""
try:
return klass._all_bases_known
except AttributeError:
pass
for base in klass.bases:
result = real_safe_infer(base, context=context)
# TODO: check for A->B->A->B pattern in class structure too?
if (
not isinstance(result, scoped_nodes.ClassDef)
or result is klass
or not has_known_bases(result, context=context)
):
klass._all_bases_known = False
return False
klass._all_bases_known = True
return True
def _type_check(type1, type2) -> bool:
if not all(map(has_known_bases, (type1, type2))):
raise _NonDeducibleTypeHierarchy
if not all([type1.newstyle, type2.newstyle]):
return False
try:
return type1 in type2.mro()[:-1]
except MroError as e:
# The MRO is invalid.
raise _NonDeducibleTypeHierarchy from e
def is_subtype(type1, type2) -> bool:
"""Check if *type1* is a subtype of *type2*."""
return _type_check(type1=type2, type2=type1)
def is_supertype(type1, type2) -> bool:
"""Check if *type2* is a supertype of *type1*."""
return _type_check(type1, type2)
def class_instance_as_index(node: bases.Instance) -> nodes.Const | None:
"""Get the value as an index for the given instance.
If an instance provides an __index__ method, then it can
be used in some scenarios where an integer is expected,
for instance when multiplying or subscripting a list.
"""
context = InferenceContext()
try:
for inferred in node.igetattr("__index__", context=context):
if not isinstance(inferred, bases.BoundMethod):
continue
context.boundnode = node
context.callcontext = CallContext(args=[], callee=inferred)
for result in inferred.infer_call_result(node, context=context):
if isinstance(result, nodes.Const) and isinstance(result.value, int):
return result
except InferenceError:
pass
return None
def object_len(node, context: InferenceContext | None = None):
"""Infer length of given node object.
:param Union[nodes.ClassDef, nodes.Instance] node:
:param node: Node to infer length of
:raises AstroidTypeError: If an invalid node is returned
from __len__ method or no __len__ method exists
:raises InferenceError: If the given node cannot be inferred
or if multiple nodes are inferred or if the code executed in python
would result in a infinite recursive check for length
:rtype int: Integer length of node
"""
# pylint: disable=import-outside-toplevel; circular import
from astroid.objects import FrozenSet
inferred_node = real_safe_infer(node, context=context)
# prevent self referential length calls from causing a recursion error
# see https://github.com/pylint-dev/astroid/issues/777
node_frame = node.frame()
if (
isinstance(node_frame, scoped_nodes.FunctionDef)
and node_frame.name == "__len__"
and isinstance(inferred_node, bases.Proxy)
and inferred_node._proxied == node_frame.parent
):
message = (
"Self referential __len__ function will "
"cause a RecursionError on line {} of {}".format(
node.lineno, node.root().file
)
)
raise InferenceError(message)
if inferred_node is None or isinstance(inferred_node, util.UninferableBase):
raise InferenceError(node=node)
if isinstance(inferred_node, nodes.Const) and isinstance(
inferred_node.value, (bytes, str)
):
return len(inferred_node.value)
if isinstance(inferred_node, (nodes.List, nodes.Set, nodes.Tuple, FrozenSet)):
return len(inferred_node.elts)
if isinstance(inferred_node, nodes.Dict):
return len(inferred_node.items)
node_type = object_type(inferred_node, context=context)
if not node_type:
raise InferenceError(node=node)
try:
len_call = next(node_type.igetattr("__len__", context=context))
except StopIteration as e:
raise AstroidTypeError(str(e)) from e
except AttributeInferenceError as e:
raise AstroidTypeError(
f"object of type '{node_type.pytype()}' has no len()"
) from e
inferred = len_call.infer_call_result(node, context)
if isinstance(inferred, util.UninferableBase):
raise InferenceError(node=node, context=context)
result_of_len = next(inferred, None)
if (
isinstance(result_of_len, nodes.Const)
and result_of_len.pytype() == "builtins.int"
):
return result_of_len.value
if (
result_of_len is None
or isinstance(result_of_len, bases.Instance)
and result_of_len.is_subtype_of("builtins.int")
):
# Fake a result as we don't know the arguments of the instance call.
return 0
raise AstroidTypeError(
f"'{result_of_len}' object cannot be interpreted as an integer"
)
def _higher_function_scope(node: nodes.NodeNG) -> nodes.FunctionDef | None:
"""Search for the first function which encloses the given
scope.
This can be used for looking up in that function's
scope, in case looking up in a lower scope for a particular
name fails.
:param node: A scope node.
:returns:
``None``, if no parent function scope was found,
otherwise an instance of :class:`astroid.nodes.scoped_nodes.Function`,
which encloses the given node.
"""
current = node
while current.parent and not isinstance(current.parent, nodes.FunctionDef):
current = current.parent
if current and current.parent:
return current.parent
return None