Coverage for C:\Repos\ekr-pylint\pylint\checkers\utils.py: 21%

765 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html 

2# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE 

3# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt 

4 

5"""Some functions that may be useful for various checkers.""" 

6 

7from __future__ import annotations 

8 

9import builtins 

10import itertools 

11import numbers 

12import re 

13import string 

14import warnings 

15from collections.abc import Iterable 

16from functools import lru_cache, partial 

17from re import Match 

18from typing import TYPE_CHECKING, Callable, TypeVar 

19 

20import _string 

21import astroid.objects 

22from astroid import TooManyLevelsError, nodes 

23from astroid.context import InferenceContext 

24 

25from pylint.constants import TYPING_TYPE_CHECKS_GUARDS 

26 

27if TYPE_CHECKING: 

28 from pylint.checkers import BaseChecker 

29 

30_NodeT = TypeVar("_NodeT", bound=nodes.NodeNG) 

31_CheckerT = TypeVar("_CheckerT", bound="BaseChecker") 

32AstCallbackMethod = Callable[[_CheckerT, _NodeT], None] 

33 

34COMP_NODE_TYPES = ( 

35 nodes.ListComp, 

36 nodes.SetComp, 

37 nodes.DictComp, 

38 nodes.GeneratorExp, 

39) 

40EXCEPTIONS_MODULE = "builtins" 

41ABC_MODULES = {"abc", "_py_abc"} 

42ABC_METHODS = { 

43 "abc.abstractproperty", 

44 "abc.abstractmethod", 

45 "abc.abstractclassmethod", 

46 "abc.abstractstaticmethod", 

47} 

48TYPING_PROTOCOLS = frozenset( 

49 {"typing.Protocol", "typing_extensions.Protocol", ".Protocol"} 

50) 

51ITER_METHOD = "__iter__" 

52AITER_METHOD = "__aiter__" 

53NEXT_METHOD = "__next__" 

54GETITEM_METHOD = "__getitem__" 

55CLASS_GETITEM_METHOD = "__class_getitem__" 

56SETITEM_METHOD = "__setitem__" 

57DELITEM_METHOD = "__delitem__" 

58CONTAINS_METHOD = "__contains__" 

59KEYS_METHOD = "keys" 

60 

61# Dictionary which maps the number of expected parameters a 

62# special method can have to a set of special methods. 

63# The following keys are used to denote the parameters restrictions: 

64# 

65# * None: variable number of parameters 

66# * number: exactly that number of parameters 

67# * tuple: these are the odd ones. Basically it means that the function 

68# can work with any number of arguments from that tuple, 

69# although it's best to implement it in order to accept 

70# all of them. 

71_SPECIAL_METHODS_PARAMS = { 

72 None: ("__new__", "__init__", "__call__"), 

73 0: ( 

74 "__del__", 

75 "__repr__", 

76 "__str__", 

77 "__bytes__", 

78 "__hash__", 

79 "__bool__", 

80 "__dir__", 

81 "__len__", 

82 "__length_hint__", 

83 "__iter__", 

84 "__reversed__", 

85 "__neg__", 

86 "__pos__", 

87 "__abs__", 

88 "__invert__", 

89 "__complex__", 

90 "__int__", 

91 "__float__", 

92 "__index__", 

93 "__trunc__", 

94 "__floor__", 

95 "__ceil__", 

96 "__enter__", 

97 "__aenter__", 

98 "__getnewargs_ex__", 

99 "__getnewargs__", 

100 "__getstate__", 

101 "__reduce__", 

102 "__copy__", 

103 "__unicode__", 

104 "__nonzero__", 

105 "__await__", 

106 "__aiter__", 

107 "__anext__", 

108 "__fspath__", 

109 "__subclasses__", 

110 "__init_subclass__", 

111 ), 

112 1: ( 

113 "__format__", 

114 "__lt__", 

115 "__le__", 

116 "__eq__", 

117 "__ne__", 

118 "__gt__", 

119 "__ge__", 

120 "__getattr__", 

121 "__getattribute__", 

122 "__delattr__", 

123 "__delete__", 

124 "__instancecheck__", 

125 "__subclasscheck__", 

126 "__getitem__", 

127 "__missing__", 

128 "__delitem__", 

129 "__contains__", 

130 "__add__", 

131 "__sub__", 

132 "__mul__", 

133 "__truediv__", 

134 "__floordiv__", 

135 "__rfloordiv__", 

136 "__mod__", 

137 "__divmod__", 

138 "__lshift__", 

139 "__rshift__", 

140 "__and__", 

141 "__xor__", 

142 "__or__", 

143 "__radd__", 

144 "__rsub__", 

145 "__rmul__", 

146 "__rtruediv__", 

147 "__rmod__", 

148 "__rdivmod__", 

149 "__rpow__", 

150 "__rlshift__", 

151 "__rrshift__", 

152 "__rand__", 

153 "__rxor__", 

154 "__ror__", 

155 "__iadd__", 

156 "__isub__", 

157 "__imul__", 

158 "__itruediv__", 

159 "__ifloordiv__", 

160 "__imod__", 

161 "__ilshift__", 

162 "__irshift__", 

163 "__iand__", 

164 "__ixor__", 

165 "__ior__", 

166 "__ipow__", 

167 "__setstate__", 

168 "__reduce_ex__", 

169 "__deepcopy__", 

170 "__cmp__", 

171 "__matmul__", 

172 "__rmatmul__", 

173 "__imatmul__", 

174 "__div__", 

175 ), 

176 2: ("__setattr__", "__get__", "__set__", "__setitem__", "__set_name__"), 

177 3: ("__exit__", "__aexit__"), 

178 (0, 1): ("__round__",), 

179 (1, 2): ("__pow__",), 

180} 

181 

182SPECIAL_METHODS_PARAMS = { 

183 name: params 

184 for params, methods in _SPECIAL_METHODS_PARAMS.items() 

185 for name in methods 

186} 

187PYMETHODS = set(SPECIAL_METHODS_PARAMS) 

188 

189SUBSCRIPTABLE_CLASSES_PEP585 = frozenset( 

190 ( 

191 "builtins.tuple", 

192 "builtins.list", 

193 "builtins.dict", 

194 "builtins.set", 

195 "builtins.frozenset", 

196 "builtins.type", 

197 "collections.deque", 

198 "collections.defaultdict", 

199 "collections.OrderedDict", 

200 "collections.Counter", 

201 "collections.ChainMap", 

202 "_collections_abc.Awaitable", 

203 "_collections_abc.Coroutine", 

204 "_collections_abc.AsyncIterable", 

205 "_collections_abc.AsyncIterator", 

206 "_collections_abc.AsyncGenerator", 

207 "_collections_abc.Iterable", 

208 "_collections_abc.Iterator", 

209 "_collections_abc.Generator", 

210 "_collections_abc.Reversible", 

211 "_collections_abc.Container", 

212 "_collections_abc.Collection", 

213 "_collections_abc.Callable", 

214 "_collections_abc.Set", 

215 "_collections_abc.MutableSet", 

216 "_collections_abc.Mapping", 

217 "_collections_abc.MutableMapping", 

218 "_collections_abc.Sequence", 

219 "_collections_abc.MutableSequence", 

220 "_collections_abc.ByteString", 

221 "_collections_abc.MappingView", 

222 "_collections_abc.KeysView", 

223 "_collections_abc.ItemsView", 

224 "_collections_abc.ValuesView", 

225 "contextlib.AbstractContextManager", 

226 "contextlib.AbstractAsyncContextManager", 

227 "re.Pattern", 

228 "re.Match", 

229 ) 

230) 

231 

232 

233class NoSuchArgumentError(Exception): 

234 pass 

235 

236 

237class InferredTypeError(Exception): 

238 pass 

239 

240 

241def is_inside_lambda(node: nodes.NodeNG) -> bool: 

242 """Return whether the given node is inside a lambda.""" 

243 warnings.warn( 

244 "utils.is_inside_lambda will be removed in favour of calling " 

245 "utils.get_node_first_ancestor_of_type(x, nodes.Lambda) in pylint 3.0", 

246 DeprecationWarning, 

247 ) 

248 return any(isinstance(parent, nodes.Lambda) for parent in node.node_ancestors()) 

249 

250 

251def get_all_elements( 

252 node: nodes.NodeNG, 

253) -> Iterable[nodes.NodeNG]: 

