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
5"""Astroid hooks for various builtins."""
6
7from __future__ import annotations
8
9import itertools
10from collections.abc import Callable, Iterable, Iterator
11from functools import partial
12from typing import TYPE_CHECKING, Any, NoReturn, Union, cast
13
14from astroid import arguments, helpers, nodes, objects, util
15from astroid.builder import AstroidBuilder
16from astroid.context import InferenceContext
17from astroid.exceptions import (
18 AstroidTypeError,
19 AttributeInferenceError,
20 InferenceError,
21 MroError,
22 UseInferenceDefault,
23)
24from astroid.inference_tip import inference_tip
25from astroid.manager import AstroidManager
26from astroid.nodes import scoped_nodes
27from astroid.typing import (
28 ConstFactoryResult,
29 InferenceResult,
30 SuccessfulInferenceResult,
31)
32
33if TYPE_CHECKING:
34 from astroid.bases import Instance
35
36ContainerObjects = Union[
37 objects.FrozenSet,
38 objects.DictItems,
39 objects.DictKeys,
40 objects.DictValues,
41]
42
43BuiltContainers = Union[
44 type[tuple],
45 type[list],
46 type[set],
47 type[frozenset],
48]
49
50CopyResult = Union[
51 nodes.Dict,
52 nodes.List,
53 nodes.Set,
54 objects.FrozenSet,
55]
56
57OBJECT_DUNDER_NEW = "object.__new__"
58
59STR_CLASS = """
60class whatever(object):
61 def join(self, iterable):
62 return {rvalue}
63 def replace(self, old, new, count=None):
64 return {rvalue}
65 def format(self, *args, **kwargs):
66 return {rvalue}
67 def encode(self, encoding='ascii', errors=None):
68 return b''
69 def decode(self, encoding='ascii', errors=None):
70 return u''
71 def capitalize(self):
72 return {rvalue}
73 def title(self):
74 return {rvalue}
75 def lower(self):
76 return {rvalue}
77 def upper(self):
78 return {rvalue}
79 def swapcase(self):
80 return {rvalue}
81 def index(self, sub, start=None, end=None):
82 return 0
83 def find(self, sub, start=None, end=None):
84 return 0
85 def count(self, sub, start=None, end=None):
86 return 0
87 def strip(self, chars=None):
88 return {rvalue}
89 def lstrip(self, chars=None):
90 return {rvalue}
91 def rstrip(self, chars=None):
92 return {rvalue}
93 def rjust(self, width, fillchar=None):
94 return {rvalue}
95 def center(self, width, fillchar=None):
96 return {rvalue}
97 def ljust(self, width, fillchar=None):
98 return {rvalue}
99"""
100
101
102BYTES_CLASS = """
103class whatever(object):
104 def join(self, iterable):
105 return {rvalue}
106 def replace(self, old, new, count=None):
107 return {rvalue}
108 def decode(self, encoding='ascii', errors=None):
109 return u''
110 def capitalize(self):
111 return {rvalue}
112 def title(self):
113 return {rvalue}
114 def lower(self):
115 return {rvalue}
116 def upper(self):
117 return {rvalue}
118 def swapcase(self):
119 return {rvalue}
120 def index(self, sub, start=None, end=None):
121 return 0
122 def find(self, sub, start=None, end=None):
123 return 0
124 def count(self, sub, start=None, end=None):
125 return 0
126 def strip(self, chars=None):
127 return {rvalue}
128 def lstrip(self, chars=None):
129 return {rvalue}
130 def rstrip(self, chars=None):
131 return {rvalue}
132 def rjust(self, width, fillchar=None):
133 return {rvalue}
134 def center(self, width, fillchar=None):
135 return {rvalue}
136 def ljust(self, width, fillchar=None):
137 return {rvalue}
138"""
139
140
141def _use_default() -> NoReturn: # pragma: no cover
142 raise UseInferenceDefault()
143
144
145def _extend_string_class(class_node, code, rvalue):
146 """Function to extend builtin str/unicode class."""
147 code = code.format(rvalue=rvalue)
148 fake = AstroidBuilder(AstroidManager()).string_build(code)["whatever"]
149 for method in fake.mymethods():
150 method.parent = class_node
151 method.lineno = None
152 method.col_offset = None
153 if "__class__" in method.locals:
154 method.locals["__class__"] = [class_node]
155 class_node.locals[method.name] = [method]
156 method.parent = class_node
157
158
159def _extend_builtins(class_transforms):
160 builtin_ast = AstroidManager().builtins_module
161 for class_name, transform in class_transforms.items():
162 transform(builtin_ast[class_name])
163
164
165def on_bootstrap():
166 """Called by astroid_bootstrapping()."""
167 _extend_builtins(
168 {
169 "bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"),
170 "str": partial(_extend_string_class, code=STR_CLASS, rvalue="''"),
171 }
172 )
173
174
175def _builtin_filter_predicate(node, builtin_name) -> bool:
176 # pylint: disable = too-many-boolean-expressions
177 if (
178 builtin_name == "type"
179 and node.root().name == "re"
180 and isinstance(node.func, nodes.Name)
181 and node.func.name == "type"
182 and isinstance(node.parent, nodes.Assign)
183 and len(node.parent.targets) == 1
184 and isinstance(node.parent.targets[0], nodes.AssignName)
185 and node.parent.targets[0].name in {"Pattern", "Match"}
186 ):
187 # Handle re.Pattern and re.Match in brain_re
188 # Match these patterns from stdlib/re.py
189 # ```py
190 # Pattern = type(...)
191 # Match = type(...)
192 # ```
193 return False
194 if isinstance(node.func, nodes.Name):
195 return node.func.name == builtin_name
196 if isinstance(node.func, nodes.Attribute):
197 return (
198 node.func.attrname == "fromkeys"
199 and isinstance(node.func.expr, nodes.Name)
200 and node.func.expr.name == "dict"
201 )
202 return False
203
204
205def register_builtin_transform(
206 manager: AstroidManager, transform, builtin_name
207) -> None:
208 """Register a new transform function for the given *builtin_name*.
209
210 The transform function must accept two parameters, a node and
211 an optional context.
212 """
213
214 def _transform_wrapper(
215 node: nodes.Call, context: InferenceContext | None = None, **kwargs: Any
216 ) -> Iterator:
217 result = transform(node, context=context)
218 if result:
219 if not result.parent:
220 # Let the transformation function determine
221 # the parent for its result. Otherwise,
222 # we set it to be the node we transformed from.
223 result.parent = node
224
225 if result.lineno is None:
226 result.lineno = node.lineno
227 # Can be a 'Module' see https://github.com/pylint-dev/pylint/issues/4671
228 # We don't have a regression test on this one: tread carefully
229 if hasattr(result, "col_offset") and result.col_offset is None:
230 result.col_offset = node.col_offset
231 return iter([result])
232
233 manager.register_transform(
234 nodes.Call,
235 inference_tip(_transform_wrapper),
236 partial(_builtin_filter_predicate, builtin_name=builtin_name),
237 )
238
239
240def _container_generic_inference(
241 node: nodes.Call,
242 context: InferenceContext | None,
243 node_type: type[nodes.BaseContainer],
244 transform: Callable[[SuccessfulInferenceResult], nodes.BaseContainer | None],
245) -> nodes.BaseContainer:
246 args = node.args
247 if not args:
248 return node_type(
249 lineno=node.lineno,
250 col_offset=node.col_offset,
251 parent=node.parent,
252 end_lineno=node.end_lineno,
253 end_col_offset=node.end_col_offset,
254 )
255 if len(node.args) > 1:
256 raise UseInferenceDefault()
257
258 (arg,) = args
259 transformed = transform(arg)
260 if not transformed:
261 try:
262 inferred = next(arg.infer(context=context))
263 except (InferenceError, StopIteration) as exc:
264 raise UseInferenceDefault from exc
265 if isinstance(inferred, util.UninferableBase):
266 raise UseInferenceDefault
267 transformed = transform(inferred)
268 if not transformed or isinstance(transformed, util.UninferableBase):
269 raise UseInferenceDefault
270 return transformed
271
272
273def _container_generic_transform(
274 arg: SuccessfulInferenceResult,
275 context: InferenceContext | None,
276 klass: type[nodes.BaseContainer],
277 iterables: tuple[type[nodes.BaseContainer] | type[ContainerObjects], ...],
278 build_elts: BuiltContainers,
279) -> nodes.BaseContainer | None:
280 elts: Iterable | str | bytes
281
282 if isinstance(arg, klass):
283 return arg
284 if isinstance(arg, iterables):
285 arg = cast(Union[nodes.BaseContainer, ContainerObjects], arg)
286 if all(isinstance(elt, nodes.Const) for elt in arg.elts):
287 elts = [cast(nodes.Const, elt).value for elt in arg.elts]
288 else:
289 # TODO: Does not handle deduplication for sets.
290 elts = []
291 for element in arg.elts:
292 if not element:
293 continue
294 inferred = util.safe_infer(element, context=context)
295 if inferred:
296 evaluated_object = nodes.EvaluatedObject(
297 original=element, value=inferred
298 )
299 elts.append(evaluated_object)
300 elif isinstance(arg, nodes.Dict):
301 # Dicts need to have consts as strings already.
302 elts = [
303 item[0].value if isinstance(item[0], nodes.Const) else _use_default()
304 for item in arg.items
305 ]
306 elif isinstance(arg, nodes.Const) and isinstance(arg.value, (str, bytes)):
307 elts = arg.value
308 else:
309 return None
310 return klass.from_elements(elts=build_elts(elts))
311
312
313def _infer_builtin_container(
314 node: nodes.Call,
315 context: InferenceContext | None,
316 klass: type[nodes.BaseContainer],
317 iterables: tuple[type[nodes.NodeNG] | type[ContainerObjects], ...],
318 build_elts: BuiltContainers,
319) -> nodes.BaseContainer:
320 transform_func = partial(
321 _container_generic_transform,
322 context=context,
323 klass=klass,
324 iterables=iterables,
325 build_elts=build_elts,
326 )
327
328 return _container_generic_inference(node, context, klass, transform_func)
329
330
331# pylint: disable=invalid-name
332infer_tuple = partial(
333 _infer_builtin_container,
334 klass=nodes.Tuple,
335 iterables=(
336 nodes.List,
337 nodes.Set,
338 objects.FrozenSet,
339 objects.DictItems,
340 objects.DictKeys,
341 objects.DictValues,
342 ),
343 build_elts=tuple,
344)
345
346infer_list = partial(
347 _infer_builtin_container,
348 klass=nodes.List,
349 iterables=(
350 nodes.Tuple,
351 nodes.Set,
352 objects.FrozenSet,
353 objects.DictItems,
354 objects.DictKeys,
355 objects.DictValues,
356 ),
357 build_elts=list,
358)
359
360infer_set = partial(
361 _infer_builtin_container,
362 klass=nodes.Set,
363 iterables=(nodes.List, nodes.Tuple, objects.FrozenSet, objects.DictKeys),
364 build_elts=set,
365)
366
367infer_frozenset = partial(
368 _infer_builtin_container,
369 klass=objects.FrozenSet,
370 iterables=(nodes.List, nodes.Tuple, nodes.Set, objects.FrozenSet, objects.DictKeys),
371 build_elts=frozenset,
372)
373
374
375def _get_elts(arg, context):
376 def is_iterable(n) -> bool:
377 return isinstance(n, (nodes.List, nodes.Tuple, nodes.Set))
378
379 try:
380 inferred = next(arg.infer(context))
381 except (InferenceError, StopIteration) as exc:
382 raise UseInferenceDefault from exc
383 if isinstance(inferred, nodes.Dict):
384 items = inferred.items
385 elif is_iterable(inferred):
386 items = []
387 for elt in inferred.elts:
388 # If an item is not a pair of two items,
389 # then fallback to the default inference.
390 # Also, take in consideration only hashable items,
391 # tuples and consts. We are choosing Names as well.
392 if not is_iterable(elt):
393 raise UseInferenceDefault()
394 if len(elt.elts) != 2:
395 raise UseInferenceDefault()
396 if not isinstance(elt.elts[0], (nodes.Tuple, nodes.Const, nodes.Name)):
397 raise UseInferenceDefault()
398 items.append(tuple(elt.elts))
399 else:
400 raise UseInferenceDefault()
401 return items
402
403
404def infer_dict(node: nodes.Call, context: InferenceContext | None = None) -> nodes.Dict:
405 """Try to infer a dict call to a Dict node.
406
407 The function treats the following cases:
408
409 * dict()
410 * dict(mapping)
411 * dict(iterable)
412 * dict(iterable, **kwargs)
413 * dict(mapping, **kwargs)
414 * dict(**kwargs)
415
416 If a case can't be inferred, we'll fallback to default inference.
417 """
418 call = arguments.CallSite.from_call(node, context=context)
419 if call.has_invalid_arguments() or call.has_invalid_keywords():
420 raise UseInferenceDefault
421
422 args = call.positional_arguments
423 kwargs = list(call.keyword_arguments.items())
424
425 items: list[tuple[InferenceResult, InferenceResult]]
426 if not args and not kwargs:
427 # dict()
428 return nodes.Dict(
429 lineno=node.lineno,
430 col_offset=node.col_offset,
431 parent=node.parent,
432 end_lineno=node.end_lineno,
433 end_col_offset=node.end_col_offset,
434 )
435 if kwargs and not args:
436 # dict(a=1, b=2, c=4)
437 items = [(nodes.Const(key), value) for key, value in kwargs]
438 elif len(args) == 1 and kwargs:
439 # dict(some_iterable, b=2, c=4)
440 elts = _get_elts(args[0], context)
441 keys = [(nodes.Const(key), value) for key, value in kwargs]
442 items = elts + keys
443 elif len(args) == 1:
444 items = _get_elts(args[0], context)
445 else:
446 raise UseInferenceDefault()
447 value = nodes.Dict(
448 col_offset=node.col_offset,
449 lineno=node.lineno,
450 parent=node.parent,
451 end_lineno=node.end_lineno,
452 end_col_offset=node.end_col_offset,
453 )
454 value.postinit(items)
455 return value
456
457
458def infer_super(
459 node: nodes.Call, context: InferenceContext | None = None
460) -> objects.Super:
461 """Understand super calls.
462
463 There are some restrictions for what can be understood:
464
465 * unbounded super (one argument form) is not understood.
466
467 * if the super call is not inside a function (classmethod or method),
468 then the default inference will be used.
469
470 * if the super arguments can't be inferred, the default inference
471 will be used.
472 """
473 if len(node.args) == 1:
474 # Ignore unbounded super.
475 raise UseInferenceDefault
476
477 scope = node.scope()
478 if not isinstance(scope, nodes.FunctionDef):
479 # Ignore non-method uses of super.
480 raise UseInferenceDefault
481 if scope.type not in ("classmethod", "method"):
482 # Not interested in staticmethods.
483 raise UseInferenceDefault
484
485 cls = scoped_nodes.get_wrapping_class(scope)
486 assert cls is not None
487 if not node.args:
488 mro_pointer = cls
489 # In we are in a classmethod, the interpreter will fill
490 # automatically the class as the second argument, not an instance.
491 if scope.type == "classmethod":
492 mro_type = cls
493 else:
494 mro_type = cls.instantiate_class()
495 else:
496 try:
497 mro_pointer = next(node.args[0].infer(context=context))
498 except (InferenceError, StopIteration) as exc:
499 raise UseInferenceDefault from exc
500 try:
501 mro_type = next(node.args[1].infer(context=context))
502 except (InferenceError, StopIteration) as exc:
503 raise UseInferenceDefault from exc
504
505 if isinstance(mro_pointer, util.UninferableBase) or isinstance(
506 mro_type, util.UninferableBase
507 ):
508 # No way we could understand this.
509 raise UseInferenceDefault
510
511 super_obj = objects.Super(
512 mro_pointer=mro_pointer,
513 mro_type=mro_type,
514 self_class=cls,
515 scope=scope,
516 call=node,
517 )
518 super_obj.parent = node
519 return super_obj
520
521
522def _infer_getattr_args(node, context):
523 if len(node.args) not in (2, 3):
524 # Not a valid getattr call.
525 raise UseInferenceDefault
526
527 try:
528 obj = next(node.args[0].infer(context=context))
529 attr = next(node.args[1].infer(context=context))
530 except (InferenceError, StopIteration) as exc:
531 raise UseInferenceDefault from exc
532
533 if isinstance(obj, util.UninferableBase) or isinstance(attr, util.UninferableBase):
534 # If one of the arguments is something we can't infer,
535 # then also make the result of the getattr call something
536 # which is unknown.
537 return util.Uninferable, util.Uninferable
538
539 is_string = isinstance(attr, nodes.Const) and isinstance(attr.value, str)
540 if not is_string:
541 raise UseInferenceDefault
542
543 return obj, attr.value
544
545
546def infer_getattr(node, context: InferenceContext | None = None):
547 """Understand getattr calls.
548
549 If one of the arguments is an Uninferable object, then the
550 result will be an Uninferable object. Otherwise, the normal attribute
551 lookup will be done.
552 """
553 obj, attr = _infer_getattr_args(node, context)
554 if (
555 isinstance(obj, util.UninferableBase)
556 or isinstance(attr, util.UninferableBase)
557 or not hasattr(obj, "igetattr")
558 ):
559 return util.Uninferable
560
561 try:
562 return next(obj.igetattr(attr, context=context))
563 except (StopIteration, InferenceError, AttributeInferenceError):
564 if len(node.args) == 3:
565 # Try to infer the default and return it instead.
566 try:
567 return next(node.args[2].infer(context=context))
568 except (StopIteration, InferenceError) as exc:
569 raise UseInferenceDefault from exc
570
571 raise UseInferenceDefault
572
573
574def infer_hasattr(node, context: InferenceContext | None = None):
575 """Understand hasattr calls.
576
577 This always guarantees three possible outcomes for calling
578 hasattr: Const(False) when we are sure that the object
579 doesn't have the intended attribute, Const(True) when
580 we know that the object has the attribute and Uninferable
581 when we are unsure of the outcome of the function call.
582 """
583 try:
584 obj, attr = _infer_getattr_args(node, context)
585 if (
586 isinstance(obj, util.UninferableBase)
587 or isinstance(attr, util.UninferableBase)
588 or not hasattr(obj, "getattr")
589 ):
590 return util.Uninferable
591 obj.getattr(attr, context=context)
592 except UseInferenceDefault:
593 # Can't infer something from this function call.
594 return util.Uninferable
595 except AttributeInferenceError:
596 # Doesn't have it.
597 return nodes.Const(False)
598 return nodes.Const(True)
599
600
601def infer_callable(node, context: InferenceContext | None = None):
602 """Understand callable calls.
603
604 This follows Python's semantics, where an object
605 is callable if it provides an attribute __call__,
606 even though that attribute is something which can't be
607 called.
608 """
609 if len(node.args) != 1:
610 # Invalid callable call.
611 raise UseInferenceDefault
612
613 argument = node.args[0]
614 try:
615 inferred = next(argument.infer(context=context))
616 except (InferenceError, StopIteration):
617 return util.Uninferable
618 if isinstance(inferred, util.UninferableBase):
619 return util.Uninferable
620 return nodes.Const(inferred.callable())
621
622
623def infer_property(
624 node: nodes.Call, context: InferenceContext | None = None
625) -> objects.Property:
626 """Understand `property` class.
627
628 This only infers the output of `property`
629 call, not the arguments themselves.
630 """
631 if len(node.args) < 1:
632 # Invalid property call.
633 raise UseInferenceDefault
634
635 getter = node.args[0]
636 try:
637 inferred = next(getter.infer(context=context))
638 except (InferenceError, StopIteration) as exc:
639 raise UseInferenceDefault from exc
640
641 if not isinstance(inferred, (nodes.FunctionDef, nodes.Lambda)):
642 raise UseInferenceDefault
643
644 prop_func = objects.Property(
645 function=inferred,
646 name="<property>",
647 lineno=node.lineno,
648 col_offset=node.col_offset,
649 # ↓ semantically, the definition of the class of property isn't within
650 # node.frame. It's somewhere in the builtins module, but we are special
651 # casing it for each "property()" call, so we are making up the
652 # definition on the spot, ad-hoc.
653 parent=scoped_nodes.SYNTHETIC_ROOT,
654 )
655 prop_func.postinit(
656 body=[],
657 args=inferred.args,
658 doc_node=getattr(inferred, "doc_node", None),
659 )
660 return prop_func
661
662
663def infer_bool(node, context: InferenceContext | None = None):
664 """Understand bool calls."""
665 if len(node.args) > 1:
666 # Invalid bool call.
667 raise UseInferenceDefault
668
669 if not node.args:
670 return nodes.Const(False)
671
672 argument = node.args[0]
673 try:
674 inferred = next(argument.infer(context=context))
675 except (InferenceError, StopIteration):
676 return util.Uninferable
677 if isinstance(inferred, util.UninferableBase):
678 return util.Uninferable
679
680 bool_value = inferred.bool_value(context=context)
681 if isinstance(bool_value, util.UninferableBase):
682 return util.Uninferable
683 return nodes.Const(bool_value)
684
685
686def infer_type(node, context: InferenceContext | None = None):
687 """Understand the one-argument form of *type*."""
688 if len(node.args) != 1:
689 raise UseInferenceDefault
690
691 return helpers.object_type(node.args[0], context)
692
693
694def infer_slice(node, context: InferenceContext | None = None):
695 """Understand `slice` calls."""
696 args = node.args
697 if not 0 < len(args) <= 3:
698 raise UseInferenceDefault
699
700 infer_func = partial(util.safe_infer, context=context)
701 args = [infer_func(arg) for arg in args]
702 for arg in args:
703 if not arg or isinstance(arg, util.UninferableBase):
704 raise UseInferenceDefault
705 if not isinstance(arg, nodes.Const):
706 raise UseInferenceDefault
707 if not isinstance(arg.value, (type(None), int)):
708 raise UseInferenceDefault
709
710 if len(args) < 3:
711 # Make sure we have 3 arguments.
712 args.extend([None] * (3 - len(args)))
713
714 slice_node = nodes.Slice(
715 lineno=node.lineno,
716 col_offset=node.col_offset,
717 parent=node.parent,
718 end_lineno=node.end_lineno,
719 end_col_offset=node.end_col_offset,
720 )
721 slice_node.postinit(*args)
722 return slice_node
723
724
725def _infer_object__new__decorator(
726 node: nodes.ClassDef, context: InferenceContext | None = None, **kwargs: Any
727) -> Iterator[Instance]:
728 # Instantiate class immediately
729 # since that's what @object.__new__ does
730 return iter((node.instantiate_class(),))
731
732
733def _infer_object__new__decorator_check(node) -> bool:
734 """Predicate before inference_tip.
735
736 Check if the given ClassDef has an @object.__new__ decorator
737 """
738 if not node.decorators:
739 return False
740
741 for decorator in node.decorators.nodes:
742 if isinstance(decorator, nodes.Attribute):
743 if decorator.as_string() == OBJECT_DUNDER_NEW:
744 return True
745 return False
746
747
748def infer_issubclass(callnode, context: InferenceContext | None = None):
749 """Infer issubclass() calls.
750
751 :param nodes.Call callnode: an `issubclass` call
752 :param InferenceContext context: the context for the inference
753 :rtype nodes.Const: Boolean Const value of the `issubclass` call
754 :raises UseInferenceDefault: If the node cannot be inferred
755 """
756 call = arguments.CallSite.from_call(callnode, context=context)
757 if call.keyword_arguments:
758 # issubclass doesn't support keyword arguments
759 raise UseInferenceDefault("TypeError: issubclass() takes no keyword arguments")
760 if len(call.positional_arguments) != 2:
761 raise UseInferenceDefault(
762 f"Expected two arguments, got {len(call.positional_arguments)}"
763 )
764 # The left hand argument is the obj to be checked
765 obj_node, class_or_tuple_node = call.positional_arguments
766
767 try:
768 obj_type = next(obj_node.infer(context=context))
769 except (InferenceError, StopIteration) as exc:
770 raise UseInferenceDefault from exc
771 if not isinstance(obj_type, nodes.ClassDef):
772 raise UseInferenceDefault("TypeError: arg 1 must be class")
773
774 # The right hand argument is the class(es) that the given
775 # object is to be checked against.
776 try:
777 class_container = _class_or_tuple_to_container(
778 class_or_tuple_node, context=context
779 )
780 except InferenceError as exc:
781 raise UseInferenceDefault from exc
782 try:
783 issubclass_bool = helpers.object_issubclass(obj_type, class_container, context)
784 except AstroidTypeError as exc:
785 raise UseInferenceDefault("TypeError: " + str(exc)) from exc
786 except MroError as exc:
787 raise UseInferenceDefault from exc
788 return nodes.Const(issubclass_bool)
789
790
791def infer_isinstance(
792 callnode: nodes.Call, context: InferenceContext | None = None
793) -> nodes.Const:
794 """Infer isinstance calls.
795
796 :param nodes.Call callnode: an isinstance call
797 :raises UseInferenceDefault: If the node cannot be inferred
798 """
799 call = arguments.CallSite.from_call(callnode, context=context)
800 if call.keyword_arguments:
801 # isinstance doesn't support keyword arguments
802 raise UseInferenceDefault("TypeError: isinstance() takes no keyword arguments")
803 if len(call.positional_arguments) != 2:
804 raise UseInferenceDefault(
805 f"Expected two arguments, got {len(call.positional_arguments)}"
806 )
807 # The left hand argument is the obj to be checked
808 obj_node, class_or_tuple_node = call.positional_arguments
809 # The right hand argument is the class(es) that the given
810 # obj is to be check is an instance of
811 try:
812 class_container = _class_or_tuple_to_container(
813 class_or_tuple_node, context=context
814 )
815 except InferenceError as exc:
816 raise UseInferenceDefault from exc
817 try:
818 isinstance_bool = helpers.object_isinstance(obj_node, class_container, context)
819 except AstroidTypeError as exc:
820 raise UseInferenceDefault("TypeError: " + str(exc)) from exc
821 except MroError as exc:
822 raise UseInferenceDefault from exc
823 if isinstance(isinstance_bool, util.UninferableBase):
824 raise UseInferenceDefault
825 return nodes.Const(isinstance_bool)
826
827
828def _class_or_tuple_to_container(
829 node: InferenceResult, context: InferenceContext | None = None
830) -> list[InferenceResult]:
831 # Move inferences results into container
832 # to simplify later logic
833 # raises InferenceError if any of the inferences fall through
834 try:
835 node_infer = next(node.infer(context=context))
836 except StopIteration as e:
837 raise InferenceError(node=node, context=context) from e
838 # arg2 MUST be a type or a TUPLE of types
839 # for isinstance
840 if isinstance(node_infer, nodes.Tuple):
841 try:
842 class_container = [
843 next(node.infer(context=context)) for node in node_infer.elts
844 ]
845 except StopIteration as e:
846 raise InferenceError(node=node, context=context) from e
847 else:
848 class_container = [node_infer]
849 return class_container
850
851
852def infer_len(node, context: InferenceContext | None = None) -> nodes.Const:
853 """Infer length calls.
854
855 :param nodes.Call node: len call to infer
856 :param context.InferenceContext: node context
857 :rtype nodes.Const: a Const node with the inferred length, if possible
858 """
859 call = arguments.CallSite.from_call(node, context=context)
860 if call.keyword_arguments:
861 raise UseInferenceDefault("TypeError: len() must take no keyword arguments")
862 if len(call.positional_arguments) != 1:
863 raise UseInferenceDefault(
864 "TypeError: len() must take exactly one argument "
865 "({len}) given".format(len=len(call.positional_arguments))
866 )
867 [argument_node] = call.positional_arguments
868
869 try:
870 return nodes.Const(helpers.object_len(argument_node, context=context))
871 except (AstroidTypeError, InferenceError) as exc:
872 raise UseInferenceDefault(str(exc)) from exc
873
874
875def infer_str(node, context: InferenceContext | None = None) -> nodes.Const:
876 """Infer str() calls.
877
878 :param nodes.Call node: str() call to infer
879 :param context.InferenceContext: node context
880 :rtype nodes.Const: a Const containing an empty string
881 """
882 call = arguments.CallSite.from_call(node, context=context)
883 if call.keyword_arguments:
884 raise UseInferenceDefault("TypeError: str() must take no keyword arguments")
885 try:
886 return nodes.Const("")
887 except (AstroidTypeError, InferenceError) as exc:
888 raise UseInferenceDefault(str(exc)) from exc
889
890
891def infer_int(node, context: InferenceContext | None = None):
892 """Infer int() calls.
893
894 :param nodes.Call node: int() call to infer
895 :param context.InferenceContext: node context
896 :rtype nodes.Const: a Const containing the integer value of the int() call
897 """
898 call = arguments.CallSite.from_call(node, context=context)
899 if call.keyword_arguments:
900 raise UseInferenceDefault("TypeError: int() must take no keyword arguments")
901
902 if call.positional_arguments:
903 try:
904 first_value = next(call.positional_arguments[0].infer(context=context))
905 except (InferenceError, StopIteration) as exc:
906 raise UseInferenceDefault(str(exc)) from exc
907
908 if isinstance(first_value, util.UninferableBase):
909 raise UseInferenceDefault
910
911 if isinstance(first_value, nodes.Const) and isinstance(
912 first_value.value, (int, str)
913 ):
914 try:
915 actual_value = int(first_value.value)
916 except ValueError:
917 return nodes.Const(0)
918 return nodes.Const(actual_value)
919
920 return nodes.Const(0)
921
922
923def infer_dict_fromkeys(node, context: InferenceContext | None = None):
924 """Infer dict.fromkeys.
925
926 :param nodes.Call node: dict.fromkeys() call to infer
927 :param context.InferenceContext context: node context
928 :rtype nodes.Dict:
929 a Dictionary containing the values that astroid was able to infer.
930 In case the inference failed for any reason, an empty dictionary
931 will be inferred instead.
932 """
933
934 def _build_dict_with_elements(elements: list) -> nodes.Dict:
935 new_node = nodes.Dict(
936 col_offset=node.col_offset,
937 lineno=node.lineno,
938 parent=node.parent,
939 end_lineno=node.end_lineno,
940 end_col_offset=node.end_col_offset,
941 )
942 new_node.postinit(elements)
943 return new_node
944
945 call = arguments.CallSite.from_call(node, context=context)
946 if call.keyword_arguments:
947 raise UseInferenceDefault("TypeError: int() must take no keyword arguments")
948 if len(call.positional_arguments) not in {1, 2}:
949 raise UseInferenceDefault(
950 "TypeError: Needs between 1 and 2 positional arguments"
951 )
952
953 default = nodes.Const(None)
954 values = call.positional_arguments[0]
955 try:
956 inferred_values = next(values.infer(context=context))
957 except (InferenceError, StopIteration):
958 return _build_dict_with_elements([])
959 if inferred_values is util.Uninferable:
960 return _build_dict_with_elements([])
961
962 # Limit to a couple of potential values, as this can become pretty complicated
963 accepted_iterable_elements = (nodes.Const,)
964 if isinstance(inferred_values, (nodes.List, nodes.Set, nodes.Tuple)):
965 elements = inferred_values.elts
966 for element in elements:
967 if not isinstance(element, accepted_iterable_elements):
968 # Fallback to an empty dict
969 return _build_dict_with_elements([])
970
971 elements_with_value = [(element, default) for element in elements]
972 return _build_dict_with_elements(elements_with_value)
973 if isinstance(inferred_values, nodes.Const) and isinstance(
974 inferred_values.value, (str, bytes)
975 ):
976 elements_with_value = [
977 (nodes.Const(element), default) for element in inferred_values.value
978 ]
979 return _build_dict_with_elements(elements_with_value)
980 if isinstance(inferred_values, nodes.Dict):
981 keys = inferred_values.itered()
982 for key in keys:
983 if not isinstance(key, accepted_iterable_elements):
984 # Fallback to an empty dict
985 return _build_dict_with_elements([])
986
987 elements_with_value = [(element, default) for element in keys]
988 return _build_dict_with_elements(elements_with_value)
989
990 # Fallback to an empty dictionary
991 return _build_dict_with_elements([])
992
993
994def _infer_copy_method(
995 node: nodes.Call, context: InferenceContext | None = None, **kwargs: Any
996) -> Iterator[CopyResult]:
997 assert isinstance(node.func, nodes.Attribute)
998 inferred_orig, inferred_copy = itertools.tee(node.func.expr.infer(context=context))
999 if all(
1000 isinstance(
1001 inferred_node, (nodes.Dict, nodes.List, nodes.Set, objects.FrozenSet)
1002 )
1003 for inferred_node in inferred_orig
1004 ):
1005 return cast(Iterator[CopyResult], inferred_copy)
1006
1007 raise UseInferenceDefault
1008
1009
1010def _is_str_format_call(node: nodes.Call) -> bool:
1011 """Catch calls to str.format()."""
1012 if not isinstance(node.func, nodes.Attribute) or not node.func.attrname == "format":
1013 return False
1014
1015 if isinstance(node.func.expr, nodes.Name):
1016 value = util.safe_infer(node.func.expr)
1017 else:
1018 value = node.func.expr
1019
1020 return isinstance(value, nodes.Const) and isinstance(value.value, str)
1021
1022
1023def _infer_str_format_call(
1024 node: nodes.Call, context: InferenceContext | None = None, **kwargs: Any
1025) -> Iterator[ConstFactoryResult | util.UninferableBase]:
1026 """Return a Const node based on the template and passed arguments."""
1027 call = arguments.CallSite.from_call(node, context=context)
1028 assert isinstance(node.func, (nodes.Attribute, nodes.AssignAttr, nodes.DelAttr))
1029
1030 value: nodes.Const
1031 if isinstance(node.func.expr, nodes.Name):
1032 if not (inferred := util.safe_infer(node.func.expr)) or not isinstance(
1033 inferred, nodes.Const
1034 ):
1035 return iter([util.Uninferable])
1036 value = inferred
1037 elif isinstance(node.func.expr, nodes.Const):
1038 value = node.func.expr
1039 else: # pragma: no cover
1040 return iter([util.Uninferable])
1041
1042 format_template = value.value
1043
1044 # Get the positional arguments passed
1045 inferred_positional: list[nodes.Const] = []
1046 for i in call.positional_arguments:
1047 one_inferred = util.safe_infer(i, context)
1048 if not isinstance(one_inferred, nodes.Const):
1049 return iter([util.Uninferable])
1050 inferred_positional.append(one_inferred)
1051
1052 pos_values: list[str] = [i.value for i in inferred_positional]
1053
1054 # Get the keyword arguments passed
1055 inferred_keyword: dict[str, nodes.Const] = {}
1056 for k, v in call.keyword_arguments.items():
1057 one_inferred = util.safe_infer(v, context)
1058 if not isinstance(one_inferred, nodes.Const):
1059 return iter([util.Uninferable])
1060 inferred_keyword[k] = one_inferred
1061
1062 keyword_values: dict[str, str] = {k: v.value for k, v in inferred_keyword.items()}
1063
1064 try:
1065 formatted_string = format_template.format(*pos_values, **keyword_values)
1066 except (AttributeError, IndexError, KeyError, TypeError, ValueError):
1067 # AttributeError: named field in format string was not found in the arguments
1068 # IndexError: there are too few arguments to interpolate
1069 # TypeError: Unsupported format string
1070 # ValueError: Unknown format code
1071 return iter([util.Uninferable])
1072
1073 return iter([nodes.const_factory(formatted_string)])
1074
1075
1076def register(manager: AstroidManager) -> None:
1077 # Builtins inference
1078 register_builtin_transform(manager, infer_bool, "bool")
1079 register_builtin_transform(manager, infer_super, "super")
1080 register_builtin_transform(manager, infer_callable, "callable")
1081 register_builtin_transform(manager, infer_property, "property")
1082 register_builtin_transform(manager, infer_getattr, "getattr")
1083 register_builtin_transform(manager, infer_hasattr, "hasattr")
1084 register_builtin_transform(manager, infer_tuple, "tuple")
1085 register_builtin_transform(manager, infer_set, "set")
1086 register_builtin_transform(manager, infer_list, "list")
1087 register_builtin_transform(manager, infer_dict, "dict")
1088 register_builtin_transform(manager, infer_frozenset, "frozenset")
1089 register_builtin_transform(manager, infer_type, "type")
1090 register_builtin_transform(manager, infer_slice, "slice")
1091 register_builtin_transform(manager, infer_isinstance, "isinstance")
1092 register_builtin_transform(manager, infer_issubclass, "issubclass")
1093 register_builtin_transform(manager, infer_len, "len")
1094 register_builtin_transform(manager, infer_str, "str")
1095 register_builtin_transform(manager, infer_int, "int")
1096 register_builtin_transform(manager, infer_dict_fromkeys, "dict.fromkeys")
1097
1098 # Infer object.__new__ calls
1099 manager.register_transform(
1100 nodes.ClassDef,
1101 inference_tip(_infer_object__new__decorator),
1102 _infer_object__new__decorator_check,
1103 )
1104
1105 manager.register_transform(
1106 nodes.Call,
1107 inference_tip(_infer_copy_method),
1108 lambda node: isinstance(node.func, nodes.Attribute)
1109 and node.func.attrname == "copy",
1110 )
1111
1112 manager.register_transform(
1113 nodes.Call,
1114 inference_tip(_infer_str_format_call),
1115 _is_str_format_call,
1116 )