Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(967)

Side by Side Diff: third_party/pylint/pylint/checkers/typecheck.py

Issue 1920403002: [content/test/gpu] Run pylint check of gpu tests in unittest instead of PRESUBMIT (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update path to LICENSE.txt of logilab/README.chromium Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 re
20 import shlex
21
22 import astroid
23 from astroid import InferenceError, NotFoundError, YES, Instance
24 from astroid.bases import BUILTINS
25
26 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE
27 from pylint.checkers import BaseChecker
28 from pylint.checkers.utils import (
29 safe_infer, is_super,
30 check_messages, decorated_with_property)
31
32 MSGS = {
33 'E1101': ('%s %r has no %r member',
34 'no-member',
35 'Used when a variable is accessed for an unexistent member.',
36 {'old_names': [('E1103', 'maybe-no-member')]}),
37 'E1102': ('%s is not callable',
38 'not-callable',
39 'Used when an object being called has been inferred to a non \
40 callable object'),
41 'E1111': ('Assigning to function call which doesn\'t return',
42 'assignment-from-no-return',
43 'Used when an assignment is done on a function call but the \
44 inferred function doesn\'t return anything.'),
45 'W1111': ('Assigning to function call which only returns None',
46 'assignment-from-none',
47 'Used when an assignment is done on a function call but the \
48 inferred function returns nothing but None.'),
49
50 'E1120': ('No value for argument %s in %s call',
51 'no-value-for-parameter',
52 'Used when a function call passes too few arguments.'),
53 'E1121': ('Too many positional arguments for %s call',
54 'too-many-function-args',
55 'Used when a function call passes too many positional \
56 arguments.'),
57 'E1123': ('Unexpected keyword argument %r in %s call',
58 'unexpected-keyword-arg',
59 'Used when a function call passes a keyword argument that \
60 doesn\'t correspond to one of the function\'s parameter names.'),
61 'E1124': ('Argument %r passed by position and keyword in %s call',
62 'redundant-keyword-arg',
63 'Used when a function call would result in assigning multiple \
64 values to a function parameter, one value from a positional \
65 argument and one from a keyword argument.'),
66 'E1125': ('Missing mandatory keyword argument %r in %s call',
67 'missing-kwoa',
68 ('Used when a function call does not pass a mandatory'
69 ' keyword-only argument.'),
70 {'minversion': (3, 0)}),
71 'E1126': ('Sequence index is not an int, slice, or instance with __index__',
72 'invalid-sequence-index',
73 'Used when a sequence type is indexed with an invalid type. '
74 'Valid types are ints, slices, and objects with an __index__ '
75 'method.'),
76 'E1127': ('Slice index is not an int, None, or instance with __index__',
77 'invalid-slice-index',
78 'Used when a slice index is not an integer, None, or an object \
79 with an __index__ method.'),
80 }
81
82 # builtin sequence types in Python 2 and 3.
83 SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray',
84 'xrange', 'range', 'bytes', 'memoryview'])
85
86 def _determine_callable(callable_obj):
87 # Ordering is important, since BoundMethod is a subclass of UnboundMethod,
88 # and Function inherits Lambda.
89 if isinstance(callable_obj, astroid.BoundMethod):
90 # Bound methods have an extra implicit 'self' argument.
91 return callable_obj, 1, callable_obj.type
92 elif isinstance(callable_obj, astroid.UnboundMethod):
93 return callable_obj, 0, 'unbound method'
94 elif isinstance(callable_obj, astroid.Function):
95 return callable_obj, 0, callable_obj.type
96 elif isinstance(callable_obj, astroid.Lambda):
97 return callable_obj, 0, 'lambda'
98 elif isinstance(callable_obj, astroid.Class):
99 # Class instantiation, lookup __new__ instead.
100 # If we only find object.__new__, we can safely check __init__
101 # instead.
102 try:
103 # Use the last definition of __new__.
104 new = callable_obj.local_attr('__new__')[-1]
105 except astroid.NotFoundError:
106 new = None
107
108 if not new or new.parent.scope().name == 'object':
109 try:
110 # Use the last definition of __init__.
111 callable_obj = callable_obj.local_attr('__init__')[-1]
112 except astroid.NotFoundError:
113 # do nothing, covered by no-init.
114 raise ValueError
115 else:
116 callable_obj = new
117
118 if not isinstance(callable_obj, astroid.Function):
119 raise ValueError
120 # both have an extra implicit 'cls'/'self' argument.
121 return callable_obj, 1, 'constructor'
122 else:
123 raise ValueError
124
125 class TypeChecker(BaseChecker):
126 """try to find bugs in the code using type inference
127 """
128
129 __implements__ = (IAstroidChecker,)
130
131 # configuration section name
132 name = 'typecheck'
133 # messages
134 msgs = MSGS
135 priority = -1
136 # configuration options
137 options = (('ignore-mixin-members',
138 {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>',
139 'help' : 'Tells whether missing members accessed in mixin \
140 class should be ignored. A mixin class is detected if its name ends with \
141 "mixin" (case insensitive).'}
142 ),
143 ('ignored-modules',
144 {'default': (),
145 'type': 'csv',
146 'metavar': '<module names>',
147 'help': 'List of module names for which member attributes \
148 should not be checked (useful for modules/projects where namespaces are \
149 manipulated during runtime and thus existing member attributes cannot be \
150 deduced by static analysis'},
151 ),
152 ('ignored-classes',
153 {'default' : ('SQLObject',),
154 'type' : 'csv',
155 'metavar' : '<members names>',
156 'help' : 'List of classes names for which member attributes \
157 should not be checked (useful for classes with attributes dynamically set).'}
158 ),
159
160 ('zope',
161 {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>',
162 'help' : 'When zope mode is activated, add a predefined set \
163 of Zope acquired attributes to generated-members.'}
164 ),
165 ('generated-members',
166 {'default' : ('REQUEST', 'acl_users', 'aq_parent'),
167 'type' : 'string',
168 'metavar' : '<members names>',
169 'help' : 'List of members which are set dynamically and \
170 missed by pylint inference system, and so shouldn\'t trigger E0201 when \
171 accessed. Python regular expressions are accepted.'}
172 ),
173 )
174
175 def open(self):
176 # do this in open since config not fully initialized in __init__
177 self.generated_members = list(self.config.generated_members)
178 if self.config.zope:
179 self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent'))
180
181 def visit_assattr(self, node):
182 if isinstance(node.ass_type(), astroid.AugAssign):
183 self.visit_getattr(node)
184
185 def visit_delattr(self, node):
186 self.visit_getattr(node)
187
188 @check_messages('no-member')
189 def visit_getattr(self, node):
190 """check that the accessed attribute exists
191
192 to avoid to much false positives for now, we'll consider the code as
193 correct if a single of the inferred nodes has the accessed attribute.
194
195 function/method, super call and metaclasses are ignored
196 """
197 # generated_members may containt regular expressions
198 # (surrounded by quote `"` and followed by a comma `,`)
199 # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' =>
200 # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}')
201 if isinstance(self.config.generated_members, str):
202 gen = shlex.shlex(self.config.generated_members)
203 gen.whitespace += ','
204 gen.wordchars += '[]-+'
205 self.config.generated_members = tuple(tok.strip('"') for tok in gen)
206 for pattern in self.config.generated_members:
207 # attribute is marked as generated, stop here
208 if re.match(pattern, node.attrname):
209 return
210 try:
211 infered = list(node.expr.infer())
212 except InferenceError:
213 return
214 # list of (node, nodename) which are missing the attribute
215 missingattr = set()
216 ignoremim = self.config.ignore_mixin_members
217 inference_failure = False
218 for owner in infered:
219 # skip yes object
220 if owner is YES:
221 inference_failure = True
222 continue
223 # skip None anyway
224 if isinstance(owner, astroid.Const) and owner.value is None:
225 continue
226 # XXX "super" / metaclass call
227 if is_super(owner) or getattr(owner, 'type', None) == 'metaclass':
228 continue
229 name = getattr(owner, 'name', 'None')
230 if name in self.config.ignored_classes:
231 continue
232 if ignoremim and name[-5:].lower() == 'mixin':
233 continue
234 try:
235 if not [n for n in owner.getattr(node.attrname)
236 if not isinstance(n.statement(), astroid.AugAssign)]:
237 missingattr.add((owner, name))
238 continue
239 except AttributeError:
240 # XXX method / function
241 continue
242 except NotFoundError:
243 if isinstance(owner, astroid.Function) and owner.decorators:
244 continue
245 if isinstance(owner, Instance) and owner.has_dynamic_getattr():
246 continue
247 # explicit skipping of module member access
248 if owner.root().name in self.config.ignored_modules:
249 continue
250 if isinstance(owner, astroid.Class):
251 # Look up in the metaclass only if the owner is itself
252 # a class.
253 # TODO: getattr doesn't return by default members
254 # from the metaclass, because handling various cases
255 # of methods accessible from the metaclass itself
256 # and/or subclasses only is too complicated for little to
257 # no benefit.
258 metaclass = owner.metaclass()
259 try:
260 if metaclass and metaclass.getattr(node.attrname):
261 continue
262 except NotFoundError:
263 pass
264 missingattr.add((owner, name))
265 continue
266 # stop on the first found
267 break
268 else:
269 # we have not found any node with the attributes, display the
270 # message for infered nodes
271 done = set()
272 for owner, name in missingattr:
273 if isinstance(owner, Instance):
274 actual = owner._proxied
275 else:
276 actual = owner
277 if actual in done:
278 continue
279 done.add(actual)
280 confidence = INFERENCE if not inference_failure else INFERENCE_F AILURE
281 self.add_message('no-member', node=node,
282 args=(owner.display_type(), name,
283 node.attrname),
284 confidence=confidence)
285
286 @check_messages('assignment-from-no-return', 'assignment-from-none')
287 def visit_assign(self, node):
288 """check that if assigning to a function call, the function is
289 possibly returning something valuable
290 """
291 if not isinstance(node.value, astroid.CallFunc):
292 return
293 function_node = safe_infer(node.value.func)
294 # skip class, generator and incomplete function definition
295 if not (isinstance(function_node, astroid.Function) and
296 function_node.root().fully_defined()):
297 return
298 if function_node.is_generator() \
299 or function_node.is_abstract(pass_is_abstract=False):
300 return
301 returns = list(function_node.nodes_of_class(astroid.Return,
302 skip_klass=astroid.Function) )
303 if len(returns) == 0:
304 self.add_message('assignment-from-no-return', node=node)
305 else:
306 for rnode in returns:
307 if not (isinstance(rnode.value, astroid.Const)
308 and rnode.value.value is None
309 or rnode.value is None):
310 break
311 else:
312 self.add_message('assignment-from-none', node=node)
313
314 def _check_uninferable_callfunc(self, node):
315 """
316 Check that the given uninferable CallFunc node does not
317 call an actual function.
318 """
319 if not isinstance(node.func, astroid.Getattr):
320 return
321
322 # Look for properties. First, obtain
323 # the lhs of the Getattr node and search the attribute
324 # there. If that attribute is a property or a subclass of properties,
325 # then most likely it's not callable.
326
327 # TODO: since astroid doesn't understand descriptors very well
328 # we will not handle them here, right now.
329
330 expr = node.func.expr
331 klass = safe_infer(expr)
332 if (klass is None or klass is astroid.YES or
333 not isinstance(klass, astroid.Instance)):
334 return
335
336 try:
337 attrs = klass._proxied.getattr(node.func.attrname)
338 except astroid.NotFoundError:
339 return
340
341 for attr in attrs:
342 if attr is astroid.YES:
343 continue
344 if not isinstance(attr, astroid.Function):
345 continue
346
347 # Decorated, see if it is decorated with a property.
348 # Also, check the returns and see if they are callable.
349 if decorated_with_property(attr):
350 if all(return_node.callable()
351 for return_node in attr.infer_call_result(node)):
352 continue
353 else:
354 self.add_message('not-callable', node=node,
355 args=node.func.as_string())
356 break
357
358 @check_messages(*(list(MSGS.keys())))
359 def visit_callfunc(self, node):
360 """check that called functions/methods are inferred to callable objects,
361 and that the arguments passed to the function match the parameters in
362 the inferred function's definition
363 """
364 # Build the set of keyword arguments, checking for duplicate keywords,
365 # and count the positional arguments.
366 keyword_args = set()
367 num_positional_args = 0
368 for arg in node.args:
369 if isinstance(arg, astroid.Keyword):
370 keyword_args.add(arg.arg)
371 else:
372 num_positional_args += 1
373
374 called = safe_infer(node.func)
375 # only function, generator and object defining __call__ are allowed
376 if called is not None and not called.callable():
377 self.add_message('not-callable', node=node,
378 args=node.func.as_string())
379
380 self._check_uninferable_callfunc(node)
381
382 try:
383 called, implicit_args, callable_name = _determine_callable(called)
384 except ValueError:
385 # Any error occurred during determining the function type, most of
386 # those errors are handled by different warnings.
387 return
388 num_positional_args += implicit_args
389 if called.args.args is None:
390 # Built-in functions have no argument information.
391 return
392
393 if len(called.argnames()) != len(set(called.argnames())):
394 # Duplicate parameter name (see E9801). We can't really make sense
395 # of the function call in this case, so just return.
396 return
397
398 # Analyze the list of formal parameters.
399 num_mandatory_parameters = len(called.args.args) - len(called.args.defau lts)
400 parameters = []
401 parameter_name_to_index = {}
402 for i, arg in enumerate(called.args.args):
403 if isinstance(arg, astroid.Tuple):
404 name = None
405 # Don't store any parameter names within the tuple, since those
406 # are not assignable from keyword arguments.
407 else:
408 if isinstance(arg, astroid.Keyword):
409 name = arg.arg
410 else:
411 assert isinstance(arg, astroid.AssName)
412 # This occurs with:
413 # def f( (a), (b) ): pass
414 name = arg.name
415 parameter_name_to_index[name] = i
416 if i >= num_mandatory_parameters:
417 defval = called.args.defaults[i - num_mandatory_parameters]
418 else:
419 defval = None
420 parameters.append([(name, defval), False])
421
422 kwparams = {}
423 for i, arg in enumerate(called.args.kwonlyargs):
424 if isinstance(arg, astroid.Keyword):
425 name = arg.arg
426 else:
427 assert isinstance(arg, astroid.AssName)
428 name = arg.name
429 kwparams[name] = [called.args.kw_defaults[i], False]
430
431 # Match the supplied arguments against the function parameters.
432
433 # 1. Match the positional arguments.
434 for i in range(num_positional_args):
435 if i < len(parameters):
436 parameters[i][1] = True
437 elif called.args.vararg is not None:
438 # The remaining positional arguments get assigned to the *args
439 # parameter.
440 break
441 else:
442 # Too many positional arguments.
443 self.add_message('too-many-function-args',
444 node=node, args=(callable_name,))
445 break
446
447 # 2. Match the keyword arguments.
448 for keyword in keyword_args:
449 if keyword in parameter_name_to_index:
450 i = parameter_name_to_index[keyword]
451 if parameters[i][1]:
452 # Duplicate definition of function parameter.
453 self.add_message('redundant-keyword-arg',
454 node=node, args=(keyword, callable_name))
455 else:
456 parameters[i][1] = True
457 elif keyword in kwparams:
458 if kwparams[keyword][1]: # XXX is that even possible?
459 # Duplicate definition of function parameter.
460 self.add_message('redundant-keyword-arg', node=node,
461 args=(keyword, callable_name))
462 else:
463 kwparams[keyword][1] = True
464 elif called.args.kwarg is not None:
465 # The keyword argument gets assigned to the **kwargs parameter.
466 pass
467 else:
468 # Unexpected keyword argument.
469 self.add_message('unexpected-keyword-arg', node=node,
470 args=(keyword, callable_name))
471
472 # 3. Match the *args, if any. Note that Python actually processes
473 # *args _before_ any keyword arguments, but we wait until after
474 # looking at the keyword arguments so as to make a more conservative
475 # guess at how many values are in the *args sequence.
476 if node.starargs is not None:
477 for i in range(num_positional_args, len(parameters)):
478 [(name, defval), assigned] = parameters[i]
479 # Assume that *args provides just enough values for all
480 # non-default parameters after the last parameter assigned by
481 # the positional arguments but before the first parameter
482 # assigned by the keyword arguments. This is the best we can
483 # get without generating any false positives.
484 if (defval is not None) or assigned:
485 break
486 parameters[i][1] = True
487
488 # 4. Match the **kwargs, if any.
489 if node.kwargs is not None:
490 for i, [(name, defval), assigned] in enumerate(parameters):
491 # Assume that *kwargs provides values for all remaining
492 # unassigned named parameters.
493 if name is not None:
494 parameters[i][1] = True
495 else:
496 # **kwargs can't assign to tuples.
497 pass
498
499 # Check that any parameters without a default have been assigned
500 # values.
501 for [(name, defval), assigned] in parameters:
502 if (defval is None) and not assigned:
503 if name is None:
504 display_name = '<tuple>'
505 else:
506 display_name = repr(name)
507 self.add_message('no-value-for-parameter', node=node,
508 args=(display_name, callable_name))
509
510 for name in kwparams:
511 defval, assigned = kwparams[name]
512 if defval is None and not assigned:
513 self.add_message('missing-kwoa', node=node,
514 args=(name, callable_name))
515
516 @check_messages('invalid-sequence-index')
517 def visit_extslice(self, node):
518 # Check extended slice objects as if they were used as a sequence
519 # index to check if the object being sliced can support them
520 return self.visit_index(node)
521
522 @check_messages('invalid-sequence-index')
523 def visit_index(self, node):
524 if not node.parent or not hasattr(node.parent, "value"):
525 return
526
527 # Look for index operations where the parent is a sequence type.
528 # If the types can be determined, only allow indices to be int,
529 # slice or instances with __index__.
530
531 parent_type = safe_infer(node.parent.value)
532 if not isinstance(parent_type, (astroid.Class, astroid.Instance)):
533 return
534
535 # Determine what method on the parent this index will use
536 # The parent of this node will be a Subscript, and the parent of that
537 # node determines if the Subscript is a get, set, or delete operation.
538 operation = node.parent.parent
539 if isinstance(operation, astroid.Assign):
540 methodname = '__setitem__'
541 elif isinstance(operation, astroid.Delete):
542 methodname = '__delitem__'
543 else:
544 methodname = '__getitem__'
545
546 # Check if this instance's __getitem__, __setitem__, or __delitem__, as
547 # appropriate to the statement, is implemented in a builtin sequence
548 # type. This way we catch subclasses of sequence types but skip classes
549 # that override __getitem__ and which may allow non-integer indices.
550 try:
551 methods = parent_type.getattr(methodname)
552 if methods is astroid.YES:
553 return
554 itemmethod = methods[0]
555 except (astroid.NotFoundError, IndexError):
556 return
557
558 if not isinstance(itemmethod, astroid.Function):
559 return
560 if itemmethod.root().name != BUILTINS:
561 return
562 if not itemmethod.parent:
563 return
564 if itemmethod.parent.name not in SEQUENCE_TYPES:
565 return
566
567 # For ExtSlice objects coming from visit_extslice, no further
568 # inference is necessary, since if we got this far the ExtSlice
569 # is an error.
570 if isinstance(node, astroid.ExtSlice):
571 index_type = node
572 else:
573 index_type = safe_infer(node)
574 if index_type is None or index_type is astroid.YES:
575 return
576
577 # Constants must be of type int
578 if isinstance(index_type, astroid.Const):
579 if isinstance(index_type.value, int):
580 return
581 # Instance values must be int, slice, or have an __index__ method
582 elif isinstance(index_type, astroid.Instance):
583 if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'):
584 return
585 try:
586 index_type.getattr('__index__')
587 return
588 except astroid.NotFoundError:
589 pass
590
591 # Anything else is an error
592 self.add_message('invalid-sequence-index', node=node)
593
594 @check_messages('invalid-slice-index')
595 def visit_slice(self, node):
596 # Check the type of each part of the slice
597 for index in (node.lower, node.upper, node.step):
598 if index is None:
599 continue
600
601 index_type = safe_infer(index)
602 if index_type is None or index_type is astroid.YES:
603 continue
604
605 # Constants must of type int or None
606 if isinstance(index_type, astroid.Const):
607 if isinstance(index_type.value, (int, type(None))):
608 continue
609 # Instance values must be of type int, None or an object
610 # with __index__
611 elif isinstance(index_type, astroid.Instance):
612 if index_type.pytype() in (BUILTINS + '.int',
613 BUILTINS + '.NoneType'):
614 continue
615
616 try:
617 index_type.getattr('__index__')
618 return
619 except astroid.NotFoundError:
620 pass
621
622 # Anything else is an error
623 self.add_message('invalid-slice-index', node=node)
624
625 def register(linter):
626 """required method to auto register this checker """
627 linter.register_checker(TypeChecker(linter))
OLDNEW
« no previous file with comments | « third_party/pylint/pylint/checkers/strings.py ('k') | third_party/pylint/pylint/checkers/utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698