Index: third_party/pylint/checkers/typecheck.py |
diff --git a/third_party/pylint/checkers/typecheck.py b/third_party/pylint/checkers/typecheck.py |
index fb8f3c6e769c5217992bb7d82e4e5fb2d0dd1368..b0a591f485c617f96a69ac0ffab43c1c74a25b33 100644 |
--- a/third_party/pylint/checkers/typecheck.py |
+++ b/third_party/pylint/checkers/typecheck.py |
@@ -1,4 +1,4 @@ |
-# Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE). |
+# Copyright (c) 2006-2010 LOGILAB S.A. (Paris, FRANCE). |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr |
# |
# This program is free software; you can redistribute it and/or modify it under |
@@ -12,128 +12,59 @@ |
# |
# You should have received a copy of the GNU General Public License along with |
# this program; if not, write to the Free Software Foundation, Inc., |
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
-"""try to find more bugs in the code using astroid inference capabilities |
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
+"""try to find more bugs in the code using astng inference capabilities |
""" |
import re |
import shlex |
-import astroid |
-from astroid import InferenceError, NotFoundError, YES, Instance |
-from astroid.bases import BUILTINS |
+from logilab import astng |
+from logilab.astng import InferenceError, NotFoundError, YES, Instance |
-from pylint.interfaces import IAstroidChecker |
+from pylint.interfaces import IASTNGChecker |
from pylint.checkers import BaseChecker |
from pylint.checkers.utils import safe_infer, is_super, check_messages |
MSGS = { |
'E1101': ('%s %r has no %r member', |
- 'no-member', |
'Used when a variable is accessed for an unexistent member.'), |
'E1102': ('%s is not callable', |
- 'not-callable', |
'Used when an object being called has been inferred to a non \ |
callable object'), |
'E1103': ('%s %r has no %r member (but some types could not be inferred)', |
- 'maybe-no-member', |
'Used when a variable is accessed for an unexistent member, but \ |
- astroid was not able to interpret all possible types of this \ |
+ astng was not able to interpret all possible types of this \ |
variable.'), |
'E1111': ('Assigning to function call which doesn\'t return', |
- 'assignment-from-no-return', |
'Used when an assignment is done on a function call but the \ |
inferred function doesn\'t return anything.'), |
'W1111': ('Assigning to function call which only returns None', |
- 'assignment-from-none', |
'Used when an assignment is done on a function call but the \ |
inferred function returns nothing but None.'), |
- 'E1120': ('No value for argument %s in %s call', |
- 'no-value-for-parameter', |
+ 'E1120': ('No value passed for parameter %s in function call', |
'Used when a function call passes too few arguments.'), |
- 'E1121': ('Too many positional arguments for %s call', |
- 'too-many-function-args', |
+ 'E1121': ('Too many positional arguments for function call', |
'Used when a function call passes too many positional \ |
arguments.'), |
- 'E1122': ('Duplicate keyword argument %r in %s call', |
- 'duplicate-keyword-arg', |
+ 'E1122': ('Duplicate keyword argument %r in function call', |
'Used when a function call passes the same keyword argument \ |
- multiple times.', |
- {'maxversion': (2, 6)}), |
- 'E1123': ('Unexpected keyword argument %r in %s call', |
- 'unexpected-keyword-arg', |
+ multiple times.'), |
+ 'E1123': ('Passing unexpected keyword argument %r in function call', |
'Used when a function call passes a keyword argument that \ |
doesn\'t correspond to one of the function\'s parameter names.'), |
- 'E1124': ('Argument %r passed by position and keyword in %s call', |
- 'redundant-keyword-arg', |
+ 'E1124': ('Multiple values passed for parameter %r in function call', |
'Used when a function call would result in assigning multiple \ |
values to a function parameter, one value from a positional \ |
argument and one from a keyword argument.'), |
- 'E1125': ('Missing mandatory keyword argument %r in %s call', |
- 'missing-kwoa', |
- ('Used when a function call does not pass a mandatory' |
- ' keyword-only argument.'), |
- {'minversion': (3, 0)}), |
- 'E1126': ('Sequence index is not an int, slice, or instance with __index__', |
- 'invalid-sequence-index', |
- 'Used when a sequence type is indexed with an invalid type. ' |
- 'Valid types are ints, slices, and objects with an __index__ ' |
- 'method.'), |
- 'E1127': ('Slice index is not an int, None, or instance with __index__', |
- 'invalid-slice-index', |
- 'Used when a slice index is not an integer, None, or an object \ |
- with an __index__ method.'), |
} |
-# builtin sequence types in Python 2 and 3. |
-SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray', |
- 'xrange', 'range', 'bytes', 'memoryview']) |
- |
-def _determine_callable(callable_obj): |
- # Ordering is important, since BoundMethod is a subclass of UnboundMethod, |
- # and Function inherits Lambda. |
- if isinstance(callable_obj, astroid.BoundMethod): |
- # Bound methods have an extra implicit 'self' argument. |
- return callable_obj, 1, callable_obj.type |
- elif isinstance(callable_obj, astroid.UnboundMethod): |
- return callable_obj, 0, 'unbound method' |
- elif isinstance(callable_obj, astroid.Function): |
- return callable_obj, 0, callable_obj.type |
- elif isinstance(callable_obj, astroid.Lambda): |
- return callable_obj, 0, 'lambda' |
- elif isinstance(callable_obj, astroid.Class): |
- # Class instantiation, lookup __new__ instead. |
- # If we only find object.__new__, we can safely check __init__ |
- # instead. |
- try: |
- # Use the last definition of __new__. |
- new = callable_obj.local_attr('__new__')[-1] |
- except astroid.NotFoundError: |
- new = None |
- |
- if not new or new.parent.scope().name == 'object': |
- try: |
- # Use the last definition of __init__. |
- callable_obj = callable_obj.local_attr('__init__')[-1] |
- except astroid.NotFoundError: |
- # do nothing, covered by no-init. |
- raise ValueError |
- else: |
- callable_obj = new |
- |
- if not isinstance(callable_obj, astroid.Function): |
- raise ValueError |
- # both have an extra implicit 'cls'/'self' argument. |
- return callable_obj, 1, 'constructor' |
- else: |
- raise ValueError |
- |
class TypeChecker(BaseChecker): |
"""try to find bugs in the code using type inference |
""" |
- __implements__ = (IAstroidChecker,) |
+ __implements__ = (IASTNGChecker,) |
# configuration section name |
name = 'typecheck' |
@@ -146,38 +77,31 @@ class TypeChecker(BaseChecker): |
'help' : 'Tells whether missing members accessed in mixin \ |
class should be ignored. A mixin class is detected if its name ends with \ |
"mixin" (case insensitive).'} |
- ), |
- ('ignored-modules', |
- {'default': (), |
- 'type': 'csv', |
- 'metavar': '<module names>', |
- 'help': 'List of module names for which member attributes \ |
-should not be checked (useful for modules/projects where namespaces are \ |
-manipulated during runtime and thus existing member attributes cannot be \ |
-deduced by static analysis'}, |
- ), |
+ ), |
+ |
('ignored-classes', |
{'default' : ('SQLObject',), |
'type' : 'csv', |
'metavar' : '<members names>', |
'help' : 'List of classes names for which member attributes \ |
should not be checked (useful for classes with attributes dynamically set).'} |
- ), |
+ ), |
('zope', |
{'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>', |
'help' : 'When zope mode is activated, add a predefined set \ |
of Zope acquired attributes to generated-members.'} |
- ), |
+ ), |
('generated-members', |
- {'default' : ('REQUEST', 'acl_users', 'aq_parent'), |
+ {'default' : ( |
+ 'REQUEST', 'acl_users', 'aq_parent'), |
'type' : 'string', |
'metavar' : '<members names>', |
'help' : 'List of members which are set dynamically and \ |
missed by pylint inference system, and so shouldn\'t trigger E0201 when \ |
accessed. Python regular expressions are accepted.'} |
- ), |
- ) |
+ ), |
+ ) |
def open(self): |
# do this in open since config not fully initialized in __init__ |
@@ -186,13 +110,13 @@ accessed. Python regular expressions are accepted.'} |
self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) |
def visit_assattr(self, node): |
- if isinstance(node.ass_type(), astroid.AugAssign): |
+ if isinstance(node.ass_type(), astng.AugAssign): |
self.visit_getattr(node) |
def visit_delattr(self, node): |
self.visit_getattr(node) |
- @check_messages('no-member', 'maybe-no-member') |
+ @check_messages('E1101', 'E1103') |
def visit_getattr(self, node): |
"""check that the accessed attribute exists |
@@ -208,7 +132,6 @@ accessed. Python regular expressions are accepted.'} |
if isinstance(self.config.generated_members, str): |
gen = shlex.shlex(self.config.generated_members) |
gen.whitespace += ',' |
- gen.wordchars += '[]-+' |
self.config.generated_members = tuple(tok.strip('"') for tok in gen) |
for pattern in self.config.generated_members: |
# attribute is marked as generated, stop here |
@@ -228,7 +151,7 @@ accessed. Python regular expressions are accepted.'} |
inference_failure = True |
continue |
# skip None anyway |
- if isinstance(owner, astroid.Const) and owner.value is None: |
+ if isinstance(owner, astng.Const) and owner.value is None: |
continue |
# XXX "super" / metaclass call |
if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': |
@@ -240,19 +163,19 @@ accessed. Python regular expressions are accepted.'} |
continue |
try: |
if not [n for n in owner.getattr(node.attrname) |
- if not isinstance(n.statement(), astroid.AugAssign)]: |
+ if not isinstance(n.statement(), astng.AugAssign)]: |
missingattr.add((owner, name)) |
continue |
except AttributeError: |
# XXX method / function |
continue |
except NotFoundError: |
- if isinstance(owner, astroid.Function) and owner.decorators: |
- continue |
+ if isinstance(owner, astng.Function) and owner.decorators: |
+ continue |
if isinstance(owner, Instance) and owner.has_dynamic_getattr(): |
continue |
- # explicit skipping of module member access |
- if owner.root().name in self.config.ignored_modules: |
+ # explicit skipping of optparse'Values class |
+ if owner.name == 'Values' and owner.root().name == 'optparse': |
continue |
missingattr.add((owner, name)) |
continue |
@@ -271,122 +194,55 @@ accessed. Python regular expressions are accepted.'} |
continue |
done.add(actual) |
if inference_failure: |
- msgid = 'maybe-no-member' |
+ msgid = 'E1103' |
else: |
- msgid = 'no-member' |
+ msgid = 'E1101' |
self.add_message(msgid, node=node, |
args=(owner.display_type(), name, |
node.attrname)) |
- @check_messages('assignment-from-no-return', 'assignment-from-none') |
+ |
def visit_assign(self, node): |
"""check that if assigning to a function call, the function is |
possibly returning something valuable |
""" |
- if not isinstance(node.value, astroid.CallFunc): |
+ if not isinstance(node.value, astng.CallFunc): |
return |
function_node = safe_infer(node.value.func) |
# skip class, generator and incomplete function definition |
- if not (isinstance(function_node, astroid.Function) and |
+ if not (isinstance(function_node, astng.Function) and |
function_node.root().fully_defined()): |
return |
if function_node.is_generator() \ |
or function_node.is_abstract(pass_is_abstract=False): |
return |
- returns = list(function_node.nodes_of_class(astroid.Return, |
- skip_klass=astroid.Function)) |
+ returns = list(function_node.nodes_of_class(astng.Return, |
+ skip_klass=astng.Function)) |
if len(returns) == 0: |
- self.add_message('assignment-from-no-return', node=node) |
+ self.add_message('E1111', node=node) |
else: |
for rnode in returns: |
- if not (isinstance(rnode.value, astroid.Const) |
- and rnode.value.value is None |
- or rnode.value is None): |
+ if not (isinstance(rnode.value, astng.Const) |
+ and rnode.value.value is None): |
break |
else: |
- self.add_message('assignment-from-none', node=node) |
+ self.add_message('W1111', node=node) |
- def _check_uninferable_callfunc(self, node): |
- """ |
- Check that the given uninferable CallFunc node does not |
- call an actual function. |
- """ |
- if not isinstance(node.func, astroid.Getattr): |
- return |
- |
- # Look for properties. First, obtain |
- # the lhs of the Getattr node and search the attribute |
- # there. If that attribute is a property or a subclass of properties, |
- # then most likely it's not callable. |
- |
- # TODO: since astroid doesn't understand descriptors very well |
- # we will not handle them here, right now. |
- |
- expr = node.func.expr |
- klass = safe_infer(expr) |
- if (klass is None or klass is astroid.YES or |
- not isinstance(klass, astroid.Instance)): |
- return |
- |
- try: |
- attrs = klass._proxied.getattr(node.func.attrname) |
- except astroid.NotFoundError: |
- return |
- |
- stop_checking = False |
- for attr in attrs: |
- if attr is astroid.YES: |
- continue |
- if stop_checking: |
- break |
- if not isinstance(attr, astroid.Function): |
- continue |
- |
- # Decorated, see if it is decorated with a property |
- if not attr.decorators: |
- continue |
- for decorator in attr.decorators.nodes: |
- if not isinstance(decorator, astroid.Name): |
- continue |
- try: |
- for infered in decorator.infer(): |
- property_like = False |
- if isinstance(infered, astroid.Class): |
- if (infered.root().name == BUILTINS and |
- infered.name == 'property'): |
- property_like = True |
- else: |
- for ancestor in infered.ancestors(): |
- if (ancestor.name == 'property' and |
- ancestor.root().name == BUILTINS): |
- property_like = True |
- break |
- if property_like: |
- self.add_message('not-callable', node=node, |
- args=node.func.as_string()) |
- stop_checking = True |
- break |
- except InferenceError: |
- pass |
- if stop_checking: |
- break |
- |
- @check_messages(*(list(MSGS.keys()))) |
def visit_callfunc(self, node): |
"""check that called functions/methods are inferred to callable objects, |
and that the arguments passed to the function match the parameters in |
the inferred function's definition |
""" |
+ |
# Build the set of keyword arguments, checking for duplicate keywords, |
# and count the positional arguments. |
keyword_args = set() |
num_positional_args = 0 |
for arg in node.args: |
- if isinstance(arg, astroid.Keyword): |
+ if isinstance(arg, astng.Keyword): |
keyword = arg.arg |
if keyword in keyword_args: |
- self.add_message('duplicate-keyword-arg', node=node, |
- args=(keyword, 'function')) |
+ self.add_message('E1122', node=node, args=keyword) |
keyword_args.add(keyword) |
else: |
num_positional_args += 1 |
@@ -394,23 +250,31 @@ accessed. Python regular expressions are accepted.'} |
called = safe_infer(node.func) |
# only function, generator and object defining __call__ are allowed |
if called is not None and not called.callable(): |
- self.add_message('not-callable', node=node, |
- args=node.func.as_string()) |
- |
- self._check_uninferable_callfunc(node) |
- |
- try: |
- called, implicit_args, callable_name = _determine_callable(called) |
- except ValueError: |
- # Any error occurred during determining the function type, most of |
- # those errors are handled by different warnings. |
+ self.add_message('E1102', node=node, args=node.func.as_string()) |
+ |
+ # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must |
+ # come first in this 'if..else'. |
+ if isinstance(called, astng.BoundMethod): |
+ # Bound methods have an extra implicit 'self' argument. |
+ num_positional_args += 1 |
+ elif isinstance(called, astng.UnboundMethod): |
+ if called.decorators is not None: |
+ for d in called.decorators.nodes: |
+ if isinstance(d, astng.Name) and (d.name == 'classmethod'): |
+ # Class methods have an extra implicit 'cls' argument. |
+ num_positional_args += 1 |
+ break |
+ elif (isinstance(called, astng.Function) or |
+ isinstance(called, astng.Lambda)): |
+ pass |
+ else: |
return |
- num_positional_args += implicit_args |
+ |
if called.args.args is None: |
# Built-in functions have no argument information. |
return |
- if len(called.argnames()) != len(set(called.argnames())): |
+ if len( called.argnames() ) != len( set( called.argnames() ) ): |
# Duplicate parameter name (see E9801). We can't really make sense |
# of the function call in this case, so just return. |
return |
@@ -420,15 +284,15 @@ accessed. Python regular expressions are accepted.'} |
parameters = [] |
parameter_name_to_index = {} |
for i, arg in enumerate(called.args.args): |
- if isinstance(arg, astroid.Tuple): |
+ if isinstance(arg, astng.Tuple): |
name = None |
# Don't store any parameter names within the tuple, since those |
# are not assignable from keyword arguments. |
else: |
- if isinstance(arg, astroid.Keyword): |
+ if isinstance(arg, astng.Keyword): |
name = arg.arg |
else: |
- assert isinstance(arg, astroid.AssName) |
+ assert isinstance(arg, astng.AssName) |
# This occurs with: |
# def f( (a), (b) ): pass |
name = arg.name |
@@ -439,15 +303,6 @@ accessed. Python regular expressions are accepted.'} |
defval = None |
parameters.append([(name, defval), False]) |
- kwparams = {} |
- for i, arg in enumerate(called.args.kwonlyargs): |
- if isinstance(arg, astroid.Keyword): |
- name = arg.arg |
- else: |
- assert isinstance(arg, astroid.AssName) |
- name = arg.name |
- kwparams[name] = [called.args.kw_defaults[i], False] |
- |
# Match the supplied arguments against the function parameters. |
# 1. Match the positional arguments. |
@@ -460,8 +315,7 @@ accessed. Python regular expressions are accepted.'} |
break |
else: |
# Too many positional arguments. |
- self.add_message('too-many-function-args', |
- node=node, args=(callable_name,)) |
+ self.add_message('E1121', node=node) |
break |
# 2. Match the keyword arguments. |
@@ -470,24 +324,15 @@ accessed. Python regular expressions are accepted.'} |
i = parameter_name_to_index[keyword] |
if parameters[i][1]: |
# Duplicate definition of function parameter. |
- self.add_message('redundant-keyword-arg', |
- node=node, args=(keyword, callable_name)) |
+ self.add_message('E1124', node=node, args=keyword) |
else: |
parameters[i][1] = True |
- elif keyword in kwparams: |
- if kwparams[keyword][1]: # XXX is that even possible? |
- # Duplicate definition of function parameter. |
- self.add_message('redundant-keyword-arg', node=node, |
- args=(keyword, callable_name)) |
- else: |
- kwparams[keyword][1] = True |
elif called.args.kwarg is not None: |
# The keyword argument gets assigned to the **kwargs parameter. |
pass |
else: |
# Unexpected keyword argument. |
- self.add_message('unexpected-keyword-arg', node=node, |
- args=(keyword, callable_name)) |
+ self.add_message('E1123', node=node, args=keyword) |
# 3. Match the *args, if any. Note that Python actually processes |
# *args _before_ any keyword arguments, but we wait until after |
@@ -521,133 +366,10 @@ accessed. Python regular expressions are accepted.'} |
for [(name, defval), assigned] in parameters: |
if (defval is None) and not assigned: |
if name is None: |
- display_name = '<tuple>' |
+ display = '<tuple>' |
else: |
display_name = repr(name) |
- self.add_message('no-value-for-parameter', node=node, |
- args=(display_name, callable_name)) |
- |
- for name in kwparams: |
- defval, assigned = kwparams[name] |
- if defval is None and not assigned: |
- self.add_message('missing-kwoa', node=node, |
- args=(name, callable_name)) |
- |
- @check_messages('invalid-sequence-index') |
- def visit_extslice(self, node): |
- # Check extended slice objects as if they were used as a sequence |
- # index to check if the object being sliced can support them |
- return self.visit_index(node) |
- |
- @check_messages('invalid-sequence-index') |
- def visit_index(self, node): |
- if not node.parent or not hasattr(node.parent, "value"): |
- return |
- |
- # Look for index operations where the parent is a sequence type. |
- # If the types can be determined, only allow indices to be int, |
- # slice or instances with __index__. |
- |
- parent_type = safe_infer(node.parent.value) |
- |
- if not isinstance(parent_type, (astroid.Class, astroid.Instance)): |
- return |
- |
- # Determine what method on the parent this index will use |
- # The parent of this node will be a Subscript, and the parent of that |
- # node determines if the Subscript is a get, set, or delete operation. |
- operation = node.parent.parent |
- if isinstance(operation, astroid.Assign): |
- methodname = '__setitem__' |
- elif isinstance(operation, astroid.Delete): |
- methodname = '__delitem__' |
- else: |
- methodname = '__getitem__' |
- |
- # Check if this instance's __getitem__, __setitem__, or __delitem__, as |
- # appropriate to the statement, is implemented in a builtin sequence |
- # type. This way we catch subclasses of sequence types but skip classes |
- # that override __getitem__ and which may allow non-integer indices. |
- try: |
- methods = parent_type.getattr(methodname) |
- if methods is astroid.YES: |
- return |
- itemmethod = methods[0] |
- except (astroid.NotFoundError, IndexError): |
- return |
- |
- if not isinstance(itemmethod, astroid.Function): |
- return |
- |
- if itemmethod.root().name != BUILTINS: |
- return |
- |
- if not itemmethod.parent: |
- return |
- |
- if itemmethod.parent.name not in SEQUENCE_TYPES: |
- return |
- |
- # For ExtSlice objects coming from visit_extslice, no further |
- # inference is necessary, since if we got this far the ExtSlice |
- # is an error. |
- if isinstance(node, astroid.ExtSlice): |
- index_type = node |
- else: |
- index_type = safe_infer(node) |
- |
- if index_type is None or index_type is astroid.YES: |
- return |
- |
- # Constants must be of type int |
- if isinstance(index_type, astroid.Const): |
- if isinstance(index_type.value, int): |
- return |
- # Instance values must be int, slice, or have an __index__ method |
- elif isinstance(index_type, astroid.Instance): |
- if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): |
- return |
- |
- try: |
- index_type.getattr('__index__') |
- return |
- except astroid.NotFoundError: |
- pass |
- |
- # Anything else is an error |
- self.add_message('invalid-sequence-index', node=node) |
- |
- @check_messages('invalid-slice-index') |
- def visit_slice(self, node): |
- # Check the type of each part of the slice |
- for index in (node.lower, node.upper, node.step): |
- if index is None: |
- continue |
- |
- index_type = safe_infer(index) |
- |
- if index_type is None or index_type is astroid.YES: |
- continue |
- |
- # Constants must of type int or None |
- if isinstance(index_type, astroid.Const): |
- if isinstance(index_type.value, (int, type(None))): |
- continue |
- # Instance values must be of type int, None or an object |
- # with __index__ |
- elif isinstance(index_type, astroid.Instance): |
- if index_type.pytype() in (BUILTINS + '.int', |
- BUILTINS + '.NoneType'): |
- continue |
- |
- try: |
- index_type.getattr('__index__') |
- return |
- except astroid.NotFoundError: |
- pass |
- |
- # Anything else is an error |
- self.add_message('invalid-slice-index', node=node) |
+ self.add_message('E1120', node=node, args=display_name) |
def register(linter): |
"""required method to auto register this checker """ |