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