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