254 """Recursively returns all atoms in nested lists and tuples.""" 

255 if isinstance(node, (nodes.Tuple, nodes.List)): 

256 for child in node.elts: 

257 yield from get_all_elements(child) 

258 else: 

259 yield node 

260 

261 

262def is_super(node: nodes.NodeNG) -> bool: 

263 """Return True if the node is referencing the "super" builtin function.""" 

264 if getattr(node, "name", None) == "super" and node.root().name == "builtins": 

265 return True 

266 return False 

267 

268 

269def is_error(node: nodes.FunctionDef) -> bool: 

270 """Return true if the given function node only raises an exception.""" 

271 return len(node.body) == 1 and isinstance(node.body[0], nodes.Raise) 

272 

273 

274builtins = builtins.__dict__.copy() # type: ignore[assignment] 

275SPECIAL_BUILTINS = ("__builtins__",) # '__path__', '__file__') 

276 

277 

278def is_builtin_object(node: nodes.NodeNG) -> bool: 

279 """Returns True if the given node is an object from the __builtin__ module.""" 

280 return node and node.root().name == "builtins" 

281 

282 

283def is_builtin(name: str) -> bool: 

284 """Return true if <name> could be considered as a builtin defined by python.""" 

285 return name in builtins or name in SPECIAL_BUILTINS # type: ignore[attr-defined] 

286 

287 

288def is_defined_in_scope( 

289 var_node: nodes.NodeNG, 

290 varname: str, 

291 scope: nodes.NodeNG, 

292) -> bool: 

293 if isinstance(scope, nodes.If): 

294 for node in scope.body: 

295 if ( 

296 isinstance(node, nodes.Assign) 

297 and any( 

298 isinstance(target, nodes.AssignName) and target.name == varname 

299 for target in node.targets 

300 ) 

301 ) or (isinstance(node, nodes.Nonlocal) and varname in node.names): 

302 return True 

303 elif isinstance(scope, (COMP_NODE_TYPES, nodes.For)): 

304 for ass_node in scope.nodes_of_class(nodes.AssignName): 

305 if ass_node.name == varname: 

306 return True 

307 elif isinstance(scope, nodes.With): 

308 for expr, ids in scope.items: 

309 if expr.parent_of(var_node): 

310 break 

311 if ids and isinstance(ids, nodes.AssignName) and ids.name == varname: 

312 return True 

313 elif isinstance(scope, (nodes.Lambda, nodes.FunctionDef)): 

314 if scope.args.is_argument(varname): 

315 # If the name is found inside a default value 

316 # of a function, then let the search continue 

317 # in the parent's tree. 

318 if scope.args.parent_of(var_node): 

319 try: 

320 scope.args.default_value(varname) 

321 scope = scope.parent 

322 is_defined_in_scope(var_node, varname, scope) 

323 except astroid.NoDefault: 

324 pass 

325 return True 

326 if getattr(scope, "name", None) == varname: 

327 return True 

328 elif isinstance(scope, nodes.ExceptHandler): 

329 if isinstance(scope.name, nodes.AssignName): 

330 ass_node = scope.name 

331 if ass_node.name == varname: 

332 return True 

333 return False 

334 

335 

336def is_defined_before(var_node: nodes.Name) -> bool: 

337 """Check if the given variable node is defined before. 

338 

339 Verify that the variable node is defined by a parent node 

340 (list, set, dict, or generator comprehension, lambda) 

341 or in a previous sibling node on the same line 

342 (statement_defining ; statement_using). 

343 """ 

344 varname = var_node.name 

345 for parent in var_node.node_ancestors(): 

346 if is_defined_in_scope(var_node, varname, parent): 

347 return True 

348 # possibly multiple statements on the same line using semicolon separator 

349 stmt = var_node.statement(future=True) 

350 _node = stmt.previous_sibling() 

351 lineno = stmt.fromlineno 

352 while _node and _node.fromlineno == lineno: 

353 for assign_node in _node.nodes_of_class(nodes.AssignName): 

354 if assign_node.name == varname: 

355 return True 

356 for imp_node in _node.nodes_of_class((nodes.ImportFrom, nodes.Import)): 

357 if varname in [name[1] or name[0] for name in imp_node.names]: 

358 return True 

359 _node = _node.previous_sibling() 

360 return False 

361 

362 

363def is_default_argument(node: nodes.NodeNG, scope: nodes.NodeNG | None = None) -> bool: 

364 """Return true if the given Name node is used in function or lambda 

365 default argument's value. 

366 """ 

367 if not scope: 

368 scope = node.scope() 

369 if isinstance(scope, (nodes.FunctionDef, nodes.Lambda)): 

370 all_defaults = itertools.chain( 

371 scope.args.defaults, (d for d in scope.args.kw_defaults if d is not None) 

372 ) 

373 return any( 

374 default_name_node is node 

375 for default_node in all_defaults 

376 for default_name_node in default_node.nodes_of_class(nodes.Name) 

377 ) 

378 

379 return False 

380 

381 

382def is_func_decorator(node: nodes.NodeNG) -> bool: 

383 """Return true if the name is used in function decorator.""" 

384 for parent in node.node_ancestors(): 

385 if isinstance(parent, nodes.Decorators): 

386 return True 

387 if parent.is_statement or isinstance( 

388 parent, 

389 ( 

390 nodes.Lambda, 

391 nodes.ComprehensionScope, 

392 nodes.ListComp, 

393 ), 

394 ): 

395 break 

396 return False 

397 

398 

399def is_ancestor_name(frame: nodes.ClassDef, node: nodes.NodeNG) -> bool: 

400 """Return whether `frame` is an astroid.Class node with `node` in the 

401 subtree of its bases attribute. 

402 """ 

403 if not isinstance(frame, nodes.ClassDef): 

404 return False 

405 return any(node in base.nodes_of_class(nodes.Name) for base in frame.bases) 

406 

407 

408def is_being_called(node: nodes.NodeNG) -> bool: 

409 """Return True if node is the function being called in a Call node.""" 

410 return isinstance(node.parent, nodes.Call) and node.parent.func is node 

411 

412 

413def assign_parent(node: nodes.NodeNG) -> nodes.NodeNG: 

414 """Return the higher parent which is not an AssignName, Tuple or List node.""" 

415 while node and isinstance(node, (nodes.AssignName, nodes.Tuple, nodes.List)): 

416 node = node.parent 

417 return node 

418 

419 

420def overrides_a_method(class_node: nodes.ClassDef, name: str) -> bool: 

421 """Return True if <name> is a method overridden from an ancestor 

422 which is not the base object class. 

423 """ 

424 for ancestor in class_node.ancestors(): 

425 if ancestor.name == "object": 

426 continue 

427 if name in ancestor and isinstance(ancestor[name], nodes.FunctionDef): 

428 return True 

429 return False 

430 

431 

432def only_required_for_messages( 

433 *messages: str, 

434) -> Callable[ 

435 [AstCallbackMethod[_CheckerT, _NodeT]], AstCallbackMethod[_CheckerT, _NodeT] 

436]: 

437 """Decorator to store messages that are handled by a checker method as an 

438 attribute of the function object. 

439 

440 This information is used by ``ASTWalker`` to decide whether to call the decorated 

441 method or not. If none of the messages is enabled, the method will be skipped. 

442 Therefore, the list of messages must be well maintained at all times! 

443 This decorator only has an effect on ``visit_*`` and ``leave_*`` methods 

444 of a class inheriting from ``BaseChecker``. 

445 """ 

446 

447 def store_messages( 

448 func: AstCallbackMethod[_CheckerT, _NodeT] 

449 ) -> AstCallbackMethod[_CheckerT, _NodeT]: 

450 setattr(func, "checks_msgs", messages) 

451 return func 

452 

453 return store_messages 

454 

455 

456def check_messages( 

457 *messages: str, 

458) -> Callable[ 

459 [AstCallbackMethod[_CheckerT, _NodeT]], AstCallbackMethod[_CheckerT, _NodeT] 

460]: 

461 """Kept for backwards compatibility, deprecated. 

462 

463 Use only_required_for_messages instead, which conveys the intent of the decorator much clearer. 

464 """ 

465 warnings.warn( 

466 "utils.check_messages will be removed in favour of calling " 

467 "utils.only_required_for_messages in pylint 3.0", 

468 DeprecationWarning, 

469 ) 

