Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/nodes/_base_nodes.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

285 statements  

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"""This module contains some base nodes that can be inherited for the different nodes. 

6 

7Previously these were called Mixin nodes. 

8""" 

9 

10from __future__ import annotations 

11 

12import itertools 

13from collections.abc import Callable, Generator, Iterator 

14from functools import cached_property, lru_cache, partial 

15from typing import TYPE_CHECKING, Any, ClassVar 

16 

17from astroid import bases, nodes, util 

18from astroid.context import ( 

19 CallContext, 

20 InferenceContext, 

21 bind_context_to_node, 

22) 

23from astroid.exceptions import ( 

24 AttributeInferenceError, 

25 InferenceError, 

26) 

27from astroid.interpreter import dunder_lookup 

28from astroid.nodes.node_ng import NodeNG 

29from astroid.typing import InferenceResult 

30 

31if TYPE_CHECKING: 

32 from astroid.nodes.node_classes import LocalsDictNodeNG 

33 

34 GetFlowFactory = Callable[ 

35 [ 

36 InferenceResult, 

37 InferenceResult | None, 

38 nodes.AugAssign | nodes.BinOp, 

39 InferenceResult, 

40 InferenceResult | None, 

41 InferenceContext, 

42 InferenceContext, 

43 ], 

44 list[partial[Generator[InferenceResult]]], 

45 ] 

46 

47 

48class Statement(NodeNG): 

49 """Statement node adding a few attributes. 

50 

51 NOTE: This class is part of the public API of 'astroid.nodes'. 

52 """ 

53 

54 is_statement = True 

55 """Whether this node indicates a statement.""" 

56 

57 def next_sibling(self): 

58 """The next sibling statement node. 

59 

60 :returns: The next sibling statement node. 

61 :rtype: NodeNG or None 

62 """ 

63 stmts = self.parent.child_sequence(self) 

64 index = stmts.index(self) 

65 try: 

66 return stmts[index + 1] 

67 except IndexError: 

68 return None 

69 

70 def previous_sibling(self): 

71 """The previous sibling statement. 

72 

73 :returns: The previous sibling statement node. 

74 :rtype: NodeNG or None 

75 """ 

76 stmts = self.parent.child_sequence(self) 

77 index = stmts.index(self) 

78 if index >= 1: 

79 return stmts[index - 1] 

80 return None 

81 

82 

83class NoChildrenNode(NodeNG): 

84 """Base nodes for nodes with no children, e.g. Pass.""" 

85 

86 def get_children(self) -> Iterator[NodeNG]: 

87 yield from () 

88 

89 

90class FilterStmtsBaseNode(NodeNG): 

91 """Base node for statement filtering and assignment type.""" 

92 

93 def _get_filtered_stmts(self, _, node, _stmts, mystmt: Statement | None): 

94 """Method used in _filter_stmts to get statements and trigger break.""" 

95 if self.statement() is mystmt: 

96 # original node's statement is the assignment, only keep 

97 # current node (gen exp, list comp) 

98 return [node], True 

99 return _stmts, False 

100 

101 def assign_type(self): 

102 return self 

103 

104 

105class AssignTypeNode(NodeNG): 

106 """Base node for nodes that can 'assign' such as AnnAssign.""" 

107 

108 def assign_type(self): 

109 return self 

110 

111 def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt: Statement | None): 

112 """Method used in filter_stmts.""" 

113 if self is mystmt: 

114 return _stmts, True 

115 if self.statement() is mystmt: 

116 # original node's statement is the assignment, only keep 

117 # current node (gen exp, list comp) 

118 return [node], True 

119 return _stmts, False 

120 

121 

122class ParentAssignNode(AssignTypeNode): 

123 """Base node for nodes whose assign_type is determined by the parent node.""" 

124 

125 def assign_type(self): 

126 return self.parent.assign_type() 

127 

128 

129class ImportNode(FilterStmtsBaseNode, NoChildrenNode, Statement): 

130 """Base node for From and Import Nodes.""" 

131 

132 modname: str | None 

133 """The module that is being imported from. 

134 

135 This is ``None`` for relative imports. 

136 """ 

137 

138 names: list[tuple[str, str | None]] 

139 """What is being imported from the module. 

140 

141 Each entry is a :class:`tuple` of the name being imported, 

142 and the alias that the name is assigned to (if any). 

143 """ 

