Index: third_party/pylint/checkers/exceptions.py |
diff --git a/third_party/pylint/checkers/exceptions.py b/third_party/pylint/checkers/exceptions.py |
index e8e5a54120327730bd0959a3cfd610c6aa110be5..88a8f225e2e80304f8959fbcbe1a39a0aed02267 100644 |
--- a/third_party/pylint/checkers/exceptions.py |
+++ b/third_party/pylint/checkers/exceptions.py |
@@ -16,18 +16,22 @@ |
""" |
import sys |
-from logilab.common.compat import builtins |
-BUILTINS_NAME = builtins.__name__ |
import astroid |
from astroid import YES, Instance, unpack_infer, List, Tuple |
+from logilab.common.compat import builtins |
from pylint.checkers import BaseChecker |
from pylint.checkers.utils import ( |
- is_empty, is_raising, |
- check_messages, inherit_from_std_ex, |
- EXCEPTIONS_MODULE, has_known_bases) |
+ is_empty, |
+ is_raising, |
+ check_messages, |
+ inherit_from_std_ex, |
+ EXCEPTIONS_MODULE, |
+ has_known_bases, |
+ safe_infer) |
from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
+ |
def _annotated_unpack_infer(stmt, context=None): |
""" |
Recursively generate nodes inferred by the given statement. |
@@ -35,33 +39,21 @@ def _annotated_unpack_infer(stmt, context=None): |
Returns an iterator which yields tuples in the format |
('original node', 'infered node'). |
""" |
- # TODO: the same code as unpack_infer, except for the annotated |
- # return. We need this type of annotation only here and |
- # there is no point in complicating the API for unpack_infer. |
- # If the need arises, this behaviour can be promoted to unpack_infer |
- # as well. |
if isinstance(stmt, (List, Tuple)): |
for elt in stmt.elts: |
- for infered_elt in unpack_infer(elt, context): |
- yield elt, infered_elt |
+ inferred = safe_infer(elt) |
+ if inferred and inferred is not YES: |
+ yield elt, inferred |
return |
- # if infered is a final node, return it and stop |
- infered = next(stmt.infer(context)) |
- if infered is stmt: |
- yield stmt, infered |
- return |
- # else, infer recursivly, except YES object that should be returned as is |
for infered in stmt.infer(context): |
if infered is YES: |
- yield stmt, infered |
- else: |
- for inf_inf in unpack_infer(infered, context): |
- yield stmt, inf_inf |
+ continue |
+ yield stmt, infered |
PY3K = sys.version_info >= (3, 0) |
OVERGENERAL_EXCEPTIONS = ('Exception',) |
- |
+BUILTINS_NAME = builtins.__name__ |
MSGS = { |
'E0701': ('Bad except clauses order (%s)', |
'bad-except-order', |
@@ -145,21 +137,8 @@ class ExceptionsChecker(BaseChecker): |
if node.exc is None: |
return |
if PY3K and node.cause: |
- try: |
- cause = next(node.cause.infer()) |
- except astroid.InferenceError: |
- pass |
- else: |
- if cause is YES: |
- return |
- if isinstance(cause, astroid.Const): |
- if cause.value is not None: |
- self.add_message('bad-exception-context', |
- node=node) |
- elif (not isinstance(cause, astroid.Class) and |
- not inherit_from_std_ex(cause)): |
- self.add_message('bad-exception-context', |
- node=node) |
+ self._check_bad_exception_context(node) |
+ |
expr = node.exc |
if self._check_raise_value(node, expr): |
return |
@@ -170,23 +149,59 @@ class ExceptionsChecker(BaseChecker): |
return |
self._check_raise_value(node, value) |
+ def _check_bad_exception_context(self, node): |
+ """Verify that the exception context is properly set. |
+ |
+ An exception context can be only `None` or an exception. |
+ """ |
+ cause = safe_infer(node.cause) |
+ if cause in (YES, None): |
+ return |
+ if isinstance(cause, astroid.Const): |
+ if cause.value is not None: |
+ self.add_message('bad-exception-context', |
+ node=node) |
+ elif (not isinstance(cause, astroid.Class) and |
+ not inherit_from_std_ex(cause)): |
+ self.add_message('bad-exception-context', |
+ node=node) |
+ |
def _check_raise_value(self, node, expr): |
"""check for bad values, string exception and class inheritance |
""" |
value_found = True |
if isinstance(expr, astroid.Const): |
value = expr.value |
- if isinstance(value, str): |
+ if not isinstance(value, str): |
# raising-string will be emitted from python3 porting checker. |
- pass |
- else: |
self.add_message('raising-bad-type', node=node, |
args=value.__class__.__name__) |
- elif (isinstance(expr, astroid.Name) and \ |
- expr.name in ('None', 'True', 'False')) or \ |
- isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
- astroid.Module, astroid.Function)): |
- self.add_message('raising-bad-type', node=node, args=expr.name) |
+ elif ((isinstance(expr, astroid.Name) and |
+ expr.name in ('None', 'True', 'False')) or |
+ isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
+ astroid.Module, astroid.Function))): |
+ emit = True |
+ if not PY3K and isinstance(expr, astroid.Tuple): |
+ # On Python 2, using the following is not an error: |
+ # raise (ZeroDivisionError, None) |
+ # raise (ZeroDivisionError, ) |
+ # What's left to do is to check that the first |
+ # argument is indeed an exception. |
+ # Verifying the other arguments is not |
+ # the scope of this check. |
+ first = expr.elts[0] |
+ inferred = safe_infer(first) |
+ if isinstance(inferred, Instance): |
+ # pylint: disable=protected-access |
+ inferred = inferred._proxied |
+ if (inferred is YES or |
+ isinstance(inferred, astroid.Class) |
+ and inherit_from_std_ex(inferred)): |
+ emit = False |
+ if emit: |
+ self.add_message('raising-bad-type', |
+ node=node, |
+ args=expr.name) |
elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
or (isinstance(expr, astroid.CallFunc) and |
isinstance(expr.func, astroid.Name) and |
@@ -194,22 +209,65 @@ class ExceptionsChecker(BaseChecker): |
self.add_message('notimplemented-raised', node=node) |
elif isinstance(expr, (Instance, astroid.Class)): |
if isinstance(expr, Instance): |
+ # pylint: disable=protected-access |
expr = expr._proxied |
if (isinstance(expr, astroid.Class) and |
- not inherit_from_std_ex(expr) and |
- expr.root().name != BUILTINS_NAME): |
+ not inherit_from_std_ex(expr)): |
if expr.newstyle: |
self.add_message('raising-non-exception', node=node) |
else: |
+ if has_known_bases(expr): |
+ confidence = INFERENCE |
+ else: |
+ confidence = INFERENCE_FAILURE |
self.add_message( |
'nonstandard-exception', node=node, |
- confidence=INFERENCE if has_known_bases(expr) else INFERENCE_FAILURE) |
+ confidence=confidence) |
else: |
value_found = False |
else: |
value_found = False |
return value_found |
+ def _check_catching_non_exception(self, handler, exc, part): |
+ if isinstance(exc, astroid.Tuple): |
+ # Check if it is a tuple of exceptions. |
+ inferred = [safe_infer(elt) for elt in exc.elts] |
+ if any(node is astroid.YES for node in inferred): |
+ # Don't emit if we don't know every component. |
+ return |
+ if all(node and inherit_from_std_ex(node) |
+ for node in inferred): |
+ return |
+ |
+ if not isinstance(exc, astroid.Class): |
+ # Don't emit the warning if the infered stmt |
+ # is None, but the exception handler is something else, |
+ # maybe it was redefined. |
+ if (isinstance(exc, astroid.Const) and |
+ exc.value is None): |
+ if ((isinstance(handler.type, astroid.Const) and |
+ handler.type.value is None) or |
+ handler.type.parent_of(exc)): |
+ # If the exception handler catches None or |
+ # the exception component, which is None, is |
+ # defined by the entire exception handler, then |
+ # emit a warning. |
+ self.add_message('catching-non-exception', |
+ node=handler.type, |
+ args=(part.as_string(), )) |
+ else: |
+ self.add_message('catching-non-exception', |
+ node=handler.type, |
+ args=(part.as_string(), )) |
+ return |
+ if (not inherit_from_std_ex(exc) and |
+ exc.root().name != BUILTINS_NAME): |
+ if has_known_bases(exc): |
+ self.add_message('catching-non-exception', |
+ node=handler.type, |
+ args=(exc.name, )) |
+ |
@check_messages('bare-except', 'broad-except', 'pointless-except', |
'binary-op-exception', 'bad-except-order', |
'catching-non-exception') |
@@ -242,28 +300,14 @@ class ExceptionsChecker(BaseChecker): |
for part, exc in excs: |
if exc is YES: |
continue |
- if isinstance(exc, astroid.Instance) and inherit_from_std_ex(exc): |
+ if (isinstance(exc, astroid.Instance) |
+ and inherit_from_std_ex(exc)): |
+ # pylint: disable=protected-access |
exc = exc._proxied |
+ |
+ self._check_catching_non_exception(handler, exc, part) |
+ |
if not isinstance(exc, astroid.Class): |
- # Don't emit the warning if the infered stmt |
- # is None, but the exception handler is something else, |
- # maybe it was redefined. |
- if (isinstance(exc, astroid.Const) and |
- exc.value is None): |
- if ((isinstance(handler.type, astroid.Const) and |
- handler.type.value is None) or |
- handler.type.parent_of(exc)): |
- # If the exception handler catches None or |
- # the exception component, which is None, is |
- # defined by the entire exception handler, then |
- # emit a warning. |
- self.add_message('catching-non-exception', |
- node=handler.type, |
- args=(part.as_string(), )) |
- else: |
- self.add_message('catching-non-exception', |
- node=handler.type, |
- args=(part.as_string(), )) |
continue |
exc_ancestors = [anc for anc in exc.ancestors() |
@@ -280,13 +324,6 @@ class ExceptionsChecker(BaseChecker): |
self.add_message('broad-except', |
args=exc.name, node=handler.type) |
- if (not inherit_from_std_ex(exc) and |
- exc.root().name != BUILTINS_NAME): |
- if has_known_bases(exc): |
- self.add_message('catching-non-exception', |
- node=handler.type, |
- args=(exc.name, )) |
- |
exceptions_classes += [exc for _, exc in excs] |