overlapping_exceptions.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Looks for overlapping exceptions."""
  5. from __future__ import annotations
  6. from typing import TYPE_CHECKING, Any
  7. import astroid
  8. from astroid import nodes, util
  9. from pylint import checkers
  10. from pylint.checkers import utils
  11. from pylint.checkers.exceptions import _annotated_unpack_infer
  12. if TYPE_CHECKING:
  13. from pylint.lint import PyLinter
  14. class OverlappingExceptionsChecker(checkers.BaseChecker):
  15. """Checks for two or more exceptions in the same exception handler
  16. clause that are identical or parts of the same inheritance hierarchy.
  17. (i.e. overlapping).
  18. """
  19. name = "overlap-except"
  20. msgs = {
  21. "W0714": (
  22. "Overlapping exceptions (%s)",
  23. "overlapping-except",
  24. "Used when exceptions in handler overlap or are identical",
  25. )
  26. }
  27. options = ()
  28. @utils.only_required_for_messages("overlapping-except")
  29. def visit_tryexcept(self, node: nodes.TryExcept) -> None:
  30. """Check for empty except."""
  31. for handler in node.handlers:
  32. if handler.type is None:
  33. continue
  34. if isinstance(handler.type, astroid.BoolOp):
  35. continue
  36. try:
  37. excs = list(_annotated_unpack_infer(handler.type))
  38. except astroid.InferenceError:
  39. continue
  40. handled_in_clause: list[tuple[Any, Any]] = []
  41. for part, exc in excs:
  42. if isinstance(exc, util.UninferableBase):
  43. continue
  44. if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex(exc):
  45. exc = exc._proxied
  46. if not isinstance(exc, astroid.ClassDef):
  47. continue
  48. exc_ancestors = [
  49. anc for anc in exc.ancestors() if isinstance(anc, astroid.ClassDef)
  50. ]
  51. for prev_part, prev_exc in handled_in_clause:
  52. prev_exc_ancestors = [
  53. anc
  54. for anc in prev_exc.ancestors()
  55. if isinstance(anc, astroid.ClassDef)
  56. ]
  57. if exc == prev_exc:
  58. self.add_message(
  59. "overlapping-except",
  60. node=handler.type,
  61. args=f"{prev_part.as_string()} and {part.as_string()} are the same",
  62. )
  63. elif prev_exc in exc_ancestors or exc in prev_exc_ancestors:
  64. ancestor = part if exc in prev_exc_ancestors else prev_part
  65. descendant = part if prev_exc in exc_ancestors else prev_part
  66. self.add_message(
  67. "overlapping-except",
  68. node=handler.type,
  69. args=f"{ancestor.as_string()} is an ancestor class of {descendant.as_string()}",
  70. )
  71. handled_in_clause += [(part, exc)]
  72. def register(linter: PyLinter) -> None:
  73. linter.register_checker(OverlappingExceptionsChecker(linter))