144 

145 def _infer_name(self, frame, name): 

146 try: 

147 return self.real_name(name) 

148 except AttributeInferenceError: 

149 return None 

150 

151 def do_import_module(self, modname: str | None = None) -> nodes.Module: 

152 """Return the ast for a module whose name is <modname> imported by <self>.""" 

153 mymodule = self.root() 

154 level: int | None = getattr(self, "level", None) # Import has no level 

155 if modname is None: 

156 modname = self.modname 

157 # If the module ImportNode is importing is a module with the same name 

158 # as the file that contains the ImportNode we don't want to use the cache 

159 # to make sure we use the import system to get the correct module. 

160 if ( 

161 modname 

162 # pylint: disable-next=no-member # pylint doesn't recognize type of mymodule 

163 and mymodule.relative_to_absolute_name(modname, level) == mymodule.name 

164 ): 

165 use_cache = False 

166 else: 

167 use_cache = True 

168 

169 # pylint: disable-next=no-member # pylint doesn't recognize type of mymodule 

170 return mymodule.import_module( 

171 modname, 

172 level=level, 

173 relative_only=bool(level and level >= 1), 

174 use_cache=use_cache, 

175 ) 

176 

177 def real_name(self, asname: str) -> str: 

178 """Get name from 'as' name.""" 

179 for name, _asname in self.names: 

180 if name == "*": 

181 return asname 

182 if not _asname: 

183 name = name.split(".", 1)[0] 

184 _asname = name 

185 if asname == _asname: 

186 return name 

187 raise AttributeInferenceError( 

188 "Could not find original name for {attribute} in {target!r}", 

189 target=self, 

190 attribute=asname, 

191 ) 

192 

193 

194class MultiLineBlockNode(NodeNG): 

195 """Base node for multi-line blocks, e.g. For and FunctionDef. 

196 

197 Note that this does not apply to every node with a `body` field. 

198 For instance, an If node has a multi-line body, but the body of an 

199 IfExpr is not multi-line, and hence cannot contain Return nodes, 

200 Assign nodes, etc. 

201 """ 

202 

203 _multi_line_block_fields: ClassVar[tuple[str, ...]] = () 

204 

205 @cached_property 

206 def _multi_line_blocks(self): 

207 return tuple(getattr(self, field) for field in self._multi_line_block_fields) 

208 

209 def _get_return_nodes_skip_functions(self): 

210 for block in self._multi_line_blocks: 

211 for child_node in block: 

212 if child_node.is_function: 

213 continue 

214 yield from child_node._get_return_nodes_skip_functions() 

215 

216 def _get_yield_nodes_skip_functions(self): 

217 for block in self._multi_line_blocks: 

218 for child_node in block: 

219 if child_node.is_function: 

220 continue 

221 yield from child_node._get_yield_nodes_skip_functions() 

222 

223 def _get_yield_nodes_skip_lambdas(self): 

224 for block in self._multi_line_blocks: 

225 for child_node in block: 

226 if child_node.is_lambda: 

227 continue 

228 yield from child_node._get_yield_nodes_skip_lambdas() 

229 

230 @cached_property 

231 def _assign_nodes_in_scope(self) -> list[nodes.Assign]: 

232 children_assign_nodes = ( 

233 child_node._assign_nodes_in_scope 

234 for block in self._multi_line_blocks 

235 for child_node in block 

236 ) 

237 return list(itertools.chain.from_iterable(children_assign_nodes)) 

238 

239 

240class MultiLineWithElseBlockNode(MultiLineBlockNode): 

241 """Base node for multi-line blocks that can have else statements.""" 

242 

243 body: list[NodeNG] 

244 """The contents of the block.""" 

245 

246 orelse: list[NodeNG] 

247 """The contents of the ``else`` block.""" 

248 

249 @cached_property 

250 def blockstart_tolineno(self): 

251 return self.lineno 

252 

253 def block_range(self, lineno: int) -> tuple[int, int]: 

254 """Get a range from the given line number to where this node ends. 

255 

256 :param lineno: The line number to start the range at. 

257 

258 :returns: The range of line numbers that this node belongs to, 

259 starting at the given line number. 

260 """ 

261 if lineno < self.fromlineno: 

262 return lineno, self.tolineno 

263 if lineno == self.body[0].fromlineno: 