470 

471 return only_required_for_messages(*messages) 

472 

473 

474class IncompleteFormatString(Exception): 

475 """A format string ended in the middle of a format specifier.""" 

476 

477 

478class UnsupportedFormatCharacter(Exception): 

479 """A format character in a format string is not one of the supported 

480 format characters. 

481 """ 

482 

483 def __init__(self, index): 

484 super().__init__(index) 

485 self.index = index 

486 

487 

488def parse_format_string( 

489 format_string: str, 

490) -> tuple[set[str], int, dict[str, str], list[str]]: 

491 """Parses a format string, returning a tuple (keys, num_args). 

492 

493 Where 'keys' is the set of mapping keys in the format string, and 'num_args' is the number 

494 of arguments required by the format string. Raises IncompleteFormatString or 

495 UnsupportedFormatCharacter if a parse error occurs. 

496 """ 

497 keys = set() 

498 key_types = {} 

499 pos_types = [] 

500 num_args = 0 

501 

502 def next_char(i): 

503 i += 1 

504 if i == len(format_string): 

505 raise IncompleteFormatString 

506 return (i, format_string[i]) 

507 

508 i = 0 

509 while i < len(format_string): 

510 char = format_string[i] 

511 if char == "%": 

512 i, char = next_char(i) 

513 # Parse the mapping key (optional). 

514 key = None 

515 if char == "(": 

516 depth = 1 

517 i, char = next_char(i) 

518 key_start = i 

519 while depth != 0: 

520 if char == "(": 

521 depth += 1 

522 elif char == ")": 

523 depth -= 1 

524 i, char = next_char(i) 

525 key_end = i - 1 

526 key = format_string[key_start:key_end] 

527 

528 # Parse the conversion flags (optional). 

529 while char in "#0- +": 

530 i, char = next_char(i) 

531 # Parse the minimum field width (optional). 

532 if char == "*": 

533 num_args += 1 

534 i, char = next_char(i) 

535 else: 

536 while char in string.digits: 

537 i, char = next_char(i) 

538 # Parse the precision (optional). 

539 if char == ".": 

540 i, char = next_char(i) 

541 if char == "*": 

542 num_args += 1 

543 i, char = next_char(i) 

544 else: 

545 while char in string.digits: 

546 i, char = next_char(i) 

547 # Parse the length modifier (optional). 

548 if char in "hlL": 

549 i, char = next_char(i) 

550 # Parse the conversion type (mandatory). 

551 flags = "diouxXeEfFgGcrs%a" 

552 if char not in flags: 

553 raise UnsupportedFormatCharacter(i) 

554 if key: 

555 keys.add(key) 

556 key_types[key] = char 

557 elif char != "%": 

558 num_args += 1 

559 pos_types.append(char) 

560 i += 1 

561 return keys, num_args, key_types, pos_types 

562 

563 

564def split_format_field_names(format_string) -> tuple[str, Iterable[tuple[bool, str]]]: 

565 try: 

566 return _string.formatter_field_name_split(format_string) 

567 except ValueError as e: 

568 raise IncompleteFormatString() from e 

569 

570 

571def collect_string_fields(format_string) -> Iterable[str | None]: 

572 """Given a format string, return an iterator 

573 of all the valid format fields. 

574 

575 It handles nested fields as well. 

576 """ 

577 formatter = string.Formatter() 

578 try: 

579 parseiterator = formatter.parse(format_string) 

580 for result in parseiterator: 

581 if all(item is None for item in result[1:]): 

582 # not a replacement format 

583 continue 

584 name = result[1] 

585 nested = result[2] 

586 yield name 

587 if nested: 

588 yield from collect_string_fields(nested) 

589 except ValueError as exc: 

590 # Probably the format string is invalid. 

591 if exc.args[0].startswith("cannot switch from manual"): 

592 # On Jython, parsing a string with both manual 

593 # and automatic positions will fail with a ValueError, 

594 # while on CPython it will simply return the fields, 

595 # the validation being done in the interpreter (?). 

596 # We're just returning two mixed fields in order 

597 # to trigger the format-combined-specification check. 

598 yield "" 

599 yield "1" 

600 return 

601 raise IncompleteFormatString(format_string) from exc 

602 

603 

604def parse_format_method_string( 

605 format_string: str, 

606) -> tuple[list[tuple[str, list[tuple[bool, str]]]], int, int]: 

607 """Parses a PEP 3101 format string, returning a tuple of 

608 (keyword_arguments, implicit_pos_args_cnt, explicit_pos_args). 

609 

610 keyword_arguments is the set of mapping keys in the format string, implicit_pos_args_cnt 

611 is the number of arguments required by the format string and 

612 explicit_pos_args is the number of arguments passed with the position. 

613 """ 

614 keyword_arguments = [] 

615 implicit_pos_args_cnt = 0 

616 explicit_pos_args = set() 

617 for name in collect_string_fields(format_string): 

618 if name and str(name).isdigit(): 

619 explicit_pos_args.add(str(name)) 

620 elif name: 

621 keyname, fielditerator = split_format_field_names(name) 

622 if isinstance(keyname, numbers.Number): 

623 explicit_pos_args.add(str(keyname)) 

624 try: 

625 keyword_arguments.append((keyname, list(fielditerator))) 

626 except ValueError as e: 

627 raise IncompleteFormatString() from e 

628 else: 

629 implicit_pos_args_cnt += 1 

630 return keyword_arguments, implicit_pos_args_cnt, len(explicit_pos_args) 

631 

632 

633def is_attr_protected(attrname: str) -> bool: 

634 """Return True if attribute name is protected (start with _ and some other 

635 details), False otherwise. 

636 """ 

637 return ( 

638 attrname[0] == "_" 

639 and attrname != "_" 

640 and not (attrname.startswith("__") and attrname.endswith("__")) 

641 ) 

642 

643 

644def node_frame_class(node: nodes.NodeNG) -> nodes.ClassDef | None: 

645 """Return the class that is wrapping the given node. 

646 

647 The function returns a class for a method node (or a staticmethod or a 

648 classmethod), otherwise it returns `None`. 

649 """ 

650 klass = node.frame(future=True) 

651 nodes_to_check = ( 

652 nodes.NodeNG, 

653 astroid.UnboundMethod, 

654 astroid.BaseInstance, 

655 ) 

656 while ( 

657 klass 

658 and isinstance(klass, nodes_to_check) 

659 and not isinstance(klass, nodes.ClassDef) 

660 ): 

661 if klass.parent is None: 

662 return None 

663 

664 klass = klass.parent.frame(future=True) 

665 

666 return klass 

667 

668 

669def get_outer_class(class_node: astroid.ClassDef) -> astroid.ClassDef | None: 

670 """Return the class that is the outer class of given (nested) class_node.""" 

671 parent_klass = class_node.parent.frame(future=True) 

672 

673 return parent_klass if isinstance(parent_klass, astroid.ClassDef) else None 

674 

675 

676def is_attr_private(attrname: str) -> Match[str] | None: 

677 """Check that attribute name is private (at least two leading underscores, 

678 at most one trailing underscore). 

679 """ 

680 regex = re.compile("^_{2,}.*[^_]+_?$") 

681 return regex.match(attrname) 

682 

683 

684def get_argument_from_call( 

685 call_node: nodes.Call, position: int | None = None, keyword: str | None = None 

686) -> nodes.Name: 

687 """Returns the specified argument from a function call. 

688 

689 :param nodes.Call call_node: Node representing a function call to check. 

690 :param int position: position of the argument. 

691 :param str keyword: the keyword of the argument. 

692 

693 :returns: The node representing the argument, None if the argument is not found. 

694 :rtype: nodes.Name 

695 :raises ValueError: if both position and keyword are None. 

696 :raises NoSuchArgumentError: if no argument at the provided position or with 

697 the provided keyword. 

698 """ 

699 if position is None and keyword is None: 

700 raise ValueError("Must specify at least one of: position or keyword.") 

701 if position is not None: 

702 try: 

703 return call_node.args[position] 

704 except IndexError: 

705 pass 

706 if keyword and call_node.keywords: 

707 for arg in call_node.keywords: 

708 if arg.arg == keyword: 

709 return arg.value 

710 

711 raise NoSuchArgumentError 

712 

713 

