gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / checkers / typecheck.py @ 745
History | View | Annotate | Download (39.8 KB)
1 |
# Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE).
|
---|---|
2 |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
#
|
4 |
# This program is free software; you can redistribute it and/or modify it under
|
5 |
# the terms of the GNU General Public License as published by the Free Software
|
6 |
# Foundation; either version 2 of the License, or (at your option) any later
|
7 |
# version.
|
8 |
#
|
9 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
12 |
#
|
13 |
# You should have received a copy of the GNU General Public License along with
|
14 |
# this program; if not, write to the Free Software Foundation, Inc.,
|
15 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
16 |
"""try to find more bugs in the code using astroid inference capabilities
|
17 |
"""
|
18 |
|
19 |
import collections |
20 |
import fnmatch |
21 |
import re |
22 |
import shlex |
23 |
import sys |
24 |
|
25 |
import six |
26 |
|
27 |
import astroid |
28 |
import astroid.context |
29 |
import astroid.arguments |
30 |
from astroid import exceptions |
31 |
from astroid import objects |
32 |
from astroid import bases |
33 |
|
34 |
from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
35 |
from pylint.checkers import BaseChecker |
36 |
from pylint.checkers.utils import ( |
37 |
is_super, check_messages, decorated_with_property, |
38 |
decorated_with, node_ignores_exception, |
39 |
is_iterable, is_mapping, supports_membership_test, |
40 |
is_comprehension, is_inside_abstract_class, |
41 |
supports_subscript, |
42 |
safe_infer, |
43 |
has_known_bases) |
44 |
from pylint import utils |
45 |
|
46 |
|
47 |
_ZOPE_DEPRECATED = ( |
48 |
"This option is deprecated. Use generated-members instead."
|
49 |
) |
50 |
BUILTINS = six.moves.builtins.__name__ |
51 |
STR_FORMAT = "%s.str.format" % BUILTINS
|
52 |
|
53 |
|
54 |
def _unflatten(iterable): |
55 |
for index, elem in enumerate(iterable): |
56 |
if (isinstance(elem, collections.Sequence) and |
57 |
not isinstance(elem, six.string_types)): |
58 |
for elem in _unflatten(elem): |
59 |
yield elem
|
60 |
elif elem and not index: |
61 |
# We're interested only in the first element.
|
62 |
yield elem
|
63 |
|
64 |
|
65 |
def _is_owner_ignored(owner, name, ignored_classes, ignored_modules): |
66 |
"""Check if the given owner should be ignored
|
67 |
|
68 |
This will verify if the owner's module is in *ignored_modules*
|
69 |
or the owner's module fully qualified name is in *ignored_modules*
|
70 |
or if the *ignored_modules* contains a pattern which catches
|
71 |
the fully qualified name of the module.
|
72 |
|
73 |
Also, similar checks are done for the owner itself, if its name
|
74 |
matches any name from the *ignored_classes* or if its qualified
|
75 |
name can be found in *ignored_classes*.
|
76 |
"""
|
77 |
ignored_modules = set(ignored_modules)
|
78 |
module_name = owner.root().name |
79 |
module_qname = owner.root().qname() |
80 |
if any(module_name in ignored_modules or |
81 |
module_qname in ignored_modules or |
82 |
fnmatch.fnmatch(module_qname, ignore) for ignore in ignored_modules): |
83 |
return True |
84 |
|
85 |
ignored_classes = set(ignored_classes)
|
86 |
if hasattr(owner, 'qname'): |
87 |
qname = owner.qname() |
88 |
else:
|
89 |
qname = ''
|
90 |
return any(name == ignore or qname == ignore for ignore in ignored_classes) |
91 |
|
92 |
|
93 |
MSGS = { |
94 |
'E1101': ('%s %r has no %r member', |
95 |
'no-member',
|
96 |
'Used when a variable is accessed for an unexistent member.',
|
97 |
{'old_names': [('E1103', 'maybe-no-member')]}), |
98 |
'E1102': ('%s is not callable', |
99 |
'not-callable',
|
100 |
'Used when an object being called has been inferred to a non \
|
101 |
callable object'),
|
102 |
'E1111': ('Assigning to function call which doesn\'t return', |
103 |
'assignment-from-no-return',
|
104 |
'Used when an assignment is done on a function call but the \
|
105 |
inferred function doesn\'t return anything.'),
|
106 |
'E1120': ('No value for argument %s in %s call', |
107 |
'no-value-for-parameter',
|
108 |
'Used when a function call passes too few arguments.'),
|
109 |
'E1121': ('Too many positional arguments for %s call', |
110 |
'too-many-function-args',
|
111 |
'Used when a function call passes too many positional \
|
112 |
arguments.'),
|
113 |
'E1123': ('Unexpected keyword argument %r in %s call', |
114 |
'unexpected-keyword-arg',
|
115 |
'Used when a function call passes a keyword argument that \
|
116 |
doesn\'t correspond to one of the function\'s parameter names.'),
|
117 |
'E1124': ('Argument %r passed by position and keyword in %s call', |
118 |
'redundant-keyword-arg',
|
119 |
'Used when a function call would result in assigning multiple \
|
120 |
values to a function parameter, one value from a positional \
|
121 |
argument and one from a keyword argument.'),
|
122 |
'E1125': ('Missing mandatory keyword argument %r in %s call', |
123 |
'missing-kwoa',
|
124 |
('Used when a function call does not pass a mandatory'
|
125 |
' keyword-only argument.'),
|
126 |
{'minversion': (3, 0)}), |
127 |
'E1126': ('Sequence index is not an int, slice, or instance with __index__', |
128 |
'invalid-sequence-index',
|
129 |
'Used when a sequence type is indexed with an invalid type. '
|
130 |
'Valid types are ints, slices, and objects with an __index__ '
|
131 |
'method.'),
|
132 |
'E1127': ('Slice index is not an int, None, or instance with __index__', |
133 |
'invalid-slice-index',
|
134 |
'Used when a slice index is not an integer, None, or an object \
|
135 |
with an __index__ method.'),
|
136 |
'E1128': ('Assigning to function call which only returns None', |
137 |
'assignment-from-none',
|
138 |
'Used when an assignment is done on a function call but the '
|
139 |
'inferred function returns nothing but None.',
|
140 |
{'old_names': [('W1111', 'assignment-from-none')]}), |
141 |
'E1129': ("Context manager '%s' doesn't implement __enter__ and __exit__.", |
142 |
'not-context-manager',
|
143 |
'Used when an instance in a with statement doesn\'t implement '
|
144 |
'the context manager protocol(__enter__/__exit__).'),
|
145 |
'E1130': ('%s', |
146 |
'invalid-unary-operand-type',
|
147 |
'Emitted when an unary operand is used on an object which does not '
|
148 |
'support this type of operation'),
|
149 |
'E1131': ('%s', |
150 |
'unsupported-binary-operation',
|
151 |
'Emitted when a binary arithmetic operation between two '
|
152 |
'operands is not supported.'),
|
153 |
'E1132': ('Got multiple values for keyword argument %r in function call', |
154 |
'repeated-keyword',
|
155 |
'Emitted when a function call got multiple values for a keyword.'),
|
156 |
'E1135': ("Value '%s' doesn't support membership test", |
157 |
'unsupported-membership-test',
|
158 |
'Emitted when an instance in membership test expression doesn\'t'
|
159 |
'implement membership protocol (__contains__/__iter__/__getitem__)'),
|
160 |
'E1136': ("Value '%s' is unsubscriptable", |
161 |
'unsubscriptable-object',
|
162 |
"Emitted when a subscripted value doesn't support subscription"
|
163 |
"(i.e. doesn't define __getitem__ method)"),
|
164 |
} |
165 |
|
166 |
# builtin sequence types in Python 2 and 3.
|
167 |
SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray', |
168 |
'xrange', 'range', 'bytes', 'memoryview']) |
169 |
|
170 |
|
171 |
def _emit_no_member(node, owner, owner_name, ignored_mixins): |
172 |
"""Try to see if no-member should be emitted for the given owner.
|
173 |
|
174 |
The following cases are ignored:
|
175 |
|
176 |
* the owner is a function and it has decorators.
|
177 |
* the owner is an instance and it has __getattr__, __getattribute__ implemented
|
178 |
* the module is explicitly ignored from no-member checks
|
179 |
* the owner is a class and the name can be found in its metaclass.
|
180 |
* The access node is protected by an except handler, which handles
|
181 |
AttributeError, Exception or bare except.
|
182 |
"""
|
183 |
if node_ignores_exception(node, AttributeError): |
184 |
return False |
185 |
# skip None anyway
|
186 |
if isinstance(owner, astroid.Const) and owner.value is None: |
187 |
return False |
188 |
if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': |
189 |
return False |
190 |
if ignored_mixins and owner_name[-5:].lower() == 'mixin': |
191 |
return False |
192 |
if isinstance(owner, astroid.FunctionDef) and owner.decorators: |
193 |
return False |
194 |
if isinstance(owner, astroid.Instance): |
195 |
if owner.has_dynamic_getattr() or not has_known_bases(owner): |
196 |
return False |
197 |
if isinstance(owner, objects.Super): |
198 |
# Verify if we are dealing with an invalid Super object.
|
199 |
# If it is invalid, then there's no point in checking that
|
200 |
# it has the required attribute. Also, don't fail if the
|
201 |
# MRO is invalid.
|
202 |
try:
|
203 |
owner.super_mro() |
204 |
except (exceptions.MroError, exceptions.SuperError):
|
205 |
return False |
206 |
if not all(map(has_known_bases, owner.type.mro())): |
207 |
return False |
208 |
return True |
209 |
|
210 |
|
211 |
def _determine_callable(callable_obj): |
212 |
# Ordering is important, since BoundMethod is a subclass of UnboundMethod,
|
213 |
# and Function inherits Lambda.
|
214 |
if isinstance(callable_obj, astroid.BoundMethod): |
215 |
# Bound methods have an extra implicit 'self' argument.
|
216 |
return callable_obj, 1, callable_obj.type |
217 |
elif isinstance(callable_obj, astroid.UnboundMethod): |
218 |
return callable_obj, 0, 'unbound method' |
219 |
elif isinstance(callable_obj, astroid.FunctionDef): |
220 |
return callable_obj, 0, callable_obj.type |
221 |
elif isinstance(callable_obj, astroid.Lambda): |
222 |
return callable_obj, 0, 'lambda' |
223 |
elif isinstance(callable_obj, astroid.ClassDef): |
224 |
# Class instantiation, lookup __new__ instead.
|
225 |
# If we only find object.__new__, we can safely check __init__
|
226 |
# instead. If __new__ belongs to builtins, then we look
|
227 |
# again for __init__ in the locals, since we won't have
|
228 |
# argument information for the builtin __new__ function.
|
229 |
try:
|
230 |
# Use the last definition of __new__.
|
231 |
new = callable_obj.local_attr('__new__')[-1] |
232 |
except exceptions.NotFoundError:
|
233 |
new = None
|
234 |
|
235 |
from_object = new and new.parent.scope().name == 'object' |
236 |
from_builtins = new and new.root().name in sys.builtin_module_names |
237 |
|
238 |
if not new or from_object or from_builtins: |
239 |
try:
|
240 |
# Use the last definition of __init__.
|
241 |
callable_obj = callable_obj.local_attr('__init__')[-1] |
242 |
except exceptions.NotFoundError:
|
243 |
# do nothing, covered by no-init.
|
244 |
raise ValueError |
245 |
else:
|
246 |
callable_obj = new |
247 |
|
248 |
if not isinstance(callable_obj, astroid.FunctionDef): |
249 |
raise ValueError |
250 |
# both have an extra implicit 'cls'/'self' argument.
|
251 |
return callable_obj, 1, 'constructor' |
252 |
else:
|
253 |
raise ValueError |
254 |
|
255 |
class TypeChecker(BaseChecker): |
256 |
"""try to find bugs in the code using type inference
|
257 |
"""
|
258 |
|
259 |
__implements__ = (IAstroidChecker,) |
260 |
|
261 |
# configuration section name
|
262 |
name = 'typecheck'
|
263 |
# messages
|
264 |
msgs = MSGS |
265 |
priority = -1
|
266 |
# configuration options
|
267 |
options = (('ignore-mixin-members',
|
268 |
{'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>', |
269 |
'help' : 'Tells whether missing members accessed in mixin \ |
270 |
class should be ignored. A mixin class is detected if its name ends with \
|
271 |
"mixin" (case insensitive).'}
|
272 |
), |
273 |
('ignored-modules',
|
274 |
{'default': (),
|
275 |
'type': 'csv', |
276 |
'metavar': '<module names>', |
277 |
'help': 'List of module names for which member attributes ' |
278 |
'should not be checked (useful for modules/projects '
|
279 |
'where namespaces are manipulated during runtime and '
|
280 |
'thus existing member attributes cannot be '
|
281 |
'deduced by static analysis. It supports qualified '
|
282 |
'module names, as well as Unix pattern matching.'}
|
283 |
), |
284 |
('ignored-classes',
|
285 |
{'default' : (),
|
286 |
'type' : 'csv', |
287 |
'metavar' : '<members names>', |
288 |
'help' : 'List of classes names for which member attributes ' |
289 |
'should not be checked (useful for classes with '
|
290 |
'attributes dynamically set). This supports '
|
291 |
'can work with qualified names.'}
|
292 |
), |
293 |
|
294 |
('zope', utils.deprecated_option(opt_type='yn', |
295 |
help_msg=_ZOPE_DEPRECATED)), |
296 |
|
297 |
('generated-members',
|
298 |
{'default' : (),
|
299 |
'type' : 'string', |
300 |
'metavar' : '<members names>', |
301 |
'help' : 'List of members which are set dynamically and \ |
302 |
missed by pylint inference system, and so shouldn\'t trigger E1101 when \
|
303 |
accessed. Python regular expressions are accepted.'}
|
304 |
), |
305 |
) |
306 |
|
307 |
def open(self): |
308 |
# do this in open since config not fully initialized in __init__
|
309 |
# generated_members may contain regular expressions
|
310 |
# (surrounded by quote `"` and followed by a comma `,`)
|
311 |
# REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' =>
|
312 |
# ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}')
|
313 |
if isinstance(self.config.generated_members, str): |
314 |
gen = shlex.shlex(self.config.generated_members)
|
315 |
gen.whitespace += ','
|
316 |
gen.wordchars += '[]-+'
|
317 |
self.config.generated_members = tuple(tok.strip('"') for tok in gen) |
318 |
|
319 |
def visit_assignattr(self, node): |
320 |
if isinstance(node.assign_type(), astroid.AugAssign): |
321 |
self.visit_attribute(node)
|
322 |
|
323 |
def visit_delattr(self, node): |
324 |
self.visit_attribute(node)
|
325 |
|
326 |
@check_messages('no-member') |
327 |
def visit_attribute(self, node): |
328 |
"""check that the accessed attribute exists
|
329 |
|
330 |
to avoid too much false positives for now, we'll consider the code as
|
331 |
correct if a single of the inferred nodes has the accessed attribute.
|
332 |
|
333 |
function/method, super call and metaclasses are ignored
|
334 |
"""
|
335 |
for pattern in self.config.generated_members: |
336 |
# attribute is marked as generated, stop here
|
337 |
if re.match(pattern, node.attrname):
|
338 |
return
|
339 |
|
340 |
try:
|
341 |
infered = list(node.expr.infer())
|
342 |
except exceptions.InferenceError:
|
343 |
return
|
344 |
# list of (node, nodename) which are missing the attribute
|
345 |
missingattr = set()
|
346 |
inference_failure = False
|
347 |
for owner in infered: |
348 |
# skip yes object
|
349 |
if owner is astroid.YES: |
350 |
inference_failure = True
|
351 |
continue
|
352 |
|
353 |
name = getattr(owner, 'name', None) |
354 |
if _is_owner_ignored(owner, name, self.config.ignored_classes, |
355 |
self.config.ignored_modules):
|
356 |
continue
|
357 |
|
358 |
try:
|
359 |
if not [n for n in owner.getattr(node.attrname) |
360 |
if not isinstance(n.statement(), astroid.AugAssign)]: |
361 |
missingattr.add((owner, name)) |
362 |
continue
|
363 |
except AttributeError: |
364 |
# XXX method / function
|
365 |
continue
|
366 |
except exceptions.NotFoundError:
|
367 |
# This can't be moved before the actual .getattr call,
|
368 |
# because there can be more values inferred and we are
|
369 |
# stopping after the first one which has the attribute in question.
|
370 |
# The problem is that if the first one has the attribute,
|
371 |
# but we continue to the next values which doesn't have the
|
372 |
# attribute, then we'll have a false positive.
|
373 |
# So call this only after the call has been made.
|
374 |
if not _emit_no_member(node, owner, name, |
375 |
self.config.ignore_mixin_members):
|
376 |
continue
|
377 |
missingattr.add((owner, name)) |
378 |
continue
|
379 |
# stop on the first found
|
380 |
break
|
381 |
else:
|
382 |
# we have not found any node with the attributes, display the
|
383 |
# message for infered nodes
|
384 |
done = set()
|
385 |
for owner, name in missingattr: |
386 |
if isinstance(owner, astroid.Instance): |
387 |
actual = owner._proxied |
388 |
else:
|
389 |
actual = owner |
390 |
if actual in done: |
391 |
continue
|
392 |
done.add(actual) |
393 |
confidence = INFERENCE if not inference_failure else INFERENCE_FAILURE |
394 |
self.add_message('no-member', node=node, |
395 |
args=(owner.display_type(), name, |
396 |
node.attrname), |
397 |
confidence=confidence) |
398 |
|
399 |
@check_messages('assignment-from-no-return', 'assignment-from-none') |
400 |
def visit_assign(self, node): |
401 |
"""check that if assigning to a function call, the function is
|
402 |
possibly returning something valuable
|
403 |
"""
|
404 |
if not isinstance(node.value, astroid.Call): |
405 |
return
|
406 |
function_node = safe_infer(node.value.func) |
407 |
# skip class, generator and incomplete function definition
|
408 |
if not (isinstance(function_node, astroid.FunctionDef) and |
409 |
function_node.root().fully_defined()): |
410 |
return
|
411 |
if function_node.is_generator() \
|
412 |
or function_node.is_abstract(pass_is_abstract=False): |
413 |
return
|
414 |
returns = list(function_node.nodes_of_class(astroid.Return,
|
415 |
skip_klass=astroid.FunctionDef)) |
416 |
if len(returns) == 0: |
417 |
self.add_message('assignment-from-no-return', node=node) |
418 |
else:
|
419 |
for rnode in returns: |
420 |
if not (isinstance(rnode.value, astroid.Const) |
421 |
and rnode.value.value is None |
422 |
or rnode.value is None): |
423 |
break
|
424 |
else:
|
425 |
self.add_message('assignment-from-none', node=node) |
426 |
|
427 |
def _check_uninferable_callfunc(self, node): |
428 |
"""
|
429 |
Check that the given uninferable CallFunc node does not
|
430 |
call an actual function.
|
431 |
"""
|
432 |
if not isinstance(node.func, astroid.Attribute): |
433 |
return
|
434 |
|
435 |
# Look for properties. First, obtain
|
436 |
# the lhs of the Getattr node and search the attribute
|
437 |
# there. If that attribute is a property or a subclass of properties,
|
438 |
# then most likely it's not callable.
|
439 |
|
440 |
# TODO: since astroid doesn't understand descriptors very well
|
441 |
# we will not handle them here, right now.
|
442 |
|
443 |
expr = node.func.expr |
444 |
klass = safe_infer(expr) |
445 |
if (klass is None or klass is astroid.YES or |
446 |
not isinstance(klass, astroid.Instance)): |
447 |
return
|
448 |
|
449 |
try:
|
450 |
attrs = klass._proxied.getattr(node.func.attrname) |
451 |
except exceptions.NotFoundError:
|
452 |
return
|
453 |
|
454 |
for attr in attrs: |
455 |
if attr is astroid.YES: |
456 |
continue
|
457 |
if not isinstance(attr, astroid.FunctionDef): |
458 |
continue
|
459 |
|
460 |
# Decorated, see if it is decorated with a property.
|
461 |
# Also, check the returns and see if they are callable.
|
462 |
if decorated_with_property(attr):
|
463 |
if all(return_node.callable() |
464 |
for return_node in attr.infer_call_result(node)): |
465 |
continue
|
466 |
else:
|
467 |
self.add_message('not-callable', node=node, |
468 |
args=node.func.as_string()) |
469 |
break
|
470 |
|
471 |
@staticmethod
|
472 |
def _no_context_variadic(node): |
473 |
"""Verify if the given call node has variadic nodes without context
|
474 |
|
475 |
This is a workaround for handling cases of nested call functions
|
476 |
which don't have the specific call context at hand.
|
477 |
Variadic arguments (variable positional arguments and variable
|
478 |
keyword arguments) are inferred, inherently wrong, by astroid
|
479 |
as a Tuple, respectively a Dict with empty elements.
|
480 |
This can lead pylint to believe that a function call receives
|
481 |
too few arguments.
|
482 |
"""
|
483 |
for arg in node.args: |
484 |
if not isinstance(arg, astroid.Starred): |
485 |
continue
|
486 |
|
487 |
inferred = safe_infer(arg.value) |
488 |
if isinstance(inferred, astroid.Tuple): |
489 |
length = len(inferred.elts)
|
490 |
elif isinstance(inferred, astroid.Dict): |
491 |
length = len(inferred.items)
|
492 |
else:
|
493 |
return False |
494 |
if not length and isinstance(inferred.statement(), astroid.FunctionDef): |
495 |
return True |
496 |
return False |
497 |
|
498 |
@check_messages(*(list(MSGS.keys()))) |
499 |
def visit_call(self, node): |
500 |
"""check that called functions/methods are inferred to callable objects,
|
501 |
and that the arguments passed to the function match the parameters in
|
502 |
the inferred function's definition
|
503 |
"""
|
504 |
# Build the set of keyword arguments, checking for duplicate keywords,
|
505 |
# and count the positional arguments.
|
506 |
call_site = astroid.arguments.CallSite.from_call(node) |
507 |
num_positional_args = len(call_site.positional_arguments)
|
508 |
keyword_args = list(call_site.keyword_arguments.keys())
|
509 |
no_context_variadic = self._no_context_variadic(node)
|
510 |
|
511 |
called = safe_infer(node.func) |
512 |
# only function, generator and object defining __call__ are allowed
|
513 |
if called is not None and not called.callable(): |
514 |
self.add_message('not-callable', node=node, |
515 |
args=node.func.as_string()) |
516 |
|
517 |
self._check_uninferable_callfunc(node)
|
518 |
|
519 |
try:
|
520 |
called, implicit_args, callable_name = _determine_callable(called) |
521 |
except ValueError: |
522 |
# Any error occurred during determining the function type, most of
|
523 |
# those errors are handled by different warnings.
|
524 |
return
|
525 |
|
526 |
num_positional_args += implicit_args |
527 |
if called.args.args is None: |
528 |
# Built-in functions have no argument information.
|
529 |
return
|
530 |
|
531 |
if len(called.argnames()) != len(set(called.argnames())): |
532 |
# Duplicate parameter name (see duplicate-argument). We can't really
|
533 |
# make sense of the function call in this case, so just return.
|
534 |
return
|
535 |
|
536 |
# Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}`
|
537 |
for keyword in call_site.duplicated_keywords: |
538 |
self.add_message('repeated-keyword', |
539 |
node=node, args=(keyword, )) |
540 |
|
541 |
if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): |
542 |
# Can't make sense of this.
|
543 |
return
|
544 |
|
545 |
# Analyze the list of formal parameters.
|
546 |
num_mandatory_parameters = len(called.args.args) - len(called.args.defaults) |
547 |
parameters = [] |
548 |
parameter_name_to_index = {} |
549 |
for i, arg in enumerate(called.args.args): |
550 |
if isinstance(arg, astroid.Tuple): |
551 |
name = None
|
552 |
# Don't store any parameter names within the tuple, since those
|
553 |
# are not assignable from keyword arguments.
|
554 |
else:
|
555 |
assert isinstance(arg, astroid.AssignName) |
556 |
# This occurs with:
|
557 |
# def f( (a), (b) ): pass
|
558 |
name = arg.name |
559 |
parameter_name_to_index[name] = i |
560 |
if i >= num_mandatory_parameters:
|
561 |
defval = called.args.defaults[i - num_mandatory_parameters] |
562 |
else:
|
563 |
defval = None
|
564 |
parameters.append([(name, defval), False])
|
565 |
|
566 |
kwparams = {} |
567 |
for i, arg in enumerate(called.args.kwonlyargs): |
568 |
if isinstance(arg, astroid.Keyword): |
569 |
name = arg.arg |
570 |
else:
|
571 |
assert isinstance(arg, astroid.AssignName) |
572 |
name = arg.name |
573 |
kwparams[name] = [called.args.kw_defaults[i], False]
|
574 |
|
575 |
# Match the supplied arguments against the function parameters.
|
576 |
|
577 |
# 1. Match the positional arguments.
|
578 |
for i in range(num_positional_args): |
579 |
if i < len(parameters): |
580 |
parameters[i][1] = True |
581 |
elif called.args.vararg is not None: |
582 |
# The remaining positional arguments get assigned to the *args
|
583 |
# parameter.
|
584 |
break
|
585 |
else:
|
586 |
# Too many positional arguments.
|
587 |
self.add_message('too-many-function-args', |
588 |
node=node, args=(callable_name,)) |
589 |
break
|
590 |
|
591 |
# 2. Match the keyword arguments.
|
592 |
for keyword in keyword_args: |
593 |
if keyword in parameter_name_to_index: |
594 |
i = parameter_name_to_index[keyword] |
595 |
if parameters[i][1]: |
596 |
# Duplicate definition of function parameter.
|
597 |
|
598 |
# Might be too hardcoded, but this can actually
|
599 |
# happen when using str.format and `self` is passed
|
600 |
# by keyword argument, as in `.format(self=self)`.
|
601 |
# It's perfectly valid to so, so we're just skipping
|
602 |
# it if that's the case.
|
603 |
if not (keyword == 'self' and called.qname() == STR_FORMAT): |
604 |
self.add_message('redundant-keyword-arg', |
605 |
node=node, args=(keyword, callable_name)) |
606 |
else:
|
607 |
parameters[i][1] = True |
608 |
elif keyword in kwparams: |
609 |
if kwparams[keyword][1]: # XXX is that even possible? |
610 |
# Duplicate definition of function parameter.
|
611 |
self.add_message('redundant-keyword-arg', node=node, |
612 |
args=(keyword, callable_name)) |
613 |
else:
|
614 |
kwparams[keyword][1] = True |
615 |
elif called.args.kwarg is not None: |
616 |
# The keyword argument gets assigned to the **kwargs parameter.
|
617 |
pass
|
618 |
else:
|
619 |
# Unexpected keyword argument.
|
620 |
self.add_message('unexpected-keyword-arg', node=node, |
621 |
args=(keyword, callable_name)) |
622 |
|
623 |
# 3. Match the **kwargs, if any.
|
624 |
if node.kwargs:
|
625 |
for i, [(name, defval), assigned] in enumerate(parameters): |
626 |
# Assume that *kwargs provides values for all remaining
|
627 |
# unassigned named parameters.
|
628 |
if name is not None: |
629 |
parameters[i][1] = True |
630 |
else:
|
631 |
# **kwargs can't assign to tuples.
|
632 |
pass
|
633 |
|
634 |
# Check that any parameters without a default have been assigned
|
635 |
# values.
|
636 |
for [(name, defval), assigned] in parameters: |
637 |
if (defval is None) and not assigned: |
638 |
if name is None: |
639 |
display_name = '<tuple>'
|
640 |
else:
|
641 |
display_name = repr(name)
|
642 |
# TODO(cpopa): this should be removed after PyCQA/astroid/issues/177
|
643 |
if not no_context_variadic: |
644 |
self.add_message('no-value-for-parameter', node=node, |
645 |
args=(display_name, callable_name)) |
646 |
|
647 |
for name in kwparams: |
648 |
defval, assigned = kwparams[name] |
649 |
if defval is None and not assigned: |
650 |
self.add_message('missing-kwoa', node=node, |
651 |
args=(name, callable_name)) |
652 |
|
653 |
@check_messages('invalid-sequence-index') |
654 |
def visit_extslice(self, node): |
655 |
# Check extended slice objects as if they were used as a sequence
|
656 |
# index to check if the object being sliced can support them
|
657 |
return self.visit_index(node) |
658 |
|
659 |
@check_messages('invalid-sequence-index') |
660 |
def visit_index(self, node): |
661 |
if not node.parent or not hasattr(node.parent, "value"): |
662 |
return
|
663 |
# Look for index operations where the parent is a sequence type.
|
664 |
# If the types can be determined, only allow indices to be int,
|
665 |
# slice or instances with __index__.
|
666 |
parent_type = safe_infer(node.parent.value) |
667 |
if not isinstance(parent_type, (astroid.ClassDef, astroid.Instance)): |
668 |
return
|
669 |
|
670 |
# Determine what method on the parent this index will use
|
671 |
# The parent of this node will be a Subscript, and the parent of that
|
672 |
# node determines if the Subscript is a get, set, or delete operation.
|
673 |
operation = node.parent.parent |
674 |
if isinstance(operation, astroid.Assign): |
675 |
methodname = '__setitem__'
|
676 |
elif isinstance(operation, astroid.Delete): |
677 |
methodname = '__delitem__'
|
678 |
else:
|
679 |
methodname = '__getitem__'
|
680 |
|
681 |
# Check if this instance's __getitem__, __setitem__, or __delitem__, as
|
682 |
# appropriate to the statement, is implemented in a builtin sequence
|
683 |
# type. This way we catch subclasses of sequence types but skip classes
|
684 |
# that override __getitem__ and which may allow non-integer indices.
|
685 |
try:
|
686 |
methods = parent_type.getattr(methodname) |
687 |
if methods is astroid.YES: |
688 |
return
|
689 |
itemmethod = methods[0]
|
690 |
except (exceptions.NotFoundError, IndexError): |
691 |
return
|
692 |
if not isinstance(itemmethod, astroid.FunctionDef): |
693 |
return
|
694 |
if itemmethod.root().name != BUILTINS:
|
695 |
return
|
696 |
if not itemmethod.parent: |
697 |
return
|
698 |
if itemmethod.parent.name not in SEQUENCE_TYPES: |
699 |
return
|
700 |
|
701 |
# For ExtSlice objects coming from visit_extslice, no further
|
702 |
# inference is necessary, since if we got this far the ExtSlice
|
703 |
# is an error.
|
704 |
if isinstance(node, astroid.ExtSlice): |
705 |
index_type = node |
706 |
else:
|
707 |
index_type = safe_infer(node) |
708 |
if index_type is None or index_type is astroid.YES: |
709 |
return
|
710 |
# Constants must be of type int
|
711 |
if isinstance(index_type, astroid.Const): |
712 |
if isinstance(index_type.value, int): |
713 |
return
|
714 |
# Instance values must be int, slice, or have an __index__ method
|
715 |
elif isinstance(index_type, astroid.Instance): |
716 |
if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): |
717 |
return
|
718 |
try:
|
719 |
index_type.getattr('__index__')
|
720 |
return
|
721 |
except exceptions.NotFoundError:
|
722 |
pass
|
723 |
elif isinstance(index_type, astroid.Slice): |
724 |
# Delegate to visit_slice. A slice can be present
|
725 |
# here after inferring the index node, which could
|
726 |
# be a `slice(...)` call for instance.
|
727 |
return self.visit_slice(index_type) |
728 |
|
729 |
# Anything else is an error
|
730 |
self.add_message('invalid-sequence-index', node=node) |
731 |
|
732 |
@check_messages('invalid-slice-index') |
733 |
def visit_slice(self, node): |
734 |
# Check the type of each part of the slice
|
735 |
for index in (node.lower, node.upper, node.step): |
736 |
if index is None: |
737 |
continue
|
738 |
|
739 |
index_type = safe_infer(index) |
740 |
if index_type is None or index_type is astroid.YES: |
741 |
continue
|
742 |
|
743 |
# Constants must of type int or None
|
744 |
if isinstance(index_type, astroid.Const): |
745 |
if isinstance(index_type.value, (int, type(None))): |
746 |
continue
|
747 |
# Instance values must be of type int, None or an object
|
748 |
# with __index__
|
749 |
elif isinstance(index_type, astroid.Instance): |
750 |
if index_type.pytype() in (BUILTINS + '.int', |
751 |
BUILTINS + '.NoneType'):
|
752 |
continue
|
753 |
|
754 |
try:
|
755 |
index_type.getattr('__index__')
|
756 |
return
|
757 |
except exceptions.NotFoundError:
|
758 |
pass
|
759 |
|
760 |
# Anything else is an error
|
761 |
self.add_message('invalid-slice-index', node=node) |
762 |
|
763 |
@check_messages('not-context-manager') |
764 |
def visit_with(self, node): |
765 |
for ctx_mgr, _ in node.items: |
766 |
context = astroid.context.InferenceContext() |
767 |
infered = safe_infer(ctx_mgr, context=context) |
768 |
if infered is None or infered is astroid.YES: |
769 |
continue
|
770 |
|
771 |
if isinstance(infered, bases.Generator): |
772 |
# Check if we are dealing with a function decorated
|
773 |
# with contextlib.contextmanager.
|
774 |
if decorated_with(infered.parent, ['contextlib.contextmanager']): |
775 |
continue
|
776 |
# If the parent of the generator is not the context manager itself,
|
777 |
# that means that it could have been returned from another
|
778 |
# function which was the real context manager.
|
779 |
# The following approach is more of a hack rather than a real
|
780 |
# solution: walk all the inferred statements for the
|
781 |
# given *ctx_mgr* and if you find one function scope
|
782 |
# which is decorated, consider it to be the real
|
783 |
# manager and give up, otherwise emit not-context-manager.
|
784 |
# See the test file for not_context_manager for a couple
|
785 |
# of self explaining tests.
|
786 |
for path in six.moves.filter(None, _unflatten(context.path)): |
787 |
scope = path.scope() |
788 |
if not isinstance(scope, astroid.FunctionDef): |
789 |
continue
|
790 |
if decorated_with(scope, ['contextlib.contextmanager']): |
791 |
break
|
792 |
else:
|
793 |
self.add_message('not-context-manager', |
794 |
node=node, args=(infered.name, )) |
795 |
else:
|
796 |
try:
|
797 |
infered.getattr('__enter__')
|
798 |
infered.getattr('__exit__')
|
799 |
except exceptions.NotFoundError:
|
800 |
if isinstance(infered, astroid.Instance): |
801 |
# If we do not know the bases of this class,
|
802 |
# just skip it.
|
803 |
if not has_known_bases(infered): |
804 |
continue
|
805 |
# Just ignore mixin classes.
|
806 |
if self.config.ignore_mixin_members: |
807 |
if infered.name[-5:].lower() == 'mixin': |
808 |
continue
|
809 |
|
810 |
self.add_message('not-context-manager', |
811 |
node=node, args=(infered.name, )) |
812 |
|
813 |
# Disabled until we'll have a more capable astroid.
|
814 |
@check_messages('invalid-unary-operand-type') |
815 |
def _visit_unaryop(self, node): |
816 |
"""Detect TypeErrors for unary operands."""
|
817 |
|
818 |
for error in node.type_errors(): |
819 |
# Let the error customize its output.
|
820 |
self.add_message('invalid-unary-operand-type', |
821 |
args=str(error), node=node)
|
822 |
|
823 |
@check_messages('unsupported-binary-operation') |
824 |
def _visit_binop(self, node): |
825 |
"""Detect TypeErrors for binary arithmetic operands."""
|
826 |
self._check_binop_errors(node)
|
827 |
|
828 |
@check_messages('unsupported-binary-operation') |
829 |
def _visit_augassign(self, node): |
830 |
"""Detect TypeErrors for augmented binary arithmetic operands."""
|
831 |
self._check_binop_errors(node)
|
832 |
|
833 |
def _check_binop_errors(self, node): |
834 |
for error in node.type_errors(): |
835 |
# Let the error customize its output.
|
836 |
self.add_message('unsupported-binary-operation', |
837 |
args=str(error), node=node)
|
838 |
|
839 |
def _check_membership_test(self, node): |
840 |
if is_inside_abstract_class(node):
|
841 |
return
|
842 |
if is_comprehension(node):
|
843 |
return
|
844 |
infered = safe_infer(node) |
845 |
if infered is None or infered is astroid.YES: |
846 |
return
|
847 |
if not supports_membership_test(infered): |
848 |
self.add_message('unsupported-membership-test', |
849 |
args=node.as_string(), |
850 |
node=node) |
851 |
|
852 |
@check_messages('unsupported-membership-test') |
853 |
def visit_compare(self, node): |
854 |
if len(node.ops) != 1: |
855 |
return
|
856 |
operator, right = node.ops[0]
|
857 |
if operator in ['in', 'not in']: |
858 |
self._check_membership_test(right)
|
859 |
|
860 |
@check_messages('unsubscriptable-object') |
861 |
def visit_subscript(self, node): |
862 |
if isinstance(node.value, (astroid.ListComp, astroid.DictComp)): |
863 |
return
|
864 |
if isinstance(node.value, astroid.SetComp): |
865 |
self.add_message('unsubscriptable-object', |
866 |
args=node.value.as_string(), |
867 |
node=node.value) |
868 |
return
|
869 |
|
870 |
infered = safe_infer(node.value) |
871 |
if infered is None or infered is astroid.YES: |
872 |
return
|
873 |
|
874 |
if is_inside_abstract_class(node):
|
875 |
return
|
876 |
|
877 |
if not supports_subscript(infered): |
878 |
self.add_message('unsubscriptable-object', |
879 |
args=node.value.as_string(), |
880 |
node=node.value) |
881 |
|
882 |
|
883 |
|
884 |
class IterableChecker(BaseChecker): |
885 |
"""
|
886 |
Checks for non-iterables used in an iterable context.
|
887 |
Contexts include:
|
888 |
- for-statement
|
889 |
- starargs in function call
|
890 |
- `yield from`-statement
|
891 |
- list, dict and set comprehensions
|
892 |
- generator expressions
|
893 |
Also checks for non-mappings in function call kwargs.
|
894 |
"""
|
895 |
|
896 |
__implements__ = (IAstroidChecker,) |
897 |
name = 'iterable_check'
|
898 |
|
899 |
msgs = {'E1133': ('Non-iterable value %s is used in an iterating context', |
900 |
'not-an-iterable',
|
901 |
'Used when a non-iterable value is used in place where'
|
902 |
'iterable is expected'),
|
903 |
'E1134': ('Non-mapping value %s is used in a mapping context', |
904 |
'not-a-mapping',
|
905 |
'Used when a non-mapping value is used in place where'
|
906 |
'mapping is expected'),
|
907 |
} |
908 |
|
909 |
def _check_iterable(self, node): |
910 |
if is_inside_abstract_class(node):
|
911 |
return
|
912 |
if is_comprehension(node):
|
913 |
return
|
914 |
infered = safe_infer(node) |
915 |
if infered is None or infered is astroid.YES: |
916 |
return
|
917 |
if not is_iterable(infered): |
918 |
self.add_message('not-an-iterable', |
919 |
args=node.as_string(), |
920 |
node=node) |
921 |
|
922 |
def _check_mapping(self, node): |
923 |
if is_inside_abstract_class(node):
|
924 |
return
|
925 |
if isinstance(node, astroid.DictComp): |
926 |
return
|
927 |
infered = safe_infer(node) |
928 |
if infered is None or infered is astroid.YES: |
929 |
return
|
930 |
if not is_mapping(infered): |
931 |
self.add_message('not-a-mapping', |
932 |
args=node.as_string(), |
933 |
node=node) |
934 |
|
935 |
@check_messages('not-an-iterable') |
936 |
def visit_for(self, node): |
937 |
self._check_iterable(node.iter)
|
938 |
|
939 |
@check_messages('not-an-iterable') |
940 |
def visit_yieldfrom(self, node): |
941 |
self._check_iterable(node.value)
|
942 |
|
943 |
@check_messages('not-an-iterable', 'not-a-mapping') |
944 |
def visit_call(self, node): |
945 |
for stararg in node.starargs: |
946 |
self._check_iterable(stararg.value)
|
947 |
for kwarg in node.kwargs: |
948 |
self._check_mapping(kwarg.value)
|
949 |
|
950 |
@check_messages('not-an-iterable') |
951 |
def visit_listcomp(self, node): |
952 |
for gen in node.generators: |
953 |
self._check_iterable(gen.iter)
|
954 |
|
955 |
@check_messages('not-an-iterable') |
956 |
def visit_dictcomp(self, node): |
957 |
for gen in node.generators: |
958 |
self._check_iterable(gen.iter)
|
959 |
|
960 |
@check_messages('not-an-iterable') |
961 |
def visit_setcomp(self, node): |
962 |
for gen in node.generators: |
963 |
self._check_iterable(gen.iter)
|
964 |
|
965 |
@check_messages('not-an-iterable') |
966 |
def visit_generatorexp(self, node): |
967 |
for gen in node.generators: |
968 |
self._check_iterable(gen.iter)
|
969 |
|
970 |
|
971 |
def register(linter): |
972 |
"""required method to auto register this checker """
|
973 |
linter.register_checker(TypeChecker(linter)) |
974 |
linter.register_checker(IterableChecker(linter)) |