264 return lineno, lineno 

265 if lineno <= self.body[-1].tolineno: 

266 return lineno, self.body[-1].tolineno 

267 return self._elsed_block_range(lineno, self.orelse, self.body[0].fromlineno - 1) 

268 

269 def _elsed_block_range( 

270 self, lineno: int, orelse: list[nodes.NodeNG], last: int | None = None 

271 ) -> tuple[int, int]: 

272 """Handle block line numbers range for try/finally, for, if and while 

273 statements. 

274 """ 

275 # If at the end of the node, return same line 

276 if lineno == self.tolineno: 

277 return lineno, lineno 

278 if orelse: 

279 # If the lineno is beyond the body of the node we check the orelse 

280 if lineno >= self.body[-1].tolineno + 1: 

281 # If the orelse has a scope of its own we determine the block range there 

282 if isinstance(orelse[0], MultiLineWithElseBlockNode): 

283 return orelse[0]._elsed_block_range(lineno, orelse[0].orelse) 

284 # Return last line of orelse 

285 return lineno, orelse[-1].tolineno 

286 # If the lineno is within the body we take the last line of the body 

287 return lineno, self.body[-1].tolineno 

288 return lineno, last or self.tolineno 

289 

290 

291class LookupMixIn(NodeNG): 

292 """Mixin to look up a name in the right scope.""" 

293 

294 @lru_cache # noqa 

295 def lookup(self, name: str) -> tuple[LocalsDictNodeNG, list[NodeNG]]: 

296 """Lookup where the given variable is assigned. 

297 

298 The lookup starts from self's scope. If self is not a frame itself 

299 and the name is found in the inner frame locals, statements will be 

300 filtered to remove ignorable statements according to self's location. 

301 

302 :param name: The name of the variable to find assignments for. 

303 

304 :returns: The scope node and the list of assignments associated to the 

305 given name according to the scope where it has been found (locals, 

306 globals or builtin). 

307 """ 

308 return self.scope().scope_lookup(self, name) 

309 

310 def ilookup(self, name): 

311 """Lookup the inferred values of the given variable. 

312 

313 :param name: The variable name to find values for. 

314 :type name: str 

315 

316 :returns: The inferred values of the statements returned from 

317 :meth:`lookup`. 

318 :rtype: Iterator 

319 """ 

320 frame, stmts = self.lookup(name) 

321 context = InferenceContext() 

322 return bases._infer_stmts(stmts, context, frame) 

323 

324 

325def _reflected_name(name) -> str: 

326 return "__r" + name[2:] 

327 

328 

329def _augmented_name(name) -> str: 

330 return "__i" + name[2:] 

331 

332 

333BIN_OP_METHOD = { 

334 "+": "__add__", 

335 "-": "__sub__", 

336 "/": "__truediv__", 

337 "//": "__floordiv__", 

338 "*": "__mul__", 

339 "**": "__pow__", 

340 "%": "__mod__", 

341 "&": "__and__", 

342 "|": "__or__", 

343 "^": "__xor__", 

344 "<<": "__lshift__", 

345 ">>": "__rshift__", 

346 "@": "__matmul__", 

347} 

348 

349REFLECTED_BIN_OP_METHOD = { 

350 key: _reflected_name(value) for (key, value) in BIN_OP_METHOD.items() 

351} 

352AUGMENTED_OP_METHOD = { 

353 key + "=": _augmented_name(value) for (key, value) in BIN_OP_METHOD.items() 

354} 

355 

356 

357class OperatorNode(NodeNG): 

358 @staticmethod 

359 def _filter_operation_errors( 

360 infer_callable: Callable[ 

361 [InferenceContext | None], 

362 Generator[InferenceResult | util.BadOperationMessage], 

363 ], 

364 context: InferenceContext | None, 

365 error: type[util.BadOperationMessage], 

366 ) -> Generator[InferenceResult]: 

367 for result in infer_callable(context): 

368 if isinstance(result, error): 

369 # For the sake of .infer(), we don't care about operation 

370 # errors, which is the job of a linter. So return something 

371 # which shows that we can't infer the result. 

372 yield util.Uninferable 

373 else: 

374 yield result 

375 

376 @staticmethod 

377 def _is_not_implemented(const) -> bool: 

378 """Check if the given const node is NotImplemented.""" 

