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