714def inherit_from_std_ex(node: nodes.NodeNG | astroid.Instance) -> bool: 

715 """Return whether the given class node is subclass of 

716 exceptions.Exception. 

717 """ 

718 ancestors = node.ancestors() if hasattr(node, "ancestors") else [] 

719 return any( 

720 ancestor.name in {"Exception", "BaseException"} 

721 and ancestor.root().name == EXCEPTIONS_MODULE 

722 for ancestor in itertools.chain([node], ancestors) 

723 ) 

724 

725 

726def error_of_type(handler: nodes.ExceptHandler, error_type) -> bool: 

727 """Check if the given exception handler catches 

728 the given error_type. 

729 

730 The *handler* parameter is a node, representing an ExceptHandler node. 

731 The *error_type* can be an exception, such as AttributeError, 

732 the name of an exception, or it can be a tuple of errors. 

733 The function will return True if the handler catches any of the 

734 given errors. 

735 """ 

736 

737 def stringify_error(error): 

738 if not isinstance(error, str): 

739 return error.__name__ 

740 return error 

741 

742 if not isinstance(error_type, tuple): 

743 error_type = (error_type,) 

744 expected_errors = {stringify_error(error) for error in error_type} 

745 if not handler.type: 

746 return False 

747 return handler.catch(expected_errors) 

748 

749 

750def decorated_with_property(node: nodes.FunctionDef) -> bool: 

751 """Detect if the given function node is decorated with a property.""" 

752 if not node.decorators: 

753 return False 

754 for decorator in node.decorators.nodes: 

755 try: 

756 if _is_property_decorator(decorator): 

757 return True 

758 except astroid.InferenceError: 

759 pass 

760 return False 

761 

762 

763def _is_property_kind(node, *kinds): 

764 if not isinstance(node, (astroid.UnboundMethod, nodes.FunctionDef)): 

765 return False 

766 if node.decorators: 

767 for decorator in node.decorators.nodes: 

768 if isinstance(decorator, nodes.Attribute) and decorator.attrname in kinds: 

769 return True 

770 return False 

771 

772 

773def is_property_setter(node: nodes.FunctionDef) -> bool: 

774 """Check if the given node is a property setter.""" 

775 return _is_property_kind(node, "setter") 

776 

777 

778def is_property_deleter(node: nodes.FunctionDef) -> bool: 

779 """Check if the given node is a property deleter.""" 

780 return _is_property_kind(node, "deleter") 

781 

782 

783def is_property_setter_or_deleter(node: nodes.FunctionDef) -> bool: 

784 """Check if the given node is either a property setter or a deleter.""" 

785 return _is_property_kind(node, "setter", "deleter") 

786 

787 

788def _is_property_decorator(decorator: nodes.Name) -> bool: 

789 for inferred in decorator.infer(): 

790 if isinstance(inferred, nodes.ClassDef): 

791 if inferred.qname() in {"builtins.property", "functools.cached_property"}: 

792 return True 

793 for ancestor in inferred.ancestors(): 

794 if ancestor.name == "property" and ancestor.root().name == "builtins": 

795 return True 

796 elif isinstance(inferred, nodes.FunctionDef): 

797 # If decorator is function, check if it has exactly one return 

798 # and the return is itself a function decorated with property 

799 returns: list[nodes.Return] = list( 

800 inferred._get_return_nodes_skip_functions() 

801 ) 

802 if len(returns) == 1 and isinstance( 

803 returns[0].value, (nodes.Name, nodes.Attribute) 

804 ): 

805 inferred = safe_infer(returns[0].value) 

806 if ( 

807 inferred 

808 and isinstance(inferred, astroid.objects.Property) 

809 and isinstance(inferred.function, nodes.FunctionDef) 

810 ): 

811 return decorated_with_property(inferred.function) 

812 return False 

813 

814 

815def decorated_with( 

816 func: ( 

817 nodes.ClassDef | nodes.FunctionDef | astroid.BoundMethod | astroid.UnboundMethod 

818 ), 

819 qnames: Iterable[str], 

820) -> bool: 

821 """Determine if the `func` node has a decorator with the qualified name `qname`.""" 

822 decorators = func.decorators.nodes if func.decorators else [] 

823 for decorator_node in decorators: 

824 if isinstance(decorator_node, nodes.Call): 

825 # We only want to infer the function name 

826 decorator_node = decorator_node.func 

827 try: 

828 if any( 

829 i.name in qnames or i.qname() in qnames 

830 for i in decorator_node.infer() 

831 if i is not None and i != astroid.Uninferable 

832 ): 

833 return True 

834 except astroid.InferenceError: 

835 continue 

836 return False 

837 

838 

839def uninferable_final_decorators( 

840 node: nodes.Decorators, 

841) -> list[nodes.Attribute | nodes.Name | None]: 

842 """Return a list of uninferable `typing.final` decorators in `node`. 

843 

844 This function is used to determine if the `typing.final` decorator is used 

845 with an unsupported Python version; the decorator cannot be inferred when 

846 using a Python version lower than 3.8. 

847 """ 

848 decorators = [] 

849 for decorator in getattr(node, "nodes", []): 

850 import_nodes: tuple[nodes.Import | nodes.ImportFrom] | None = None 

851 

852 # Get the `Import` node. The decorator is of the form: @module.name 

853 if isinstance(decorator, nodes.Attribute): 

854 inferred = safe_infer(decorator.expr) 

855 if isinstance(inferred, nodes.Module) and inferred.qname() == "typing": 

856 _, import_nodes = decorator.expr.lookup(decorator.expr.name) 

857 

858 # Get the `ImportFrom` node. The decorator is of the form: @name 

859 elif isinstance(decorator, nodes.Name): 

860 _, import_nodes = decorator.lookup(decorator.name) 

861 

862 # The `final` decorator is expected to be found in the 

863 # import_nodes. Continue if we don't find any `Import` or `ImportFrom` 

864 # nodes for this decorator. 

865 if not import_nodes: 

866 continue 

867 import_node = import_nodes[0] 

868 

869 if not isinstance(import_node, (astroid.Import, astroid.ImportFrom)): 

870 continue 

871 

872 import_names = dict(import_node.names) 

873 

874 # Check if the import is of the form: `from typing import final` 

875 is_from_import = ("final" in import_names) and import_node.modname == "typing" 

876 

877 # Check if the import is of the form: `import typing` 

878 is_import = ("typing" in import_names) and getattr( 

879 decorator, "attrname", None 

880 ) == "final" 

881 

882 if (is_from_import or is_import) and safe_infer(decorator) in [ 

883 astroid.Uninferable, 

884 None, 

885 ]: 

886 decorators.append(decorator) 

887 return decorators 

888 

889 

890@lru_cache(maxsize=1024) 

891def unimplemented_abstract_methods( 

892 node: nodes.ClassDef, is_abstract_cb: nodes.FunctionDef = None 

893) -> dict[str, nodes.NodeNG]: 

894 """Get the unimplemented abstract methods for the given *node*. 

895 

896 A method can be considered abstract if the callback *is_abstract_cb* 

897 returns a ``True`` value. The check defaults to verifying that 

898 a method is decorated with abstract methods. 

899 The function will work only for new-style classes. For old-style 

900 classes, it will simply return an empty dictionary. 

901 For the rest of them, it will return a dictionary of abstract method 

902 names and their inferred objects. 

903 """ 

904 if is_abstract_cb is None: 

905 is_abstract_cb = partial(decorated_with, qnames=ABC_METHODS) 

906 visited: dict[str, nodes.NodeNG] = {} 

907 try: 

908 mro = reversed(node.mro()) 

909 except NotImplementedError: 

910 # Old style class, it will not have a mro. 

911 return {} 

912 except astroid.ResolveError: 

913 # Probably inconsistent hierarchy, don't try to figure this out here. 

914 return {} 

915 for ancestor in mro: 

916 for obj in ancestor.values(): 

917 inferred = obj 

918 if isinstance(obj, nodes.AssignName): 

919 inferred = safe_infer(obj) 

920 if not inferred: 

921 # Might be an abstract function, 

922 # but since we don't have enough information 

923 # in order to take this decision, we're taking 

924 # the *safe* decision instead. 

925 if obj.name in visited: 

926 del visited[obj.name] 

927 continue 

928 if not isinstance(inferred, nodes.FunctionDef): 