379 return isinstance(const, nodes.Const) and const.value is NotImplemented 

380 

381 @staticmethod 

382 def _infer_old_style_string_formatting( 

383 instance: nodes.Const, other: nodes.NodeNG, context: InferenceContext 

384 ) -> tuple[util.UninferableBase | nodes.Const]: 

385 """Infer the result of '"string" % ...'. 

386 

387 TODO: Instead of returning Uninferable we should rely 

388 on the call to '%' to see if the result is actually uninferable. 

389 """ 

390 if isinstance(other, nodes.Tuple): 

391 if util.Uninferable in other.elts: 

392 return (util.Uninferable,) 

393 inferred_positional = [util.safe_infer(i, context) for i in other.elts] 

394 if all(isinstance(i, nodes.Const) for i in inferred_positional): 

395 values = tuple(i.value for i in inferred_positional) 

396 else: 

397 values = None 

398 elif isinstance(other, nodes.Dict): 

399 values: dict[Any, Any] = {} 

400 for pair in other.items: 

401 key = util.safe_infer(pair[0], context) 

402 if not isinstance(key, nodes.Const): 

403 return (util.Uninferable,) 

404 value = util.safe_infer(pair[1], context) 

405 if not isinstance(value, nodes.Const): 

406 return (util.Uninferable,) 

407 values[key.value] = value.value 

408 elif isinstance(other, nodes.Const): 

409 values = other.value 

410 else: 

411 return (util.Uninferable,) 

412 

413 # pylint: disable-next=import-outside-toplevel 

414 from astroid.protocols import _old_style_format_too_large 

415 

416 if _old_style_format_too_large(instance.value, values): 

417 return (util.Uninferable,) 

418 

419 try: 

420 return (nodes.const_factory(instance.value % values),) 

421 except (TypeError, KeyError, ValueError): 

422 return (util.Uninferable,) 

423 

424 @staticmethod 

425 def _invoke_binop_inference( 

426 instance: InferenceResult, 

427 opnode: nodes.AugAssign | nodes.BinOp, 

428 op: str, 

429 other: InferenceResult, 

430 context: InferenceContext, 

431 method_name: str, 

432 ) -> Generator[InferenceResult]: 

433 """Invoke binary operation inference on the given instance.""" 

434 methods = dunder_lookup.lookup(instance, method_name) 

435 context = bind_context_to_node(context, instance) 

436 method = methods[0] 

437 context.callcontext.callee = method 

438 

439 if ( 

440 isinstance(instance, nodes.Const) 

441 and isinstance(instance.value, (str, bytes)) 

442 and op == "%" 

443 ): 

444 return iter( 

445 OperatorNode._infer_old_style_string_formatting( 

446 instance, other, context 

447 ) 

448 ) 

449 

450 try: 

451 inferred = next(method.infer(context=context)) 

452 except StopIteration as e: 

453 raise InferenceError(node=method, context=context) from e 

454 if isinstance(inferred, util.UninferableBase): 

455 raise InferenceError 

456 if not isinstance( 

457 instance, 

458 (nodes.Const, nodes.Tuple, nodes.List, nodes.ClassDef, bases.Instance), 

459 ): 

460 raise InferenceError # pragma: no cover # Used as a failsafe 

461 return instance.infer_binary_op(opnode, op, other, context, inferred) 

462 

463 @staticmethod 

464 def _aug_op( 

465 instance: InferenceResult, 

466 opnode: nodes.AugAssign, 

467 op: str, 

468 other: InferenceResult, 

469 context: InferenceContext, 

470 reverse: bool = False, 

471 ) -> partial[Generator[InferenceResult]]: 

472 """Get an inference callable for an augmented binary operation.""" 

473 method_name = AUGMENTED_OP_METHOD[op] 

474 return partial( 

475 OperatorNode._invoke_binop_inference, 

476 instance=instance, 

477 op=op, 

478 opnode=opnode, 

479 other=other, 

480 context=context, 

481 method_name=method_name, 

482 ) 

483 

484 @staticmethod 

485 def _bin_op( 

486 instance: InferenceResult, 

487 opnode: nodes.AugAssign | nodes.BinOp, 

488 op: str, 

489 other: InferenceResult, 

490 context: InferenceContext, 

491 reverse: bool = False, 

492 ) -> partial[Generator[InferenceResult]]: 

