OLD | NEW |
(Empty) | |
| 1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
| 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 3 # |
| 4 # This file is part of astroid. |
| 5 # |
| 6 # astroid is free software: you can redistribute it and/or modify it |
| 7 # under the terms of the GNU Lesser General Public License as published by the |
| 8 # Free Software Foundation, either version 2.1 of the License, or (at your |
| 9 # option) any later version. |
| 10 # |
| 11 # astroid is distributed in the hope that it will be useful, but |
| 12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
| 14 # for more details. |
| 15 # |
| 16 # You should have received a copy of the GNU Lesser General Public License along |
| 17 # with astroid. If not, see <http://www.gnu.org/licenses/>. |
| 18 """this module contains a set of functions to handle inference on astroid trees |
| 19 """ |
| 20 |
| 21 __doctype__ = "restructuredtext en" |
| 22 |
| 23 from itertools import chain |
| 24 |
| 25 from astroid import nodes |
| 26 |
| 27 from astroid.manager import AstroidManager |
| 28 from astroid.exceptions import (AstroidError, InferenceError, NoDefault, |
| 29 NotFoundError, UnresolvableName) |
| 30 from astroid.bases import (YES, Instance, InferenceContext, |
| 31 _infer_stmts, path_wrapper, |
| 32 raise_if_nothing_infered) |
| 33 from astroid.protocols import ( |
| 34 _arguments_infer_argname, |
| 35 BIN_OP_METHOD, UNARY_OP_METHOD) |
| 36 |
| 37 MANAGER = AstroidManager() |
| 38 |
| 39 |
| 40 class CallContext(object): |
| 41 """when inferring a function call, this class is used to remember values |
| 42 given as argument |
| 43 """ |
| 44 def __init__(self, args, starargs, dstarargs): |
| 45 self.args = [] |
| 46 self.nargs = {} |
| 47 for arg in args: |
| 48 if isinstance(arg, nodes.Keyword): |
| 49 self.nargs[arg.arg] = arg.value |
| 50 else: |
| 51 self.args.append(arg) |
| 52 self.starargs = starargs |
| 53 self.dstarargs = dstarargs |
| 54 |
| 55 def infer_argument(self, funcnode, name, context): |
| 56 """infer a function argument value according to the call context""" |
| 57 # 1. search in named keywords |
| 58 try: |
| 59 return self.nargs[name].infer(context) |
| 60 except KeyError: |
| 61 # Function.args.args can be None in astroid (means that we don't hav
e |
| 62 # information on argnames) |
| 63 argindex = funcnode.args.find_argname(name)[0] |
| 64 if argindex is not None: |
| 65 # 2. first argument of instance/class method |
| 66 if argindex == 0 and funcnode.type in ('method', 'classmethod'): |
| 67 if context.boundnode is not None: |
| 68 boundnode = context.boundnode |
| 69 else: |
| 70 # XXX can do better ? |
| 71 boundnode = funcnode.parent.frame() |
| 72 if funcnode.type == 'method': |
| 73 if not isinstance(boundnode, Instance): |
| 74 boundnode = Instance(boundnode) |
| 75 return iter((boundnode,)) |
| 76 if funcnode.type == 'classmethod': |
| 77 return iter((boundnode,)) |
| 78 # if we have a method, extract one position |
| 79 # from the index, so we'll take in account |
| 80 # the extra parameter represented by `self` or `cls` |
| 81 if funcnode.type in ('method', 'classmethod'): |
| 82 argindex -= 1 |
| 83 # 2. search arg index |
| 84 try: |
| 85 return self.args[argindex].infer(context) |
| 86 except IndexError: |
| 87 pass |
| 88 # 3. search in *args (.starargs) |
| 89 if self.starargs is not None: |
| 90 its = [] |
| 91 for infered in self.starargs.infer(context): |
| 92 if infered is YES: |
| 93 its.append((YES,)) |
| 94 continue |
| 95 try: |
| 96 its.append(infered.getitem(argindex, context).infer(
context)) |
| 97 except (InferenceError, AttributeError): |
| 98 its.append((YES,)) |
| 99 except (IndexError, TypeError): |
| 100 continue |
| 101 if its: |
| 102 return chain(*its) |
| 103 # 4. XXX search in **kwargs (.dstarargs) |
| 104 if self.dstarargs is not None: |
| 105 its = [] |
| 106 for infered in self.dstarargs.infer(context): |
| 107 if infered is YES: |
| 108 its.append((YES,)) |
| 109 continue |
| 110 try: |
| 111 its.append(infered.getitem(name, context).infer(context)) |
| 112 except (InferenceError, AttributeError): |
| 113 its.append((YES,)) |
| 114 except (IndexError, TypeError): |
| 115 continue |
| 116 if its: |
| 117 return chain(*its) |
| 118 # 5. */** argument, (Tuple or Dict) |
| 119 if name == funcnode.args.vararg: |
| 120 return iter((nodes.const_factory(()))) |
| 121 if name == funcnode.args.kwarg: |
| 122 return iter((nodes.const_factory({}))) |
| 123 # 6. return default value if any |
| 124 try: |
| 125 return funcnode.args.default_value(name).infer(context) |
| 126 except NoDefault: |
| 127 raise InferenceError(name) |
| 128 |
| 129 |
| 130 # .infer method ############################################################### |
| 131 |
| 132 |
| 133 def infer_end(self, context=None): |
| 134 """inference's end for node such as Module, Class, Function, Const... |
| 135 """ |
| 136 yield self |
| 137 nodes.Module._infer = infer_end |
| 138 nodes.Class._infer = infer_end |
| 139 nodes.Function._infer = infer_end |
| 140 nodes.Lambda._infer = infer_end |
| 141 nodes.Const._infer = infer_end |
| 142 nodes.List._infer = infer_end |
| 143 nodes.Tuple._infer = infer_end |
| 144 nodes.Dict._infer = infer_end |
| 145 nodes.Set._infer = infer_end |
| 146 |
| 147 def _higher_function_scope(node): |
| 148 """ Search for the first function which encloses the given |
| 149 scope. This can be used for looking up in that function's |
| 150 scope, in case looking up in a lower scope for a particular |
| 151 name fails. |
| 152 |
| 153 :param node: A scope node. |
| 154 :returns: |
| 155 ``None``, if no parent function scope was found, |
| 156 otherwise an instance of :class:`astroid.scoped_nodes.Function`, |
| 157 which encloses the given node. |
| 158 """ |
| 159 current = node |
| 160 while current.parent and not isinstance(current.parent, nodes.Function): |
| 161 current = current.parent |
| 162 if current and current.parent: |
| 163 return current.parent |
| 164 |
| 165 def infer_name(self, context=None): |
| 166 """infer a Name: use name lookup rules""" |
| 167 frame, stmts = self.lookup(self.name) |
| 168 if not stmts: |
| 169 # Try to see if the name is enclosed in a nested function |
| 170 # and use the higher (first function) scope for searching. |
| 171 # TODO: should this be promoted to other nodes as well? |
| 172 parent_function = _higher_function_scope(self.scope()) |
| 173 if parent_function: |
| 174 _, stmts = parent_function.lookup(self.name) |
| 175 |
| 176 if not stmts: |
| 177 raise UnresolvableName(self.name) |
| 178 return _infer_stmts(stmts, context, frame, self.name) |
| 179 nodes.Name._infer = path_wrapper(infer_name) |
| 180 nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper |
| 181 |
| 182 |
| 183 def infer_callfunc(self, context=None): |
| 184 """infer a CallFunc node by trying to guess what the function returns""" |
| 185 if context is None: |
| 186 context = InferenceContext() |
| 187 for callee in self.func.infer(context): |
| 188 with context.scope( |
| 189 callcontext=CallContext(self.args, self.starargs, self.kwargs), |
| 190 boundnode=None, |
| 191 ): |
| 192 if callee is YES: |
| 193 yield callee |
| 194 continue |
| 195 try: |
| 196 if hasattr(callee, 'infer_call_result'): |
| 197 for infered in callee.infer_call_result(self, context): |
| 198 yield infered |
| 199 except InferenceError: |
| 200 ## XXX log error ? |
| 201 continue |
| 202 nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) |
| 203 |
| 204 |
| 205 def infer_import(self, context=None, asname=True, lookupname=None): |
| 206 """infer an Import node: return the imported module/object""" |
| 207 if lookupname is None: |
| 208 raise InferenceError() |
| 209 if asname: |
| 210 yield self.do_import_module(self.real_name(lookupname)) |
| 211 else: |
| 212 yield self.do_import_module(lookupname) |
| 213 nodes.Import._infer = path_wrapper(infer_import) |
| 214 |
| 215 def infer_name_module(self, name): |
| 216 context = InferenceContext() |
| 217 return self.infer(context, asname=False, lookupname=name) |
| 218 nodes.Import.infer_name_module = infer_name_module |
| 219 |
| 220 |
| 221 def infer_from(self, context=None, asname=True, lookupname=None): |
| 222 """infer a From nodes: return the imported module/object""" |
| 223 if lookupname is None: |
| 224 raise InferenceError() |
| 225 if asname: |
| 226 lookupname = self.real_name(lookupname) |
| 227 module = self.do_import_module() |
| 228 try: |
| 229 return _infer_stmts(module.getattr(lookupname, ignore_locals=module is s
elf.root()), context, lookupname=lookupname) |
| 230 except NotFoundError: |
| 231 raise InferenceError(lookupname) |
| 232 nodes.From._infer = path_wrapper(infer_from) |
| 233 |
| 234 |
| 235 def infer_getattr(self, context=None): |
| 236 """infer a Getattr node by using getattr on the associated object""" |
| 237 if not context: |
| 238 context = InferenceContext() |
| 239 for owner in self.expr.infer(context): |
| 240 if owner is YES: |
| 241 yield owner |
| 242 continue |
| 243 try: |
| 244 with context.scope(boundnode=owner): |
| 245 for obj in owner.igetattr(self.attrname, context): |
| 246 yield obj |
| 247 except (NotFoundError, InferenceError): |
| 248 pass |
| 249 except AttributeError: |
| 250 # XXX method / function |
| 251 pass |
| 252 nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) |
| 253 nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work
with a path wrapper |
| 254 |
| 255 |
| 256 def infer_global(self, context=None, lookupname=None): |
| 257 if lookupname is None: |
| 258 raise InferenceError() |
| 259 try: |
| 260 return _infer_stmts(self.root().getattr(lookupname), context) |
| 261 except NotFoundError: |
| 262 raise InferenceError() |
| 263 nodes.Global._infer = path_wrapper(infer_global) |
| 264 |
| 265 |
| 266 def infer_subscript(self, context=None): |
| 267 """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" |
| 268 value = next(self.value.infer(context)) |
| 269 if value is YES: |
| 270 yield YES |
| 271 return |
| 272 |
| 273 index = next(self.slice.infer(context)) |
| 274 if index is YES: |
| 275 yield YES |
| 276 return |
| 277 |
| 278 if isinstance(index, nodes.Const): |
| 279 try: |
| 280 assigned = value.getitem(index.value, context) |
| 281 except AttributeError: |
| 282 raise InferenceError() |
| 283 except (IndexError, TypeError): |
| 284 yield YES |
| 285 return |
| 286 |
| 287 # Prevent inferring if the infered subscript |
| 288 # is the same as the original subscripted object. |
| 289 if self is assigned: |
| 290 yield YES |
| 291 return |
| 292 for infered in assigned.infer(context): |
| 293 yield infered |
| 294 else: |
| 295 raise InferenceError() |
| 296 nodes.Subscript._infer = path_wrapper(infer_subscript) |
| 297 nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) |
| 298 |
| 299 def infer_unaryop(self, context=None): |
| 300 for operand in self.operand.infer(context): |
| 301 try: |
| 302 yield operand.infer_unary_op(self.op) |
| 303 except TypeError: |
| 304 continue |
| 305 except AttributeError: |
| 306 meth = UNARY_OP_METHOD[self.op] |
| 307 if meth is None: |
| 308 yield YES |
| 309 else: |
| 310 try: |
| 311 # XXX just suppose if the type implement meth, returned type |
| 312 # will be the same |
| 313 operand.getattr(meth) |
| 314 yield operand |
| 315 except GeneratorExit: |
| 316 raise |
| 317 except: |
| 318 yield YES |
| 319 nodes.UnaryOp._infer = path_wrapper(infer_unaryop) |
| 320 |
| 321 def _infer_binop(operator, operand1, operand2, context, failures=None): |
| 322 if operand1 is YES: |
| 323 yield operand1 |
| 324 return |
| 325 try: |
| 326 for valnode in operand1.infer_binary_op(operator, operand2, context): |
| 327 yield valnode |
| 328 except AttributeError: |
| 329 try: |
| 330 # XXX just suppose if the type implement meth, returned type |
| 331 # will be the same |
| 332 operand1.getattr(BIN_OP_METHOD[operator]) |
| 333 yield operand1 |
| 334 except: |
| 335 if failures is None: |
| 336 yield YES |
| 337 else: |
| 338 failures.append(operand1) |
| 339 |
| 340 def infer_binop(self, context=None): |
| 341 failures = [] |
| 342 for lhs in self.left.infer(context): |
| 343 for val in _infer_binop(self.op, lhs, self.right, context, failures): |
| 344 yield val |
| 345 for lhs in failures: |
| 346 for rhs in self.right.infer(context): |
| 347 for val in _infer_binop(self.op, rhs, lhs, context): |
| 348 yield val |
| 349 nodes.BinOp._infer = path_wrapper(infer_binop) |
| 350 |
| 351 |
| 352 def infer_arguments(self, context=None, lookupname=None): |
| 353 if lookupname is None: |
| 354 raise InferenceError() |
| 355 return _arguments_infer_argname(self, lookupname, context) |
| 356 nodes.Arguments._infer = infer_arguments |
| 357 |
| 358 |
| 359 def infer_ass(self, context=None): |
| 360 """infer a AssName/AssAttr: need to inspect the RHS part of the |
| 361 assign node |
| 362 """ |
| 363 stmt = self.statement() |
| 364 if isinstance(stmt, nodes.AugAssign): |
| 365 return stmt.infer(context) |
| 366 stmts = list(self.assigned_stmts(context=context)) |
| 367 return _infer_stmts(stmts, context) |
| 368 nodes.AssName._infer = path_wrapper(infer_ass) |
| 369 nodes.AssAttr._infer = path_wrapper(infer_ass) |
| 370 |
| 371 def infer_augassign(self, context=None): |
| 372 failures = [] |
| 373 for lhs in self.target.infer_lhs(context): |
| 374 for val in _infer_binop(self.op, lhs, self.value, context, failures): |
| 375 yield val |
| 376 for lhs in failures: |
| 377 for rhs in self.value.infer(context): |
| 378 for val in _infer_binop(self.op, rhs, lhs, context): |
| 379 yield val |
| 380 nodes.AugAssign._infer = path_wrapper(infer_augassign) |
| 381 |
| 382 |
| 383 # no infer method on DelName and DelAttr (expected InferenceError) |
| 384 |
| 385 |
| 386 def infer_empty_node(self, context=None): |
| 387 if not self.has_underlying_object(): |
| 388 yield YES |
| 389 else: |
| 390 try: |
| 391 for infered in MANAGER.infer_ast_from_something(self.object, |
| 392 context=context): |
| 393 yield infered |
| 394 except AstroidError: |
| 395 yield YES |
| 396 nodes.EmptyNode._infer = path_wrapper(infer_empty_node) |
| 397 |
| 398 |
| 399 def infer_index(self, context=None): |
| 400 return self.value.infer(context) |
| 401 nodes.Index._infer = infer_index |
OLD | NEW |