929 if obj.name in visited: 

930 del visited[obj.name] 

931 if isinstance(inferred, nodes.FunctionDef): 

932 # It's critical to use the original name, 

933 # since after inferring, an object can be something 

934 # else than expected, as in the case of the 

935 # following assignment. 

936 # 

937 # class A: 

938 # def keys(self): pass 

939 # __iter__ = keys 

940 abstract = is_abstract_cb(inferred) 

941 if abstract: 

942 visited[obj.name] = inferred 

943 elif not abstract and obj.name in visited: 

944 del visited[obj.name] 

945 return visited 

946 

947 

948def find_try_except_wrapper_node( 

949 node: nodes.NodeNG, 

950) -> nodes.ExceptHandler | nodes.TryExcept | None: 

951 """Return the ExceptHandler or the TryExcept node in which the node is.""" 

952 current = node 

953 ignores = (nodes.ExceptHandler, nodes.TryExcept) 

954 while current and not isinstance(current.parent, ignores): 

955 current = current.parent 

956 

957 if current and isinstance(current.parent, ignores): 

958 return current.parent 

959 return None 

960 

961 

962def find_except_wrapper_node_in_scope( 

963 node: nodes.NodeNG, 

964) -> nodes.ExceptHandler | nodes.TryExcept | None: 

965 """Return the ExceptHandler in which the node is, without going out of scope.""" 

966 for current in node.node_ancestors(): 

967 if isinstance(current, astroid.scoped_nodes.LocalsDictNodeNG): 

968 # If we're inside a function/class definition, we don't want to keep checking 

969 # higher ancestors for `except` clauses, because if these exist, it means our 

970 # function/class was defined in an `except` clause, rather than the current code 

971 # actually running in an `except` clause. 

972 return None 

973 if isinstance(current, nodes.ExceptHandler): 

974 return current 

975 return None 

976 

977 

978def is_from_fallback_block(node: nodes.NodeNG) -> bool: 

979 """Check if the given node is from a fallback import block.""" 

980 context = find_try_except_wrapper_node(node) 

981 if not context: 

982 return False 

983 

984 if isinstance(context, nodes.ExceptHandler): 

985 other_body = context.parent.body 

986 handlers = context.parent.handlers 

987 else: 

988 other_body = itertools.chain.from_iterable( 

989 handler.body for handler in context.handlers 

990 ) 

991 handlers = context.handlers 

992 

993 has_fallback_imports = any( 

994 isinstance(import_node, (nodes.ImportFrom, nodes.Import)) 

995 for import_node in other_body 

996 ) 

997 ignores_import_error = _except_handlers_ignores_exceptions( 

998 handlers, (ImportError, ModuleNotFoundError) 

999 ) 

1000 return ignores_import_error or has_fallback_imports 

1001 

1002 

1003def _except_handlers_ignores_exceptions( 

1004 handlers: nodes.ExceptHandler, 

1005 exceptions: tuple[type[ImportError], type[ModuleNotFoundError]], 

1006) -> bool: 

1007 func = partial(error_of_type, error_type=exceptions) 

1008 return any(func(handler) for handler in handlers) 

1009 

1010 

1011def get_exception_handlers( 

1012 node: nodes.NodeNG, exception=Exception 

1013) -> list[nodes.ExceptHandler] | None: 

1014 """Return the collections of handlers handling the exception in arguments. 

1015 

1016 Args: 

1017 node (nodes.NodeNG): A node that is potentially wrapped in a try except. 

1018 exception (builtin.Exception or str): exception or name of the exception. 

1019 

1020 Returns: 

1021 list: the collection of handlers that are handling the exception or None. 

1022 

1023 """ 

1024 context = find_try_except_wrapper_node(node) 

1025 if isinstance(context, nodes.TryExcept): 

1026 return [ 

1027 handler for handler in context.handlers if error_of_type(handler, exception) 

1028 ] 

1029 return [] 

1030 

1031 

1032def is_node_inside_try_except(node: nodes.Raise) -> bool: 

1033 """Check if the node is directly under a Try/Except statement 

1034 (but not under an ExceptHandler!). 

1035 

1036 Args: 

1037 node (nodes.Raise): the node raising the exception. 

1038 

1039 Returns: 

1040 bool: True if the node is inside a try/except statement, False otherwise. 

1041 """ 

1042 context = find_try_except_wrapper_node(node) 

1043 return isinstance(context, nodes.TryExcept) 

1044 

1045 

1046def node_ignores_exception(node: nodes.NodeNG, exception=Exception) -> bool: 

1047 """Check if the node is in a TryExcept which handles the given exception. 

1048 

1049 If the exception is not given, the function is going to look for bare 

1050 excepts. 

1051 """ 

1052 managing_handlers = get_exception_handlers(node, exception) 

1053 if not managing_handlers: 

1054 return False 

1055 return any(managing_handlers) 

1056 

1057 

1058def class_is_abstract(node: nodes.ClassDef) -> bool: 

1059 """Return true if the given class node should be considered as an abstract 

1060 class. 

1061 """ 

1062 # Only check for explicit metaclass=ABCMeta on this specific class 

1063 meta = node.declared_metaclass() 

1064 if meta is not None: 

1065 if meta.name == "ABCMeta" and meta.root().name in ABC_MODULES: 

1066 return True 

1067 

1068 for ancestor in node.ancestors(): 

1069 if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES: 

1070 # abc.ABC inheritance 

1071 return True 

1072 

1073 for method in node.methods(): 

1074 if method.parent.frame(future=True) is node: 

1075 if method.is_abstract(pass_is_abstract=False): 

1076 return True 

1077 return False 

1078 

1079 

1080def _supports_protocol_method(value: nodes.NodeNG, attr: str) -> bool: 

1081 try: 

1082 attributes = value.getattr(attr) 

1083 except astroid.NotFoundError: 

1084 return False 

1085 

1086 first = attributes[0] 

1087 

1088 # Return False if a constant is assigned 

1089 if isinstance(first, nodes.AssignName): 

1090 this_assign_parent = get_node_first_ancestor_of_type( 

1091 first, (nodes.Assign, nodes.NamedExpr) 

1092 ) 

1093 if this_assign_parent is None: # pragma: no cover 

1094 # Cannot imagine this being None, but return True to avoid false positives 

1095 return True 

1096 if isinstance(this_assign_parent.value, nodes.BaseContainer): 

1097 if all(isinstance(n, nodes.Const) for n in this_assign_parent.value.elts): 

1098 return False 

1099 if isinstance(this_assign_parent.value, nodes.Const): 

1100 return False 

1101 return True 

1102 

1103 

1104def is_comprehension(node: nodes.NodeNG) -> bool: 

1105 comprehensions = ( 

1106 nodes.ListComp, 

1107 nodes.SetComp, 

1108 nodes.DictComp, 

1109 nodes.GeneratorExp, 

1110 ) 

1111 return isinstance(node, comprehensions) 

1112 

1113 

1114def _supports_mapping_protocol(value: nodes.NodeNG) -> bool: 

1115 return _supports_protocol_method( 

1116 value, GETITEM_METHOD 

1117 ) and _supports_protocol_method(value, KEYS_METHOD) 

1118 

1119 

1120def _supports_membership_test_protocol(value: nodes.NodeNG) -> bool: 

1121 return _supports_protocol_method(value, CONTAINS_METHOD) 

1122 

1123 

1124def _supports_iteration_protocol(value: nodes.NodeNG) -> bool: 

1125 return _supports_protocol_method(value, ITER_METHOD) or _supports_protocol_method( 

1126 value, GETITEM_METHOD 

1127 ) 

1128 

1129 

1130def _supports_async_iteration_protocol(value: nodes.NodeNG) -> bool: 

1131 return _supports_protocol_method(value, AITER_METHOD) 

1132 

1133 

1134def _supports_getitem_protocol(value: nodes.NodeNG) -> bool: 

1135 return _supports_protocol_method(value, GETITEM_METHOD) 

1136 

1137 

1138def _supports_setitem_protocol(value: nodes.NodeNG) -> bool: 

1139 return _supports_protocol_method(value, SETITEM_METHOD) 

1140 

1141 

1142def _supports_delitem_protocol(value: nodes.NodeNG) -> bool: 

1143 return _supports_protocol_method(value, DELITEM_METHOD) 

1144 

