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