1# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
2# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
3# Copyright (c) 2013-2014 Google, Inc.
4# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
5# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
6# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
7# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
8# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
9# Copyright (c) 2017 Michał Masłowski <m.maslowski@clearcode.cc>
10# Copyright (c) 2017 Calen Pennington <cale@edx.org>
11# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
12# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com>
13# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com>
14# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
15# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
16# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
17# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
18# Copyright (c) 2020 Leandro T. C. Melo <ltcmelo@gmail.com>
19# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
20# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
21# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
22# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
23# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
24
25# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
26# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
27
28"""this module contains a set of functions to handle inference on astroid trees
29"""
30
31import ast
32import functools
33import itertools
34import operator
35from typing import Any, Callable, Dict, Iterable, Optional
36
37import wrapt
38
39from astroid import bases, decorators, helpers, nodes, protocols, util
40from astroid.context import (
41    CallContext,
42    InferenceContext,
43    bind_context_to_node,
44    copy_context,
45)
46from astroid.exceptions import (
47    AstroidBuildingError,
48    AstroidError,
49    AstroidIndexError,
50    AstroidTypeError,
51    AttributeInferenceError,
52    InferenceError,
53    NameInferenceError,
54    _NonDeducibleTypeHierarchy,
55)
56from astroid.interpreter import dunder_lookup
57from astroid.manager import AstroidManager
58
59# Prevents circular imports
60objects = util.lazy_import("objects")
61
62
63# .infer method ###############################################################
64
65
66def infer_end(self, context=None):
67    """Inference's end for nodes that yield themselves on inference
68
69    These are objects for which inference does not have any semantic,
70    such as Module or Consts.
71    """
72    yield self
73
74
75# We add ignores to all these assignments in this file
76# See https://github.com/python/mypy/issues/2427
77nodes.Module._infer = infer_end  # type: ignore[assignment]
78nodes.ClassDef._infer = infer_end  # type: ignore[assignment]
79nodes.Lambda._infer = infer_end  # type: ignore[assignment]
80nodes.Const._infer = infer_end  # type: ignore[assignment]
81nodes.Slice._infer = infer_end  # type: ignore[assignment]
82
83
84def _infer_sequence_helper(node, context=None):
85    """Infer all values based on _BaseContainer.elts"""
86    values = []
87
88    for elt in node.elts:
89        if isinstance(elt, nodes.Starred):
90            starred = helpers.safe_infer(elt.value, context)
91            if not starred:
92                raise InferenceError(node=node, context=context)
93            if not hasattr(starred, "elts"):
94                raise InferenceError(node=node, context=context)
95            values.extend(_infer_sequence_helper(starred))
96        elif isinstance(elt, nodes.NamedExpr):
97            value = helpers.safe_infer(elt.value, context)
98            if not value:
99                raise InferenceError(node=node, context=context)
100            values.append(value)
101        else:
102            values.append(elt)
103    return values
104
105
106@decorators.raise_if_nothing_inferred
107def infer_sequence(self, context=None):
108    has_starred_named_expr = any(
109        isinstance(e, (nodes.Starred, nodes.NamedExpr)) for e in self.elts
110    )
111    if has_starred_named_expr:
112        values = _infer_sequence_helper(self, context)
113        new_seq = type(self)(
114            lineno=self.lineno, col_offset=self.col_offset, parent=self.parent
115        )
116        new_seq.postinit(values)
117
118        yield new_seq
119    else:
120        yield self
121
122
123nodes.List._infer = infer_sequence  # type: ignore[assignment]
124nodes.Tuple._infer = infer_sequence  # type: ignore[assignment]
125nodes.Set._infer = infer_sequence  # type: ignore[assignment]
126
127
128def infer_map(self, context=None):
129    if not any(isinstance(k, nodes.DictUnpack) for k, _ in self.items):
130        yield self
131    else:
132        items = _infer_map(self, context)
133        new_seq = type(self)(self.lineno, self.col_offset, self.parent)
134        new_seq.postinit(list(items.items()))
135        yield new_seq
136
137
138def _update_with_replacement(lhs_dict, rhs_dict):
139    """Delete nodes that equate to duplicate keys
140
141    Since an astroid node doesn't 'equal' another node with the same value,
142    this function uses the as_string method to make sure duplicate keys
143    don't get through
144
145    Note that both the key and the value are astroid nodes
146
147    Fixes issue with DictUnpack causing duplicte keys
148    in inferred Dict items
149
150    :param dict(nodes.NodeNG, nodes.NodeNG) lhs_dict: Dictionary to 'merge' nodes into
151    :param dict(nodes.NodeNG, nodes.NodeNG) rhs_dict: Dictionary with nodes to pull from
152    :return dict(nodes.NodeNG, nodes.NodeNG): merged dictionary of nodes
153    """
154    combined_dict = itertools.chain(lhs_dict.items(), rhs_dict.items())
155    # Overwrite keys which have the same string values
156    string_map = {key.as_string(): (key, value) for key, value in combined_dict}
157    # Return to dictionary
158    return dict(string_map.values())
159
160
161def _infer_map(node, context):
162    """Infer all values based on Dict.items"""
163    values = {}
164    for name, value in node.items:
165        if isinstance(name, nodes.DictUnpack):
166            double_starred = helpers.safe_infer(value, context)
167            if not double_starred:
168                raise InferenceError
169            if not isinstance(double_starred, nodes.Dict):
170                raise InferenceError(node=node, context=context)
171            unpack_items = _infer_map(double_starred, context)
172            values = _update_with_replacement(values, unpack_items)
173        else:
174            key = helpers.safe_infer(name, context=context)
175            value = helpers.safe_infer(value, context=context)
176            if any(not elem for elem in (key, value)):
177                raise InferenceError(node=node, context=context)
178            values = _update_with_replacement(values, {key: value})
179    return values
180
181
182nodes.Dict._infer = infer_map  # type: ignore[assignment]
183
184
185def _higher_function_scope(node):
186    """Search for the first function which encloses the given
187    scope. This can be used for looking up in that function's
188    scope, in case looking up in a lower scope for a particular
189    name fails.
190
191    :param node: A scope node.
192    :returns:
193        ``None``, if no parent function scope was found,
194        otherwise an instance of :class:`astroid.nodes.scoped_nodes.Function`,
195        which encloses the given node.
196    """
197    current = node
198    while current.parent and not isinstance(current.parent, nodes.FunctionDef):
199        current = current.parent
200    if current and current.parent:
201        return current.parent
202    return None
203
204
205def infer_name(self, context=None):
206    """infer a Name: use name lookup rules"""
207    frame, stmts = self.lookup(self.name)
208    if not stmts:
209        # Try to see if the name is enclosed in a nested function
210        # and use the higher (first function) scope for searching.
211        parent_function = _higher_function_scope(self.scope())
212        if parent_function:
213            _, stmts = parent_function.lookup(self.name)
214
215        if not stmts:
216            raise NameInferenceError(
217                name=self.name, scope=self.scope(), context=context
218            )
219    context = copy_context(context)
220    context.lookupname = self.name
221    return bases._infer_stmts(stmts, context, frame)
222
223
224# pylint: disable=no-value-for-parameter
225nodes.Name._infer = decorators.raise_if_nothing_inferred(
226    decorators.path_wrapper(infer_name)
227)
228nodes.AssignName.infer_lhs = infer_name  # won't work with a path wrapper
229
230
231@decorators.raise_if_nothing_inferred
232@decorators.path_wrapper
233def infer_call(self, context=None):
234    """infer a Call node by trying to guess what the function returns"""
235    callcontext = copy_context(context)
236    callcontext.boundnode = None
237    if context is not None:
238        callcontext.extra_context = _populate_context_lookup(self, context.clone())
239
240    for callee in self.func.infer(context):
241        if callee is util.Uninferable:
242            yield callee
243            continue
244        try:
245            if hasattr(callee, "infer_call_result"):
246                callcontext.callcontext = CallContext(
247                    args=self.args, keywords=self.keywords, callee=callee
248                )
249                yield from callee.infer_call_result(caller=self, context=callcontext)
250        except InferenceError:
251            continue
252    return dict(node=self, context=context)
253
254
255nodes.Call._infer = infer_call  # type: ignore[assignment]
256
257
258@decorators.raise_if_nothing_inferred
259@decorators.path_wrapper
260def infer_import(self, context=None, asname=True):
261    """infer an Import node: return the imported module/object"""
262    name = context.lookupname
263    if name is None:
264        raise InferenceError(node=self, context=context)
265
266    try:
267        if asname:
268            yield self.do_import_module(self.real_name(name))
269        else:
270            yield self.do_import_module(name)
271    except AstroidBuildingError as exc:
272        raise InferenceError(node=self, context=context) from exc
273
274
275nodes.Import._infer = infer_import
276
277
278@decorators.raise_if_nothing_inferred
279@decorators.path_wrapper
280def infer_import_from(self, context=None, asname=True):
281    """infer a ImportFrom node: return the imported module/object"""
282    name = context.lookupname
283    if name is None:
284        raise InferenceError(node=self, context=context)
285    if asname:
286        try:
287            name = self.real_name(name)
288        except AttributeInferenceError as exc:
289            # See https://github.com/PyCQA/pylint/issues/4692
290            raise InferenceError(node=self, context=context) from exc
291    try:
292        module = self.do_import_module()
293    except AstroidBuildingError as exc:
294        raise InferenceError(node=self, context=context) from exc
295
296    try:
297        context = copy_context(context)
298        context.lookupname = name
299        stmts = module.getattr(name, ignore_locals=module is self.root())
300        return bases._infer_stmts(stmts, context)
301    except AttributeInferenceError as error:
302        raise InferenceError(
303            str(error), target=self, attribute=name, context=context
304        ) from error
305
306
307nodes.ImportFrom._infer = infer_import_from  # type: ignore[assignment]
308
309
310def infer_attribute(self, context=None):
311    """infer an Attribute node by using getattr on the associated object"""
312    for owner in self.expr.infer(context):
313        if owner is util.Uninferable:
314            yield owner
315            continue
316
317        if not context:
318            context = InferenceContext()
319
320        old_boundnode = context.boundnode
321        try:
322            context.boundnode = owner
323            yield from owner.igetattr(self.attrname, context)
324        except (
325            AttributeInferenceError,
326            InferenceError,
327            AttributeError,
328        ):
329            pass
330        finally:
331            context.boundnode = old_boundnode
332    return dict(node=self, context=context)
333
334
335nodes.Attribute._infer = decorators.raise_if_nothing_inferred(
336    decorators.path_wrapper(infer_attribute)
337)
338# won't work with a path wrapper
339nodes.AssignAttr.infer_lhs = decorators.raise_if_nothing_inferred(infer_attribute)
340
341
342@decorators.raise_if_nothing_inferred
343@decorators.path_wrapper
344def infer_global(self, context=None):
345    if context.lookupname is None:
346        raise InferenceError(node=self, context=context)
347    try:
348        return bases._infer_stmts(self.root().getattr(context.lookupname), context)
349    except AttributeInferenceError as error:
350        raise InferenceError(
351            str(error), target=self, attribute=context.lookupname, context=context
352        ) from error
353
354
355nodes.Global._infer = infer_global  # type: ignore[assignment]
356
357
358_SUBSCRIPT_SENTINEL = object()
359
360
361def infer_subscript(self, context=None):
362    """Inference for subscripts
363
364    We're understanding if the index is a Const
365    or a slice, passing the result of inference
366    to the value's `getitem` method, which should
367    handle each supported index type accordingly.
368    """
369
370    found_one = False
371    for value in self.value.infer(context):
372        if value is util.Uninferable:
373            yield util.Uninferable
374            return None
375        for index in self.slice.infer(context):
376            if index is util.Uninferable:
377                yield util.Uninferable
378                return None
379
380            # Try to deduce the index value.
381            index_value = _SUBSCRIPT_SENTINEL
382            if value.__class__ == bases.Instance:
383                index_value = index
384            elif index.__class__ == bases.Instance:
385                instance_as_index = helpers.class_instance_as_index(index)
386                if instance_as_index:
387                    index_value = instance_as_index
388            else:
389                index_value = index
390
391            if index_value is _SUBSCRIPT_SENTINEL:
392                raise InferenceError(node=self, context=context)
393
394            try:
395                assigned = value.getitem(index_value, context)
396            except (
397                AstroidTypeError,
398                AstroidIndexError,
399                AttributeInferenceError,
400                AttributeError,
401            ) as exc:
402                raise InferenceError(node=self, context=context) from exc
403
404            # Prevent inferring if the inferred subscript
405            # is the same as the original subscripted object.
406            if self is assigned or assigned is util.Uninferable:
407                yield util.Uninferable
408                return None
409            yield from assigned.infer(context)
410            found_one = True
411
412    if found_one:
413        return dict(node=self, context=context)
414    return None
415
416
417nodes.Subscript._infer = decorators.raise_if_nothing_inferred(  # type: ignore[assignment]
418    decorators.path_wrapper(infer_subscript)
419)
420nodes.Subscript.infer_lhs = decorators.raise_if_nothing_inferred(infer_subscript)
421
422
423@decorators.raise_if_nothing_inferred
424@decorators.path_wrapper
425def _infer_boolop(self, context=None):
426    """Infer a boolean operation (and / or / not).
427
428    The function will calculate the boolean operation
429    for all pairs generated through inference for each component
430    node.
431    """
432    values = self.values
433    if self.op == "or":
434        predicate = operator.truth
435    else:
436        predicate = operator.not_
437
438    try:
439        values = [value.infer(context=context) for value in values]
440    except InferenceError:
441        yield util.Uninferable
442        return None
443
444    for pair in itertools.product(*values):
445        if any(item is util.Uninferable for item in pair):
446            # Can't infer the final result, just yield Uninferable.
447            yield util.Uninferable
448            continue
449
450        bool_values = [item.bool_value() for item in pair]
451        if any(item is util.Uninferable for item in bool_values):
452            # Can't infer the final result, just yield Uninferable.
453            yield util.Uninferable
454            continue
455
456        # Since the boolean operations are short circuited operations,
457        # this code yields the first value for which the predicate is True
458        # and if no value respected the predicate, then the last value will
459        # be returned (or Uninferable if there was no last value).
460        # This is conforming to the semantics of `and` and `or`:
461        #   1 and 0 -> 1
462        #   0 and 1 -> 0
463        #   1 or 0 -> 1
464        #   0 or 1 -> 1
465        value = util.Uninferable
466        for value, bool_value in zip(pair, bool_values):
467            if predicate(bool_value):
468                yield value
469                break
470        else:
471            yield value
472
473    return dict(node=self, context=context)
474
475
476nodes.BoolOp._infer = _infer_boolop
477
478
479# UnaryOp, BinOp and AugAssign inferences
480
481
482def _filter_operation_errors(self, infer_callable, context, error):
483    for result in infer_callable(self, context):
484        if isinstance(result, error):
485            # For the sake of .infer(), we don't care about operation
486            # errors, which is the job of pylint. So return something
487            # which shows that we can't infer the result.
488            yield util.Uninferable
489        else:
490            yield result
491
492
493def _infer_unaryop(self, context=None):
494    """Infer what an UnaryOp should return when evaluated."""
495    for operand in self.operand.infer(context):
496        try:
497            yield operand.infer_unary_op(self.op)
498        except TypeError as exc:
499            # The operand doesn't support this operation.
500            yield util.BadUnaryOperationMessage(operand, self.op, exc)
501        except AttributeError as exc:
502            meth = protocols.UNARY_OP_METHOD[self.op]
503            if meth is None:
504                # `not node`. Determine node's boolean
505                # value and negate its result, unless it is
506                # Uninferable, which will be returned as is.
507                bool_value = operand.bool_value()
508                if bool_value is not util.Uninferable:
509                    yield nodes.const_factory(not bool_value)
510                else:
511                    yield util.Uninferable
512            else:
513                if not isinstance(operand, (bases.Instance, nodes.ClassDef)):
514                    # The operation was used on something which
515                    # doesn't support it.
516                    yield util.BadUnaryOperationMessage(operand, self.op, exc)
517                    continue
518
519                try:
520                    try:
521                        methods = dunder_lookup.lookup(operand, meth)
522                    except AttributeInferenceError:
523                        yield util.BadUnaryOperationMessage(operand, self.op, exc)
524                        continue
525
526                    meth = methods[0]
527                    inferred = next(meth.infer(context=context), None)
528                    if inferred is util.Uninferable or not inferred.callable():
529                        continue
530
531                    context = copy_context(context)
532                    context.boundnode = operand
533                    context.callcontext = CallContext(args=[], callee=inferred)
534
535                    call_results = inferred.infer_call_result(self, context=context)
536                    result = next(call_results, None)
537                    if result is None:
538                        # Failed to infer, return the same type.
539                        yield operand
540                    else:
541                        yield result
542                except AttributeInferenceError as exc:
543                    # The unary operation special method was not found.
544                    yield util.BadUnaryOperationMessage(operand, self.op, exc)
545                except InferenceError:
546                    yield util.Uninferable
547
548
549@decorators.raise_if_nothing_inferred
550@decorators.path_wrapper
551def infer_unaryop(self, context=None):
552    """Infer what an UnaryOp should return when evaluated."""
553    yield from _filter_operation_errors(
554        self, _infer_unaryop, context, util.BadUnaryOperationMessage
555    )
556    return dict(node=self, context=context)
557
558
559nodes.UnaryOp._infer_unaryop = _infer_unaryop
560nodes.UnaryOp._infer = infer_unaryop
561
562
563def _is_not_implemented(const):
564    """Check if the given const node is NotImplemented."""
565    return isinstance(const, nodes.Const) and const.value is NotImplemented
566
567
568def _invoke_binop_inference(instance, opnode, op, other, context, method_name):
569    """Invoke binary operation inference on the given instance."""
570    methods = dunder_lookup.lookup(instance, method_name)
571    context = bind_context_to_node(context, instance)
572    method = methods[0]
573    context.callcontext.callee = method
574    try:
575        inferred = next(method.infer(context=context))
576    except StopIteration as e:
577        raise InferenceError(node=method, context=context) from e
578    if inferred is util.Uninferable:
579        raise InferenceError
580    return instance.infer_binary_op(opnode, op, other, context, inferred)
581
582
583def _aug_op(instance, opnode, op, other, context, reverse=False):
584    """Get an inference callable for an augmented binary operation."""
585    method_name = protocols.AUGMENTED_OP_METHOD[op]
586    return functools.partial(
587        _invoke_binop_inference,
588        instance=instance,
589        op=op,
590        opnode=opnode,
591        other=other,
592        context=context,
593        method_name=method_name,
594    )
595
596
597def _bin_op(instance, opnode, op, other, context, reverse=False):
598    """Get an inference callable for a normal binary operation.
599
600    If *reverse* is True, then the reflected method will be used instead.
601    """
602    if reverse:
603        method_name = protocols.REFLECTED_BIN_OP_METHOD[op]
604    else:
605        method_name = protocols.BIN_OP_METHOD[op]
606    return functools.partial(
607        _invoke_binop_inference,
608        instance=instance,
609        op=op,
610        opnode=opnode,
611        other=other,
612        context=context,
613        method_name=method_name,
614    )
615
616
617def _get_binop_contexts(context, left, right):
618    """Get contexts for binary operations.
619
620    This will return two inference contexts, the first one
621    for x.__op__(y), the other one for y.__rop__(x), where
622    only the arguments are inversed.
623    """
624    # The order is important, since the first one should be
625    # left.__op__(right).
626    for arg in (right, left):
627        new_context = context.clone()
628        new_context.callcontext = CallContext(args=[arg])
629        new_context.boundnode = None
630        yield new_context
631
632
633def _same_type(type1, type2):
634    """Check if type1 is the same as type2."""
635    return type1.qname() == type2.qname()
636
637
638def _get_binop_flow(
639    left, left_type, binary_opnode, right, right_type, context, reverse_context
640):
641    """Get the flow for binary operations.
642
643    The rules are a bit messy:
644
645        * if left and right have the same type, then only one
646          method will be called, left.__op__(right)
647        * if left and right are unrelated typewise, then first
648          left.__op__(right) is tried and if this does not exist
649          or returns NotImplemented, then right.__rop__(left) is tried.
650        * if left is a subtype of right, then only left.__op__(right)
651          is tried.
652        * if left is a supertype of right, then right.__rop__(left)
653          is first tried and then left.__op__(right)
654    """
655    op = binary_opnode.op
656    if _same_type(left_type, right_type):
657        methods = [_bin_op(left, binary_opnode, op, right, context)]
658    elif helpers.is_subtype(left_type, right_type):
659        methods = [_bin_op(left, binary_opnode, op, right, context)]
660    elif helpers.is_supertype(left_type, right_type):
661        methods = [
662            _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True),
663            _bin_op(left, binary_opnode, op, right, context),
664        ]
665    else:
666        methods = [
667            _bin_op(left, binary_opnode, op, right, context),
668            _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True),
669        ]
670    return methods
671
672
673def _get_aug_flow(
674    left, left_type, aug_opnode, right, right_type, context, reverse_context
675):
676    """Get the flow for augmented binary operations.
677
678    The rules are a bit messy:
679
680        * if left and right have the same type, then left.__augop__(right)
681          is first tried and then left.__op__(right).
682        * if left and right are unrelated typewise, then
683          left.__augop__(right) is tried, then left.__op__(right)
684          is tried and then right.__rop__(left) is tried.
685        * if left is a subtype of right, then left.__augop__(right)
686          is tried and then left.__op__(right).
687        * if left is a supertype of right, then left.__augop__(right)
688          is tried, then right.__rop__(left) and then
689          left.__op__(right)
690    """
691    bin_op = aug_opnode.op.strip("=")
692    aug_op = aug_opnode.op
693    if _same_type(left_type, right_type):
694        methods = [
695            _aug_op(left, aug_opnode, aug_op, right, context),
696            _bin_op(left, aug_opnode, bin_op, right, context),
697        ]
698    elif helpers.is_subtype(left_type, right_type):
699        methods = [
700            _aug_op(left, aug_opnode, aug_op, right, context),
701            _bin_op(left, aug_opnode, bin_op, right, context),
702        ]
703    elif helpers.is_supertype(left_type, right_type):
704        methods = [
705            _aug_op(left, aug_opnode, aug_op, right, context),
706            _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True),
707            _bin_op(left, aug_opnode, bin_op, right, context),
708        ]
709    else:
710        methods = [
711            _aug_op(left, aug_opnode, aug_op, right, context),
712            _bin_op(left, aug_opnode, bin_op, right, context),
713            _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True),
714        ]
715    return methods
716
717
718def _infer_binary_operation(left, right, binary_opnode, context, flow_factory):
719    """Infer a binary operation between a left operand and a right operand
720
721    This is used by both normal binary operations and augmented binary
722    operations, the only difference is the flow factory used.
723    """
724
725    context, reverse_context = _get_binop_contexts(context, left, right)
726    left_type = helpers.object_type(left)
727    right_type = helpers.object_type(right)
728    methods = flow_factory(
729        left, left_type, binary_opnode, right, right_type, context, reverse_context
730    )
731    for method in methods:
732        try:
733            results = list(method())
734        except AttributeError:
735            continue
736        except AttributeInferenceError:
737            continue
738        except InferenceError:
739            yield util.Uninferable
740            return
741        else:
742            if any(result is util.Uninferable for result in results):
743                yield util.Uninferable
744                return
745
746            if all(map(_is_not_implemented, results)):
747                continue
748            not_implemented = sum(
749                1 for result in results if _is_not_implemented(result)
750            )
751            if not_implemented and not_implemented != len(results):
752                # Can't infer yet what this is.
753                yield util.Uninferable
754                return
755
756            yield from results
757            return
758    # The operation doesn't seem to be supported so let the caller know about it
759    yield util.BadBinaryOperationMessage(left_type, binary_opnode.op, right_type)
760
761
762def _infer_binop(self, context):
763    """Binary operation inference logic."""
764    left = self.left
765    right = self.right
766
767    # we use two separate contexts for evaluating lhs and rhs because
768    # 1. evaluating lhs may leave some undesired entries in context.path
769    #    which may not let us infer right value of rhs
770    context = context or InferenceContext()
771    lhs_context = copy_context(context)
772    rhs_context = copy_context(context)
773    lhs_iter = left.infer(context=lhs_context)
774    rhs_iter = right.infer(context=rhs_context)
775    for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
776        if any(value is util.Uninferable for value in (rhs, lhs)):
777            # Don't know how to process this.
778            yield util.Uninferable
779            return
780
781        try:
782            yield from _infer_binary_operation(lhs, rhs, self, context, _get_binop_flow)
783        except _NonDeducibleTypeHierarchy:
784            yield util.Uninferable
785
786
787@decorators.yes_if_nothing_inferred
788@decorators.path_wrapper
789def infer_binop(self, context=None):
790    return _filter_operation_errors(
791        self, _infer_binop, context, util.BadBinaryOperationMessage
792    )
793
794
795nodes.BinOp._infer_binop = _infer_binop
796nodes.BinOp._infer = infer_binop
797
798COMPARE_OPS: Dict[str, Callable[[Any, Any], bool]] = {
799    "==": operator.eq,
800    "!=": operator.ne,
801    "<": operator.lt,
802    "<=": operator.le,
803    ">": operator.gt,
804    ">=": operator.ge,
805    "in": lambda a, b: a in b,
806    "not in": lambda a, b: a not in b,
807}
808UNINFERABLE_OPS = {
809    "is",
810    "is not",
811}
812
813
814def _to_literal(node: nodes.NodeNG) -> Any:
815    # Can raise SyntaxError or ValueError from ast.literal_eval
816    # Can raise AttributeError from node.as_string() as not all nodes have a visitor
817    # Is this the stupidest idea or the simplest idea?
818    return ast.literal_eval(node.as_string())
819
820
821def _do_compare(
822    left_iter: Iterable[nodes.NodeNG], op: str, right_iter: Iterable[nodes.NodeNG]
823) -> "bool | type[util.Uninferable]":
824    """
825    If all possible combinations are either True or False, return that:
826    >>> _do_compare([1, 2], '<=', [3, 4])
827    True
828    >>> _do_compare([1, 2], '==', [3, 4])
829    False
830
831    If any item is uninferable, or if some combinations are True and some
832    are False, return Uninferable:
833    >>> _do_compare([1, 3], '<=', [2, 4])
834    util.Uninferable
835    """
836    retval = None
837    if op in UNINFERABLE_OPS:
838        return util.Uninferable
839    op_func = COMPARE_OPS[op]
840
841    for left, right in itertools.product(left_iter, right_iter):
842        if left is util.Uninferable or right is util.Uninferable:
843            return util.Uninferable
844
845        try:
846            left, right = _to_literal(left), _to_literal(right)
847        except (SyntaxError, ValueError, AttributeError):
848            return util.Uninferable
849
850        try:
851            expr = op_func(left, right)
852        except TypeError as exc:
853            raise AstroidTypeError from exc
854
855        if retval is None:
856            retval = expr
857        elif retval != expr:
858            return util.Uninferable
859            # (or both, but "True | False" is basically the same)
860
861    return retval  # it was all the same value
862
863
864def _infer_compare(
865    self: nodes.Compare, context: Optional[InferenceContext] = None
866) -> Any:
867    """Chained comparison inference logic."""
868    retval = True
869
870    ops = self.ops
871    left_node = self.left
872    lhs = list(left_node.infer(context=context))
873    # should we break early if first element is uninferable?
874    for op, right_node in ops:
875        # eagerly evaluate rhs so that values can be re-used as lhs
876        rhs = list(right_node.infer(context=context))
877        try:
878            retval = _do_compare(lhs, op, rhs)
879        except AstroidTypeError:
880            retval = util.Uninferable
881            break
882        if retval is not True:
883            break  # short-circuit
884        lhs = rhs  # continue
885    if retval is util.Uninferable:
886        yield retval
887    else:
888        yield nodes.Const(retval)
889
890
891nodes.Compare._infer = _infer_compare  # type: ignore[assignment]
892
893
894def _infer_augassign(self, context=None):
895    """Inference logic for augmented binary operations."""
896    if context is None:
897        context = InferenceContext()
898
899    rhs_context = context.clone()
900
901    lhs_iter = self.target.infer_lhs(context=context)
902    rhs_iter = self.value.infer(context=rhs_context)
903    for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
904        if any(value is util.Uninferable for value in (rhs, lhs)):
905            # Don't know how to process this.
906            yield util.Uninferable
907            return
908
909        try:
910            yield from _infer_binary_operation(
911                left=lhs,
912                right=rhs,
913                binary_opnode=self,
914                context=context,
915                flow_factory=_get_aug_flow,
916            )
917        except _NonDeducibleTypeHierarchy:
918            yield util.Uninferable
919
920
921@decorators.raise_if_nothing_inferred
922@decorators.path_wrapper
923def infer_augassign(self, context=None):
924    return _filter_operation_errors(
925        self, _infer_augassign, context, util.BadBinaryOperationMessage
926    )
927
928
929nodes.AugAssign._infer_augassign = _infer_augassign
930nodes.AugAssign._infer = infer_augassign
931
932# End of binary operation inference.
933
934
935@decorators.raise_if_nothing_inferred
936def infer_arguments(self, context=None):
937    name = context.lookupname
938    if name is None:
939        raise InferenceError(node=self, context=context)
940    return protocols._arguments_infer_argname(self, name, context)
941
942
943nodes.Arguments._infer = infer_arguments  # type: ignore[assignment]
944
945
946@decorators.raise_if_nothing_inferred
947@decorators.path_wrapper
948def infer_assign(self, context=None):
949    """infer a AssignName/AssignAttr: need to inspect the RHS part of the
950    assign node
951    """
952    if isinstance(self.parent, nodes.AugAssign):
953        return self.parent.infer(context)
954
955    stmts = list(self.assigned_stmts(context=context))
956    return bases._infer_stmts(stmts, context)
957
958
959nodes.AssignName._infer = infer_assign
960nodes.AssignAttr._infer = infer_assign
961
962
963@decorators.raise_if_nothing_inferred
964@decorators.path_wrapper
965def infer_empty_node(self, context=None):
966    if not self.has_underlying_object():
967        yield util.Uninferable
968    else:
969        try:
970            yield from AstroidManager().infer_ast_from_something(
971                self.object, context=context
972            )
973        except AstroidError:
974            yield util.Uninferable
975
976
977nodes.EmptyNode._infer = infer_empty_node  # type: ignore[assignment]
978
979
980@decorators.raise_if_nothing_inferred
981def infer_index(self, context=None):
982    return self.value.infer(context)
983
984
985nodes.Index._infer = infer_index  # type: ignore[assignment]
986
987
988def _populate_context_lookup(call, context):
989    # Allows context to be saved for later
990    # for inference inside a function
991    context_lookup = {}
992    if context is None:
993        return context_lookup
994    for arg in call.args:
995        if isinstance(arg, nodes.Starred):
996            context_lookup[arg.value] = context
997        else:
998            context_lookup[arg] = context
999    keywords = call.keywords if call.keywords is not None else []
1000    for keyword in keywords:
1001        context_lookup[keyword.value] = context
1002    return context_lookup
1003
1004
1005@decorators.raise_if_nothing_inferred
1006def infer_ifexp(self, context=None):
1007    """Support IfExp inference
1008
1009    If we can't infer the truthiness of the condition, we default
1010    to inferring both branches. Otherwise, we infer either branch
1011    depending on the condition.
1012    """
1013    both_branches = False
1014    # We use two separate contexts for evaluating lhs and rhs because
1015    # evaluating lhs may leave some undesired entries in context.path
1016    # which may not let us infer right value of rhs.
1017
1018    context = context or InferenceContext()
1019    lhs_context = copy_context(context)
1020    rhs_context = copy_context(context)
1021    try:
1022        test = next(self.test.infer(context=context.clone()))
1023    except (InferenceError, StopIteration):
1024        both_branches = True
1025    else:
1026        if test is not util.Uninferable:
1027            if test.bool_value():
1028                yield from self.body.infer(context=lhs_context)
1029            else:
1030                yield from self.orelse.infer(context=rhs_context)
1031        else:
1032            both_branches = True
1033    if both_branches:
1034        yield from self.body.infer(context=lhs_context)
1035        yield from self.orelse.infer(context=rhs_context)
1036
1037
1038nodes.IfExp._infer = infer_ifexp  # type: ignore[assignment]
1039
1040
1041# pylint: disable=dangerous-default-value
1042@wrapt.decorator
1043def _cached_generator(func, instance, args, kwargs, _cache={}):  # noqa: B006
1044    node = args[0]
1045    try:
1046        return iter(_cache[func, id(node)])
1047    except KeyError:
1048        result = func(*args, **kwargs)
1049        # Need to keep an iterator around
1050        original, copy = itertools.tee(result)
1051        _cache[func, id(node)] = list(copy)
1052        return original
1053
1054
1055# When inferring a property, we instantiate a new `objects.Property` object,
1056# which in turn, because it inherits from `FunctionDef`, sets itself in the locals
1057# of the wrapping frame. This means that everytime we infer a property, the locals
1058# are mutated with a new instance of the property. This is why we cache the result
1059# of the function's inference.
1060@_cached_generator
1061def infer_functiondef(self, context=None):
1062    if not self.decorators or not bases._is_property(self):
1063        yield self
1064        return dict(node=self, context=context)
1065
1066    prop_func = objects.Property(
1067        function=self,
1068        name=self.name,
1069        doc=self.doc,
1070        lineno=self.lineno,
1071        parent=self.parent,
1072        col_offset=self.col_offset,
1073    )
1074    prop_func.postinit(body=[], args=self.args)
1075    yield prop_func
1076    return dict(node=self, context=context)
1077
1078
1079nodes.FunctionDef._infer = infer_functiondef  # type: ignore[assignment]
1080