1145 

1146def _is_abstract_class_name(name: str) -> bool: 

1147 lname = name.lower() 

1148 is_mixin = lname.endswith("mixin") 

1149 is_abstract = lname.startswith("abstract") 

1150 is_base = lname.startswith("base") or lname.endswith("base") 

1151 return is_mixin or is_abstract or is_base 

1152 

1153 

1154def is_inside_abstract_class(node: nodes.NodeNG) -> bool: 

1155 while node is not None: 

1156 if isinstance(node, nodes.ClassDef): 

1157 if class_is_abstract(node): 

1158 return True 

1159 name = getattr(node, "name", None) 

1160 if name is not None and _is_abstract_class_name(name): 

1161 return True 

1162 node = node.parent 

1163 return False 

1164 

1165 

1166def _supports_protocol( 

1167 value: nodes.NodeNG, protocol_callback: nodes.FunctionDef 

1168) -> bool: 

1169 if isinstance(value, nodes.ClassDef): 

1170 if not has_known_bases(value): 

1171 return True 

1172 # classobj can only be iterable if it has an iterable metaclass 

1173 meta = value.metaclass() 

1174 if meta is not None: 

1175 if protocol_callback(meta): 

1176 return True 

1177 if isinstance(value, astroid.BaseInstance): 

1178 if not has_known_bases(value): 

1179 return True 

1180 if value.has_dynamic_getattr(): 

1181 return True 

1182 if protocol_callback(value): 

1183 return True 

1184 

1185 if isinstance(value, nodes.ComprehensionScope): 

1186 return True 

1187 

1188 if ( 

1189 isinstance(value, astroid.bases.Proxy) 

1190 and isinstance(value._proxied, astroid.BaseInstance) 

1191 and has_known_bases(value._proxied) 

1192 ): 

1193 value = value._proxied 

1194 return protocol_callback(value) 

1195 

1196 return False 

1197 

1198 

1199def is_iterable(value: nodes.NodeNG, check_async: bool = False) -> bool: 

1200 if check_async: 

1201 protocol_check = _supports_async_iteration_protocol 

1202 else: 

1203 protocol_check = _supports_iteration_protocol 

1204 return _supports_protocol(value, protocol_check) 

1205 

1206 

1207def is_mapping(value: nodes.NodeNG) -> bool: 

1208 return _supports_protocol(value, _supports_mapping_protocol) 

1209 

1210 

1211def supports_membership_test(value: nodes.NodeNG) -> bool: 

1212 supported = _supports_protocol(value, _supports_membership_test_protocol) 

1213 return supported or is_iterable(value) 

1214 

1215 

1216def supports_getitem(value: nodes.NodeNG, node: nodes.NodeNG) -> bool: 

1217 if isinstance(value, nodes.ClassDef): 

1218 if _supports_protocol_method(value, CLASS_GETITEM_METHOD): 

1219 return True 

1220 if subscriptable_with_postponed_evaluation_enabled(node): 

1221 return True 

1222 return _supports_protocol(value, _supports_getitem_protocol) 

1223 

1224 

1225def supports_setitem(value: nodes.NodeNG, _: nodes.NodeNG) -> bool: 

1226 return _supports_protocol(value, _supports_setitem_protocol) 

1227 

1228 

1229def supports_delitem(value: nodes.NodeNG, _: nodes.NodeNG) -> bool: 

1230 return _supports_protocol(value, _supports_delitem_protocol) 

1231 

1232 

1233def _get_python_type_of_node(node: nodes.NodeNG) -> str | None: 

1234 pytype = getattr(node, "pytype", None) 

1235 if callable(pytype): 

1236 return pytype() 

1237 return None 

1238 

1239 

1240@lru_cache(maxsize=1024) 

1241def safe_infer( 

1242 node: nodes.NodeNG, context: InferenceContext | None = None 

1243) -> nodes.NodeNG | type[astroid.Uninferable] | None: 

1244 """Return the inferred value for the given node. 

1245 

1246 Return None if inference failed or if there is some ambiguity (more than 

1247 one node has been inferred of different types). 

1248 """ 

1249 inferred_types: set[str | None] = set() 

1250 try: 

1251 infer_gen = node.infer(context=context) 

1252 value = next(infer_gen) 

1253 except astroid.InferenceError: 

1254 return None 

1255 

1256 if value is not astroid.Uninferable: 

1257 inferred_types.add(_get_python_type_of_node(value)) 

1258 

1259 try: 

1260 for inferred in infer_gen: 

1261 inferred_type = _get_python_type_of_node(inferred) 

1262 if inferred_type not in inferred_types: 

1263 return None # If there is ambiguity on the inferred node. 

1264 if ( 

1265 isinstance(inferred, nodes.FunctionDef) 

1266 and inferred.args.args is not None 

1267 and isinstance(value, nodes.FunctionDef) 

1268 and value.args.args is not None 

1269 and len(inferred.args.args) != len(value.args.args) 

1270 ): 

1271 return None # Different number of arguments indicates ambiguity 

1272 except astroid.InferenceError: 

1273 return None # There is some kind of ambiguity 

1274 except StopIteration: 

1275 return value 

1276 return value if len(inferred_types) <= 1 else None 

1277 

1278 

1279@lru_cache(maxsize=512) 

1280def infer_all( 

1281 node: nodes.NodeNG, context: InferenceContext = None 

1282) -> list[nodes.NodeNG]: 

1283 try: 

1284 return list(node.infer(context=context)) 

1285 except astroid.InferenceError: 

1286 return [] 

1287 

1288 

1289def has_known_bases(klass: nodes.ClassDef, context=None) -> bool: 

1290 """Return true if all base classes of a class could be inferred.""" 

1291 try: 

1292 return klass._all_bases_known 

1293 except AttributeError: 

1294 pass 

1295 for base in klass.bases: 

1296 result = safe_infer(base, context=context) 

1297 if ( 

1298 not isinstance(result, nodes.ClassDef) 

1299 or result is klass 

1300 or not has_known_bases(result, context=context) 

1301 ): 

1302 klass._all_bases_known = False 

1303 return False 

1304 klass._all_bases_known = True 

1305 return True 

1306 

1307 

1308def is_none(node: nodes.NodeNG) -> bool: 

1309 return ( 

1310 node is None 

1311 or (isinstance(node, nodes.Const) and node.value is None) 

1312 or (isinstance(node, nodes.Name) and node.name == "None") 

1313 ) 

1314 

1315 

1316def node_type(node: nodes.NodeNG) -> nodes.NodeNG | None: 

1317 """Return the inferred type for `node`. 

1318 

1319 If there is more than one possible type, or if inferred type is Uninferable or None, 

1320 return None 

1321 """ 

1322 # check there is only one possible type for the assign node. Else we 

1323 # don't handle it for now 

1324 types: set[nodes.NodeNG] = set() 

1325 try: 

1326 for var_type in node.infer(): 

1327 if var_type == astroid.Uninferable or is_none(var_type): 

1328 continue 

1329 types.add(var_type) 

1330 if len(types) > 1: 

1331 return None 

1332 except astroid.InferenceError: 

1333 return None 

1334 return types.pop() if types else None 

1335 

1336 

1337def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool: 

1338 """Check if the given function node is a singledispatch function.""" 

1339 

1340 singledispatch_qnames = ( 

1341 "functools.singledispatch", 

1342 "singledispatch.singledispatch", 

1343 ) 

1344 

1345 if not isinstance(node, nodes.FunctionDef): 

1346 return False 

1347 

1348 decorators = node.decorators.nodes if node.decorators else [] 

1349 for decorator in decorators: 

1350 # func.register are function calls 

1351 if not isinstance(decorator, nodes.Call): 

1352 continue 

1353 

1354 func = decorator.func 

1355 if not isinstance(func, nodes.Attribute) or func.attrname != "register": 

1356 continue 

1357 

1358 try: 

1359 func_def = next(func.expr.infer()) 

1360 except astroid.InferenceError: 

1361 continue 

1362 

1363 if isinstance(func_def, nodes.FunctionDef): 

1364 return decorated_with(func_def, singledispatch_qnames) 

1365 

1366 return False 

1367 

1368 

1369def get_node_last_lineno(node: nodes.NodeNG) -> int: 