493 """Get an inference callable for a normal binary operation. 

494 

495 If *reverse* is True, then the reflected method will be used instead. 

496 """ 

497 if reverse: 

498 method_name = REFLECTED_BIN_OP_METHOD[op] 

499 else: 

500 method_name = BIN_OP_METHOD[op] 

501 return partial( 

502 OperatorNode._invoke_binop_inference, 

503 instance=instance, 

504 op=op, 

505 opnode=opnode, 

506 other=other, 

507 context=context, 

508 method_name=method_name, 

509 ) 

510 

511 @staticmethod 

512 def _bin_op_or_union_type( 

513 left: bases.UnionType | nodes.ClassDef | nodes.Const, 

514 right: bases.UnionType | nodes.ClassDef | nodes.Const, 

515 ) -> Generator[InferenceResult]: 

516 """Create a new UnionType instance for binary or, e.g. int | str.""" 

517 yield bases.UnionType(left, right) 

518 

519 @staticmethod 

520 def _get_binop_contexts(context, left, right): 

521 """Get contexts for binary operations. 

522 

523 This will return two inference contexts, the first one 

524 for x.__op__(y), the other one for y.__rop__(x), where 

525 only the arguments are inversed. 

526 """ 

527 # The order is important, since the first one should be 

528 # left.__op__(right). 

529 for arg in (right, left): 

530 new_context = context.clone() 

531 new_context.callcontext = CallContext(args=[arg]) 

532 new_context.boundnode = None 

533 yield new_context 

534 

535 @staticmethod 

536 def _same_type(type1, type2) -> bool: 

537 """Check if type1 is the same as type2.""" 

538 return type1.qname() == type2.qname() 

539 

540 @staticmethod 

541 def _get_aug_flow( 

542 left: InferenceResult, 

543 left_type: InferenceResult | None, 

544 aug_opnode: nodes.AugAssign, 

545 right: InferenceResult, 

546 right_type: InferenceResult | None, 

547 context: InferenceContext, 

548 reverse_context: InferenceContext, 

549 ) -> list[partial[Generator[InferenceResult]]]: 

550 """Get the flow for augmented binary operations. 

551 

552 The rules are a bit messy: 

553 

554 * if left and right have the same type, then left.__augop__(right) 

555 is first tried and then left.__op__(right). 

556 * if left and right are unrelated typewise, then 

557 left.__augop__(right) is tried, then left.__op__(right) 

558 is tried and then right.__rop__(left) is tried. 

559 * if left is a subtype of right, then left.__augop__(right) 

560 is tried and then left.__op__(right). 

561 * if left is a supertype of right, then left.__augop__(right) 

562 is tried, then right.__rop__(left) and then 

563 left.__op__(right) 

564 """ 

565 from astroid import helpers # pylint: disable=import-outside-toplevel 

566 

567 bin_op = aug_opnode.op.strip("=") 

568 aug_op = aug_opnode.op 

569 if OperatorNode._same_type(left_type, right_type): 

570 methods = [ 

571 OperatorNode._aug_op(left, aug_opnode, aug_op, right, context), 

572 OperatorNode._bin_op(left, aug_opnode, bin_op, right, context), 

573 ] 

574 elif helpers.is_subtype(left_type, right_type): 

575 methods = [ 

576 OperatorNode._aug_op(left, aug_opnode, aug_op, right, context), 

577 OperatorNode._bin_op(left, aug_opnode, bin_op, right, context), 

578 ] 

579 elif helpers.is_supertype(left_type, right_type): 

580 methods = [ 

581 OperatorNode._aug_op(left, aug_opnode, aug_op, right, context), 

582 OperatorNode._bin_op( 

583 right, aug_opnode, bin_op, left, reverse_context, reverse=True 

584 ), 

585 OperatorNode._bin_op(left, aug_opnode, bin_op, right, context), 

586 ] 

587 else: 

588 methods = [ 

589 OperatorNode._aug_op(left, aug_opnode, aug_op, right, context), 

590 OperatorNode._bin_op(left, aug_opnode, bin_op, right, context), 

591 OperatorNode._bin_op( 

592 right, aug_opnode, bin_op, left, reverse_context, reverse=True 

593 ), 

594 ] 

595 return methods 

596 

597 @staticmethod 

