blob: 8d35e4ce329ddb42a15676e1a80831fb92a0fe4c [file] [log] [blame]
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
"""Looks for overlapping exceptions."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import astroid
from astroid import nodes, util
from pylint import checkers
from pylint.checkers import utils
from pylint.checkers.exceptions import _annotated_unpack_infer
if TYPE_CHECKING:
from pylint.lint import PyLinter
class OverlappingExceptionsChecker(checkers.BaseChecker):
"""Checks for two or more exceptions in the same exception handler
clause that are identical or parts of the same inheritance hierarchy.
(i.e. overlapping).
"""
name = "overlap-except"
msgs = {
"W0714": (
"Overlapping exceptions (%s)",
"overlapping-except",
"Used when exceptions in handler overlap or are identical",
)
}
options = ()
@utils.only_required_for_messages("overlapping-except")
def visit_try(self, node: nodes.Try) -> None:
"""Check for empty except."""
for handler in node.handlers:
if handler.type is None:
continue
if isinstance(handler.type, astroid.BoolOp):
continue
try:
excs = list(_annotated_unpack_infer(handler.type))
except astroid.InferenceError:
continue
handled_in_clause: list[tuple[Any, Any]] = []
for part, exc in excs:
if isinstance(exc, util.UninferableBase):
continue
if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex(exc):
exc = exc._proxied
if not isinstance(exc, astroid.ClassDef):
continue
exc_ancestors = [
anc for anc in exc.ancestors() if isinstance(anc, astroid.ClassDef)
]
for prev_part, prev_exc in handled_in_clause:
prev_exc_ancestors = [
anc
for anc in prev_exc.ancestors()
if isinstance(anc, astroid.ClassDef)
]
if exc == prev_exc:
self.add_message(
"overlapping-except",
node=handler.type,
args=f"{prev_part.as_string()} and {part.as_string()} are the same",
)
elif prev_exc in exc_ancestors or exc in prev_exc_ancestors:
ancestor = part if exc in prev_exc_ancestors else prev_part
descendant = part if prev_exc in exc_ancestors else prev_part
self.add_message(
"overlapping-except",
node=handler.type,
args=f"{ancestor.as_string()} is an ancestor class of {descendant.as_string()}",
)
handled_in_clause += [(part, exc)]
def register(linter: PyLinter) -> None:
linter.register_checker(OverlappingExceptionsChecker(linter))