1370 """Get the last lineno of the given node. 

1371 

1372 For a simple statement this will just be node.lineno, 

1373 but for a node that has child statements (e.g. a method) this will be the lineno of the last 

1374 child statement recursively. 

1375 """ 

1376 # 'finalbody' is always the last clause in a try statement, if present 

1377 if getattr(node, "finalbody", False): 

1378 return get_node_last_lineno(node.finalbody[-1]) 

1379 # For if, while, and for statements 'orelse' is always the last clause. 

1380 # For try statements 'orelse' is the last in the absence of a 'finalbody' 

1381 if getattr(node, "orelse", False): 

1382 return get_node_last_lineno(node.orelse[-1]) 

1383 # try statements have the 'handlers' last if there is no 'orelse' or 'finalbody' 

1384 if getattr(node, "handlers", False): 

1385 return get_node_last_lineno(node.handlers[-1]) 

1386 # All compound statements have a 'body' 

1387 if getattr(node, "body", False): 

1388 return get_node_last_lineno(node.body[-1]) 

1389 # Not a compound statement 

1390 return node.lineno 

1391 

1392 

1393def is_postponed_evaluation_enabled(node: nodes.NodeNG) -> bool: 

1394 """Check if the postponed evaluation of annotations is enabled.""" 

1395 module = node.root() 

1396 return "annotations" in module.future_imports 

1397 

1398 

1399def is_class_subscriptable_pep585_with_postponed_evaluation_enabled( 

1400 value: nodes.ClassDef, node: nodes.NodeNG 

1401) -> bool: 

1402 """Check if class is subscriptable with PEP 585 and 

1403 postponed evaluation enabled. 

1404 """ 

1405 warnings.warn( 

1406 "'is_class_subscriptable_pep585_with_postponed_evaluation_enabled' has been " 

1407 "deprecated and will be removed in pylint 3.0. " 

1408 "Use 'subscriptable_with_postponed_evaluation_enabled' instead.", 

1409 DeprecationWarning, 

1410 ) 

1411 return ( 

1412 is_postponed_evaluation_enabled(node) 

1413 and value.qname() in SUBSCRIPTABLE_CLASSES_PEP585 

1414 and is_node_in_type_annotation_context(node) 

1415 ) 

1416 

1417 

1418def subscriptable_with_postponed_evaluation_enabled(node: nodes.NodeNG) -> bool: 

1419 """Check if class can be subscriptable in type annotation context.""" 

1420 return is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context( 

1421 node 

1422 ) 

1423 

1424 

1425def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool: 

1426 """Check if node is in type annotation context. 

1427 

1428 Check for 'AnnAssign', function 'Arguments', 

1429 or part of function return type annotation. 

1430 """ 

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

1432 current_node, parent_node = node, node.parent 

1433 while True: 

1434 if ( 

1435 isinstance(parent_node, nodes.AnnAssign) 

1436 and parent_node.annotation == current_node 

1437 or isinstance(parent_node, nodes.Arguments) 

1438 and current_node 

1439 in ( 

1440 *parent_node.annotations, 

1441 *parent_node.posonlyargs_annotations, 

1442 *parent_node.kwonlyargs_annotations, 

1443 parent_node.varargannotation, 

1444 parent_node.kwargannotation, 

1445 ) 

1446 or isinstance(parent_node, nodes.FunctionDef) 

1447 and parent_node.returns == current_node 

1448 ): 

1449 return True 

1450 current_node, parent_node = parent_node, parent_node.parent 

1451 if isinstance(parent_node, nodes.Module): 

1452 return False 

1453 

1454 

1455def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool: 

1456 """Check if first node is a subclass of second node. 

1457 

1458 :param child: Node to check for subclass. 

1459 :param parent: Node to check for superclass. 

1460 :returns: True if child is derived from parent. False otherwise. 

1461 """ 

1462 if not all(isinstance(node, nodes.ClassDef) for node in (child, parent)): 

1463 return False 

1464 

1465 for ancestor in child.ancestors(): 

1466 try: 

1467 if astroid.helpers.is_subtype(ancestor, parent): 

1468 return True 

1469 except astroid.exceptions._NonDeducibleTypeHierarchy: 

1470 continue 

1471 return False 

1472 

1473 

1474@lru_cache(maxsize=1024) 

1475def is_overload_stub(node: nodes.NodeNG) -> bool: 

1476 """Check if a node is a function stub decorated with typing.overload. 

1477 

1478 :param node: Node to check. 

1479 :returns: True if node is an overload function stub. False otherwise. 

1480 """ 

1481 decorators = getattr(node, "decorators", None) 

1482 return bool(decorators and decorated_with(node, ["typing.overload", "overload"])) 

1483 

1484 

1485def is_protocol_class(cls: nodes.NodeNG) -> bool: 

1486 """Check if the given node represents a protocol class. 

1487 

1488 :param cls: The node to check 

1489 :returns: True if the node is a typing protocol class, false otherwise. 

1490 """ 

1491 if not isinstance(cls, nodes.ClassDef): 

1492 return False 

1493 

1494 # Use .ancestors() since not all protocol classes can have 

1495 # their mro deduced. 

1496 return any(parent.qname() in TYPING_PROTOCOLS for parent in cls.ancestors()) 

1497 

1498 

1499def is_call_of_name(node: nodes.NodeNG, name: str) -> bool: 

1500 """Checks if node is a function call with the given name.""" 

1501 return ( 

1502 isinstance(node, nodes.Call) 

1503 and isinstance(node.func, nodes.Name) 

1504 and node.func.name == name 

1505 ) 

1506 

1507 

1508def is_test_condition( 

1509 node: nodes.NodeNG, 

1510 parent: nodes.NodeNG | None = None, 

1511) -> bool: 

1512 """Returns true if the given node is being tested for truthiness.""" 

1513 parent = parent or node.parent 

1514 if isinstance(parent, (nodes.While, nodes.If, nodes.IfExp, nodes.Assert)): 

1515 return node is parent.test or parent.test.parent_of(node) 

1516 if isinstance(parent, nodes.Comprehension): 

1517 return node in parent.ifs 

1518 return is_call_of_name(parent, "bool") and parent.parent_of(node) 

1519 

1520 

1521def is_classdef_type(node: nodes.ClassDef) -> bool: 

1522 """Test if ClassDef node is Type.""" 

1523 if node.name == "type": 

1524 return True 

1525 return any(isinstance(b, nodes.Name) and b.name == "type" for b in node.bases) 

1526 

1527 

1528def is_attribute_typed_annotation( 

1529 node: nodes.ClassDef | astroid.Instance, attr_name: str 

1530) -> bool: 

1531 """Test if attribute is typed annotation in current node 

1532 or any base nodes. 

1533 """ 

1534 attribute = node.locals.get(attr_name, [None])[0] 

1535 if ( 

1536 attribute 

1537 and isinstance(attribute, nodes.AssignName) 

1538 and isinstance(attribute.parent, nodes.AnnAssign) 

1539 ): 

1540 return True 

1541 for base in node.bases: 

1542 inferred = safe_infer(base) 

1543 if ( 

1544 inferred 

1545 and isinstance(inferred, nodes.ClassDef) 

1546 and is_attribute_typed_annotation(inferred, attr_name) 

1547 ): 

1548 return True 

1549 return False 

1550 

1551 

1552def is_assign_name_annotated_with(node: nodes.AssignName, typing_name: str) -> bool: 

1553 """Test if AssignName node has `typing_name` annotation. 

1554 

1555 Especially useful to check for `typing._SpecialForm` instances 

1556 like: `Union`, `Optional`, `Literal`, `ClassVar`, `Final`. 

1557 """ 

1558 if not isinstance(node.parent, nodes.AnnAssign): 

1559 return False 

1560 annotation = node.parent.annotation 

1561 if isinstance(annotation, nodes.Subscript): 

1562 annotation = annotation.value 

1563 if ( 

1564 isinstance(annotation, nodes.Name) 

1565 and annotation.name == typing_name 

1566 or isinstance(annotation, nodes.Attribute) 

1567 and annotation.attrname == typing_name 

1568 ): 

1569 return True 

1570 return False 

1571 

1572 

1573def get_iterating_dictionary_name(node: nodes.For | nodes.Comprehension) -> str | None: 

1574 """Get the name of the dictionary which keys are being iterated over on 

1575 a ``nodes.For`` or ``nodes.Comprehension`` node. 

1576 

1577 If the iterating object is not either the keys method of a dictionary 

1578 or a dictionary itself, this returns None. 

1579 """ 

