OLD | NEW |
(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)) |
OLD | NEW |