598 def _get_binop_flow( 

599 left: InferenceResult, 

600 left_type: InferenceResult | None, 

601 binary_opnode: nodes.AugAssign | nodes.BinOp, 

602 right: InferenceResult, 

603 right_type: InferenceResult | None, 

604 context: InferenceContext, 

605 reverse_context: InferenceContext, 

606 ) -> list[partial[Generator[InferenceResult]]]: 

607 """Get the flow for binary operations. 

608 

609 The rules are a bit messy: 

610 

611 * if left and right have the same type, then only one 

612 method will be called, left.__op__(right) 

613 * if left and right are unrelated typewise, then first 

614 left.__op__(right) is tried and if this does not exist 

615 or returns NotImplemented, then right.__rop__(left) is tried. 

616 * if left is a subtype of right, then only left.__op__(right) 

617 is tried. 

618 * if left is a supertype of right, then right.__rop__(left) 

619 is first tried and then left.__op__(right) 

620 """ 

621 from astroid import helpers # pylint: disable=import-outside-toplevel 

622 

623 op = binary_opnode.op 

624 if OperatorNode._same_type(left_type, right_type): 

625 methods = [OperatorNode._bin_op(left, binary_opnode, op, right, context)] 

626 elif helpers.is_subtype(left_type, right_type): 

627 methods = [OperatorNode._bin_op(left, binary_opnode, op, right, context)] 

628 elif helpers.is_supertype(left_type, right_type): 

629 methods = [ 

630 OperatorNode._bin_op( 

631 right, binary_opnode, op, left, reverse_context, reverse=True 

632 ), 

633 OperatorNode._bin_op(left, binary_opnode, op, right, context), 

634 ] 

635 else: 

636 methods = [ 

637 OperatorNode._bin_op(left, binary_opnode, op, right, context), 

638 OperatorNode._bin_op( 

639 right, binary_opnode, op, left, reverse_context, reverse=True 

640 ), 

641 ] 

642 

643 # pylint: disable = too-many-boolean-expressions 

644 if ( 

645 op == "|" 

646 and ( 

647 isinstance(left, (bases.UnionType, nodes.ClassDef)) 

648 or (isinstance(left, nodes.Const) and left.value is None) 

649 ) 

650 and ( 

651 isinstance(right, (bases.UnionType, nodes.ClassDef)) 

652 or (isinstance(right, nodes.Const) and right.value is None) 

653 ) 

654 ): 

655 methods.extend([partial(OperatorNode._bin_op_or_union_type, left, right)]) 

656 return methods 

657 

658 @staticmethod 

659 def _infer_binary_operation( 

660 left: InferenceResult, 

661 right: InferenceResult, 

662 binary_opnode: nodes.AugAssign | nodes.BinOp, 

663 context: InferenceContext, 

664 flow_factory: GetFlowFactory, 

665 ) -> Generator[InferenceResult | util.BadBinaryOperationMessage]: 

666 """Infer a binary operation between a left operand and a right operand. 

667 

668 This is used by both normal binary operations and augmented binary 

669 operations, the only difference is the flow factory used. 

670 """ 

671 from astroid import helpers # pylint: disable=import-outside-toplevel 

672 

673 context, reverse_context = OperatorNode._get_binop_contexts( 

674 context, left, right 

675 ) 

676 left_type = helpers.object_type(left) 

677 right_type = helpers.object_type(right) 

678 methods = flow_factory( 

679 left, left_type, binary_opnode, right, right_type, context, reverse_context 

680 ) 

681 for method in methods: 

682 try: 

683 results = list(method()) 

684 except AttributeError: 

685 continue 

686 except AttributeInferenceError: 

687 continue 

688 except InferenceError: 

689 yield util.Uninferable 

690 return 

691 else: 

692 if any(isinstance(result, util.UninferableBase) for result in results): 

693 yield util.Uninferable 

694 return 

695 

696 if all(map(OperatorNode._is_not_implemented, results)): 

697 continue 

698 not_implemented = sum( 

699 1 for result in results if OperatorNode._is_not_implemented(result) 

700 ) 

701 if not_implemented and not_implemented != len(results): 

702 # Can't infer yet what this is. 

703 yield util.Uninferable 

704 return 

705 

706 yield from results 

707 return 

708 

709 # The operation doesn't seem to be supported so let the caller know about it 

710 yield util.BadBinaryOperationMessage(left_type, binary_opnode.op, right_type)