1580 # Is it a proper keys call? 

1581 if ( 

1582 isinstance(node.iter, nodes.Call) 

1583 and isinstance(node.iter.func, nodes.Attribute) 

1584 and node.iter.func.attrname == "keys" 

1585 ): 

1586 inferred = safe_infer(node.iter.func) 

1587 if not isinstance(inferred, astroid.BoundMethod): 

1588 return None 

1589 return node.iter.as_string().rpartition(".keys")[0] 

1590 

1591 # Is it a dictionary? 

1592 if isinstance(node.iter, (nodes.Name, nodes.Attribute)): 

1593 inferred = safe_infer(node.iter) 

1594 if not isinstance(inferred, nodes.Dict): 

1595 return None 

1596 return node.iter.as_string() 

1597 

1598 return None 

1599 

1600 

1601def get_subscript_const_value(node: nodes.Subscript) -> nodes.Const: 

1602 """Returns the value 'subscript.slice' of a Subscript node. 

1603 

1604 :param node: Subscript Node to extract value from 

1605 :returns: Const Node containing subscript value 

1606 :raises InferredTypeError: if the subscript node cannot be inferred as a Const 

1607 """ 

1608 inferred = safe_infer(node.slice) 

1609 if not isinstance(inferred, nodes.Const): 

1610 raise InferredTypeError("Subscript.slice cannot be inferred as a nodes.Const") 

1611 

1612 return inferred 

1613 

1614 

1615def get_import_name(importnode: nodes.Import | nodes.ImportFrom, modname: str) -> str: 

1616 """Get a prepared module name from the given import node. 

1617 

1618 In the case of relative imports, this will return the 

1619 absolute qualified module name, which might be useful 

1620 for debugging. Otherwise, the initial module name 

1621 is returned unchanged. 

1622 

1623 :param importnode: node representing import statement. 

1624 :param modname: module name from import statement. 

1625 :returns: absolute qualified module name of the module 

1626 used in import. 

1627 """ 

1628 if isinstance(importnode, nodes.ImportFrom) and importnode.level: 

1629 root = importnode.root() 

1630 if isinstance(root, nodes.Module): 

1631 try: 

1632 return root.relative_to_absolute_name(modname, level=importnode.level) 

1633 except TooManyLevelsError: 

1634 return modname 

1635 return modname 

1636 

1637 

1638def is_sys_guard(node: nodes.If) -> bool: 

1639 """Return True if IF stmt is a sys.version_info guard. 

1640 

1641 >>> import sys 

1642 >>> if sys.version_info > (3, 8): 

1643 >>> from typing import Literal 

1644 >>> else: 

1645 >>> from typing_extensions import Literal 

1646 """ 

1647 if isinstance(node.test, nodes.Compare): 

1648 value = node.test.left 

1649 if isinstance(value, nodes.Subscript): 

1650 value = value.value 

1651 if ( 

1652 isinstance(value, nodes.Attribute) 

1653 and value.as_string() == "sys.version_info" 

1654 ): 

1655 return True 

1656 

1657 return False 

1658 

1659 

1660def is_typing_guard(node: nodes.If) -> bool: 

1661 """Return True if IF stmt is a typing guard. 

1662 

1663 >>> from typing import TYPE_CHECKING 

1664 >>> if TYPE_CHECKING: 

1665 >>> from xyz import a 

1666 """ 

1667 return isinstance( 

1668 node.test, (nodes.Name, nodes.Attribute) 

1669 ) and node.test.as_string().endswith("TYPE_CHECKING") 

1670 

1671 

1672def is_node_in_typing_guarded_import_block(node: nodes.NodeNG) -> bool: 

1673 """Return True if node is part for guarded `typing.TYPE_CHECKING` if block.""" 

1674 return isinstance(node.parent, nodes.If) and is_typing_guard(node.parent) 

1675 

1676 

1677def is_node_in_guarded_import_block(node: nodes.NodeNG) -> bool: 

1678 """Return True if node is part for guarded if block. 

1679 

1680 I.e. `sys.version_info` or `typing.TYPE_CHECKING` 

1681 """ 

1682 return isinstance(node.parent, nodes.If) and ( 

1683 is_sys_guard(node.parent) or is_typing_guard(node.parent) 

1684 ) 

1685 

1686 

1687def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: 

1688 """Check if the given variable name is reassigned in the same scope after the current node.""" 

1689 return any( 

1690 a.name == varname and a.lineno > node.lineno 

1691 for a in node.scope().nodes_of_class( 

1692 (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef) 

1693 ) 

1694 ) 

1695 

1696 

1697def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool: 

1698 """Check if the given variable name is deleted in the same scope after the current node.""" 

1699 return any( 

1700 getattr(target, "name", None) == varname and target.lineno > node.lineno 

1701 for del_node in node.scope().nodes_of_class(nodes.Delete) 

1702 for target in del_node.targets 

1703 ) 

1704 

1705 

1706def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool: 

1707 """Checks whether a function body only consists of a single Ellipsis.""" 

1708 return ( 

1709 len(node.body) == 1 

1710 and isinstance(node.body[0], nodes.Expr) 

1711 and isinstance(node.body[0].value, nodes.Const) 

1712 and node.body[0].value.value == Ellipsis 

1713 ) 

1714 

1715 

1716def is_base_container(node: nodes.NodeNG | None) -> bool: 

1717 return isinstance(node, nodes.BaseContainer) and not node.elts 

1718 

1719 

1720def is_empty_dict_literal(node: nodes.NodeNG | None) -> bool: 

1721 return isinstance(node, nodes.Dict) and not node.items 

1722 

1723 

1724def is_empty_str_literal(node: nodes.NodeNG | None) -> bool: 

1725 return ( 

1726 isinstance(node, nodes.Const) and isinstance(node.value, str) and not node.value 

1727 ) 

1728 

1729 

1730def returns_bool(node: nodes.NodeNG) -> bool: 

1731 """Returns true if a node is a return that returns a constant boolean.""" 

1732 return ( 

1733 isinstance(node, nodes.Return) 

1734 and isinstance(node.value, nodes.Const) 

1735 and node.value.value in {True, False} 

1736 ) 

1737 

1738 

1739def get_node_first_ancestor_of_type( 

1740 node: nodes.NodeNG, ancestor_type: type[_NodeT] | tuple[type[_NodeT], ...] 

1741) -> _NodeT | None: 

1742 """Return the first parent node that is any of the provided types (or None).""" 

1743 for ancestor in node.node_ancestors(): 

1744 if isinstance(ancestor, ancestor_type): 

1745 return ancestor 

1746 return None 

1747 

1748 

1749def get_node_first_ancestor_of_type_and_its_child( 

1750 node: nodes.NodeNG, ancestor_type: type[_NodeT] | tuple[type[_NodeT], ...] 

1751) -> tuple[None, None] | tuple[_NodeT, nodes.NodeNG]: 

1752 """Modified version of get_node_first_ancestor_of_type to also return the 

1753 descendant visited directly before reaching the sought ancestor. 

1754 

1755 Useful for extracting whether a statement is guarded by a try, except, or finally 

1756 when searching for a TryFinally ancestor. 

1757 """ 

1758 child = node 

1759 for ancestor in node.node_ancestors(): 

1760 if isinstance(ancestor, ancestor_type): 

1761 return (ancestor, child) 

1762 child = ancestor 

1763 return None, None 

1764 

1765 

1766def in_type_checking_block(node: nodes.NodeNG) -> bool: 

1767 """Check if a node is guarded by a TYPE_CHECKS guard.""" 

1768 return any( 

1769 isinstance(ancestor, nodes.If) 

1770 and ancestor.test.as_string() in TYPING_TYPE_CHECKS_GUARDS 

1771 for ancestor in node.node_ancestors() 

1772 ) 

1773 

1774 

1775@lru_cache() 

1776def in_for_else_branch(parent: nodes.NodeNG, stmt: nodes.Statement) -> bool: 

1777 """Returns True if stmt is inside the else branch for a parent For stmt.""" 

1778 return isinstance(parent, nodes.For) and any( 

1779 else_stmt.parent_of(stmt) or else_stmt == stmt for else_stmt in parent.orelse 

1780 )