| 1 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 2 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 3 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 4 | # version. |
|---|
| 5 | # |
|---|
| 6 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU General Public License along with |
|---|
| 11 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 13 | """this module contains a set of functions to handle inference on astng trees |
|---|
| 14 | |
|---|
| 15 | :author: Sylvain Thenault |
|---|
| 16 | :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 17 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 18 | :copyright: 2003-2008 Sylvain Thenault |
|---|
| 19 | :contact: mailto:thenault@gmail.com |
|---|
| 20 | """ |
|---|
| 21 | |
|---|
| 22 | from __future__ import generators |
|---|
| 23 | |
|---|
| 24 | __doctype__ = "restructuredtext en" |
|---|
| 25 | |
|---|
| 26 | from copy import copy |
|---|
| 27 | |
|---|
| 28 | from logilab.common.compat import imap, chain, set |
|---|
| 29 | |
|---|
| 30 | from logilab.astng import MANAGER, YES, InferenceContext, Instance, Generator, \ |
|---|
| 31 | unpack_infer, _infer_stmts, nodes, copy_context |
|---|
| 32 | from logilab.astng import ASTNGError, InferenceError, UnresolvableName, \ |
|---|
| 33 | NoDefault, NotFoundError, ASTNGBuildingException |
|---|
| 34 | |
|---|
| 35 | |
|---|
| 36 | def path_wrapper(func): |
|---|
| 37 | """return the given infer function wrapped to handle the path""" |
|---|
| 38 | def wrapped(node, context=None, _func=func, **kwargs): |
|---|
| 39 | """wrapper function handling context""" |
|---|
| 40 | if context is None: |
|---|
| 41 | context = InferenceContext(node) |
|---|
| 42 | context.push(node) |
|---|
| 43 | yielded = set() |
|---|
| 44 | try: |
|---|
| 45 | for res in _func(node, context, **kwargs): |
|---|
| 46 | # unproxy only true instance, not const, tuple, dict... |
|---|
| 47 | if res.__class__ is Instance: |
|---|
| 48 | ares = res._proxied |
|---|
| 49 | else: |
|---|
| 50 | ares = res |
|---|
| 51 | if not ares in yielded: |
|---|
| 52 | yield res |
|---|
| 53 | yielded.add(ares) |
|---|
| 54 | context.pop() |
|---|
| 55 | except: |
|---|
| 56 | context.pop() |
|---|
| 57 | raise |
|---|
| 58 | return wrapped |
|---|
| 59 | |
|---|
| 60 | # .infer method ############################################################### |
|---|
| 61 | |
|---|
| 62 | def infer_default(self, context=None): |
|---|
| 63 | """we don't know how to resolve a statement by default""" |
|---|
| 64 | #print 'inference error', self, name, path |
|---|
| 65 | raise InferenceError(self.__class__.__name__) |
|---|
| 66 | |
|---|
| 67 | #infer_default = infer_default |
|---|
| 68 | nodes.Node.infer = infer_default |
|---|
| 69 | |
|---|
| 70 | |
|---|
| 71 | def infer_end(self, context=None): |
|---|
| 72 | """inference's end for node such as Module, Class, Function, Const... |
|---|
| 73 | """ |
|---|
| 74 | yield self |
|---|
| 75 | |
|---|
| 76 | #infer_end = path_wrapper(infer_end) |
|---|
| 77 | nodes.Module.infer = nodes.Class.infer = infer_end |
|---|
| 78 | nodes.List.infer = infer_end |
|---|
| 79 | nodes.Tuple.infer = infer_end |
|---|
| 80 | nodes.Dict.infer = infer_end |
|---|
| 81 | nodes.Const.infer = infer_end |
|---|
| 82 | |
|---|
| 83 | def infer_empty_node(self, context=None): |
|---|
| 84 | if not self.has_underlying_object(): |
|---|
| 85 | yield YES |
|---|
| 86 | else: |
|---|
| 87 | try: |
|---|
| 88 | for infered in MANAGER.infer_astng_from_something(self.object, |
|---|
| 89 | context=context): |
|---|
| 90 | yield infered |
|---|
| 91 | except ASTNGError: |
|---|
| 92 | yield YES |
|---|
| 93 | nodes.EmptyNode.infer = path_wrapper(infer_empty_node) |
|---|
| 94 | |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | class CallContext: |
|---|
| 98 | """when infering a function call, this class is used to remember values |
|---|
| 99 | given as argument |
|---|
| 100 | """ |
|---|
| 101 | def __init__(self, args, starargs, dstarargs): |
|---|
| 102 | self.args = [] |
|---|
| 103 | self.nargs = {} |
|---|
| 104 | for arg in args: |
|---|
| 105 | if isinstance(arg, nodes.Keyword): |
|---|
| 106 | self.nargs[arg.name] = arg.expr |
|---|
| 107 | else: |
|---|
| 108 | self.args.append(arg) |
|---|
| 109 | self.starargs = starargs |
|---|
| 110 | self.dstarargs = dstarargs |
|---|
| 111 | |
|---|
| 112 | def infer_argument(self, funcnode, name, context): |
|---|
| 113 | """infer a function argument value according the the call context""" |
|---|
| 114 | # 1. search in named keywords |
|---|
| 115 | try: |
|---|
| 116 | return self.nargs[name].infer(context) |
|---|
| 117 | except KeyError: |
|---|
| 118 | # Function.argnames can be None in astng (means that we don't have |
|---|
| 119 | # information on argnames) |
|---|
| 120 | if funcnode.argnames is not None: |
|---|
| 121 | try: |
|---|
| 122 | argindex = funcnode.argnames.index(name) |
|---|
| 123 | except ValueError: |
|---|
| 124 | pass |
|---|
| 125 | else: |
|---|
| 126 | # 2. first argument of instance/class method |
|---|
| 127 | if argindex == 0 and funcnode.type in ('method', 'classmethod'): |
|---|
| 128 | if context.boundnode is not None: |
|---|
| 129 | boundnode = context.boundnode |
|---|
| 130 | else: |
|---|
| 131 | # XXX can do better ? |
|---|
| 132 | boundnode = funcnode.parent.frame() |
|---|
| 133 | if funcnode.type == 'method': |
|---|
| 134 | return iter((Instance(boundnode),)) |
|---|
| 135 | if funcnode.type == 'classmethod': |
|---|
| 136 | return iter((boundnode,)) |
|---|
| 137 | # 2. search arg index |
|---|
| 138 | try: |
|---|
| 139 | return self.args[argindex].infer(context) |
|---|
| 140 | except IndexError: |
|---|
| 141 | pass |
|---|
| 142 | # 3. search in *args (.starargs) |
|---|
| 143 | if self.starargs is not None: |
|---|
| 144 | its = [] |
|---|
| 145 | for infered in self.starargs.infer(context): |
|---|
| 146 | if infered is YES: |
|---|
| 147 | its.append((YES,)) |
|---|
| 148 | continue |
|---|
| 149 | try: |
|---|
| 150 | its.append(infered.getitem(argindex).infer(context)) |
|---|
| 151 | except (InferenceError, AttributeError): |
|---|
| 152 | its.append((YES,)) |
|---|
| 153 | except IndexError: |
|---|
| 154 | continue |
|---|
| 155 | if its: |
|---|
| 156 | return chain(*its) |
|---|
| 157 | # 4. XXX search in **kwargs (.dstarargs) |
|---|
| 158 | if self.dstarargs is not None: |
|---|
| 159 | its = [] |
|---|
| 160 | for infered in self.dstarargs.infer(context): |
|---|
| 161 | if infered is YES: |
|---|
| 162 | its.append((YES,)) |
|---|
| 163 | continue |
|---|
| 164 | try: |
|---|
| 165 | its.append(infered.getitem(name).infer(context)) |
|---|
| 166 | except (InferenceError, AttributeError): |
|---|
| 167 | its.append((YES,)) |
|---|
| 168 | except IndexError: |
|---|
| 169 | continue |
|---|
| 170 | if its: |
|---|
| 171 | return chain(*its) |
|---|
| 172 | # 5. */** argument, (Tuple or Dict) |
|---|
| 173 | mularg = funcnode.mularg_class(name) |
|---|
| 174 | if mularg is not None: |
|---|
| 175 | # XXX should be able to compute values inside |
|---|
| 176 | return iter((mularg,)) |
|---|
| 177 | # 6. return default value if any |
|---|
| 178 | try: |
|---|
| 179 | return funcnode.default_value(name).infer(context) |
|---|
| 180 | except NoDefault: |
|---|
| 181 | raise InferenceError(name) |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | def infer_function(self, context=None): |
|---|
| 185 | """infer on Function nodes must be take with care since it |
|---|
| 186 | may be called to infer one of it's argument (in which case <name> |
|---|
| 187 | should be given) |
|---|
| 188 | """ |
|---|
| 189 | name = context.lookupname |
|---|
| 190 | # no name is given, we are infering the function itself |
|---|
| 191 | if name is None: |
|---|
| 192 | yield self |
|---|
| 193 | return |
|---|
| 194 | if context.callcontext: |
|---|
| 195 | # reset call context/name |
|---|
| 196 | callcontext = context.callcontext |
|---|
| 197 | context = copy_context(context) |
|---|
| 198 | context.callcontext = None |
|---|
| 199 | for infered in callcontext.infer_argument(self, name, context): |
|---|
| 200 | yield infered |
|---|
| 201 | return |
|---|
| 202 | # Function.argnames can be None in astng (means that we don't have |
|---|
| 203 | # information on argnames), in which case we can't do anything more |
|---|
| 204 | if self.argnames is None: |
|---|
| 205 | yield YES |
|---|
| 206 | return |
|---|
| 207 | if not name in self.argnames: |
|---|
| 208 | raise InferenceError() |
|---|
| 209 | # first argument of instance/class method |
|---|
| 210 | if name == self.argnames[0]: |
|---|
| 211 | if self.type == 'method': |
|---|
| 212 | yield Instance(self.parent.frame()) |
|---|
| 213 | return |
|---|
| 214 | if self.type == 'classmethod': |
|---|
| 215 | yield self.parent.frame() |
|---|
| 216 | return |
|---|
| 217 | mularg = self.mularg_class(name) |
|---|
| 218 | if mularg is not None: # */** argument, no doubt it's a Tuple or Dict |
|---|
| 219 | yield mularg |
|---|
| 220 | return |
|---|
| 221 | # if there is a default value, yield it. And then yield YES to reflect |
|---|
| 222 | # we can't guess given argument value |
|---|
| 223 | try: |
|---|
| 224 | context = copy_context(context) |
|---|
| 225 | for infered in self.default_value(name).infer(context): |
|---|
| 226 | yield infered |
|---|
| 227 | yield YES |
|---|
| 228 | except NoDefault: |
|---|
| 229 | yield YES |
|---|
| 230 | |
|---|
| 231 | nodes.Function.infer = path_wrapper(infer_function) |
|---|
| 232 | nodes.Lambda.infer = path_wrapper(infer_function) |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | def infer_name(self, context=None): |
|---|
| 236 | """infer a Name: use name lookup rules""" |
|---|
| 237 | context = context.clone() |
|---|
| 238 | context.lookupname = self.name |
|---|
| 239 | frame, stmts = self.lookup(self.name) |
|---|
| 240 | if not stmts: |
|---|
| 241 | raise UnresolvableName(self.name) |
|---|
| 242 | return _infer_stmts(stmts, context, frame) |
|---|
| 243 | |
|---|
| 244 | nodes.Name.infer = path_wrapper(infer_name) |
|---|
| 245 | |
|---|
| 246 | |
|---|
| 247 | def infer_assname(self, context=None): |
|---|
| 248 | """infer a AssName/AssAttr: need to inspect the RHS part of the |
|---|
| 249 | assign node |
|---|
| 250 | """ |
|---|
| 251 | stmts = self.assigned_stmts(context=context) |
|---|
| 252 | return _infer_stmts(stmts, context) |
|---|
| 253 | |
|---|
| 254 | nodes.AssName.infer = path_wrapper(infer_assname) |
|---|
| 255 | |
|---|
| 256 | |
|---|
| 257 | def infer_assattr(self, context=None): |
|---|
| 258 | """infer a AssName/AssAttr: need to inspect the RHS part of the |
|---|
| 259 | assign node |
|---|
| 260 | """ |
|---|
| 261 | stmts = self.assigned_stmts(context=context) |
|---|
| 262 | return _infer_stmts(stmts, context) |
|---|
| 263 | |
|---|
| 264 | nodes.AssAttr.infer = path_wrapper(infer_assattr) |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | def infer_callfunc(self, context=None): |
|---|
| 268 | """infer a CallFunc node by trying to guess what's the function is |
|---|
| 269 | returning |
|---|
| 270 | """ |
|---|
| 271 | one_infered = False |
|---|
| 272 | context = context.clone() |
|---|
| 273 | context.callcontext = CallContext(self.args, self.star_args, self.dstar_args) |
|---|
| 274 | for callee in self.node.infer(context): |
|---|
| 275 | if callee is YES: |
|---|
| 276 | yield callee |
|---|
| 277 | one_infered = True |
|---|
| 278 | continue |
|---|
| 279 | try: |
|---|
| 280 | for infered in callee.infer_call_result(self, context): |
|---|
| 281 | yield infered |
|---|
| 282 | one_infered = True |
|---|
| 283 | except (AttributeError, InferenceError): |
|---|
| 284 | ## XXX log error ? |
|---|
| 285 | continue |
|---|
| 286 | if not one_infered: |
|---|
| 287 | raise InferenceError() |
|---|
| 288 | |
|---|
| 289 | nodes.CallFunc.infer = path_wrapper(infer_callfunc) |
|---|
| 290 | |
|---|
| 291 | |
|---|
| 292 | def infer_getattr(self, context=None): |
|---|
| 293 | """infer a Getattr node by using getattr on the associated object |
|---|
| 294 | """ |
|---|
| 295 | one_infered = False |
|---|
| 296 | # XXX |
|---|
| 297 | #context = context.clone() |
|---|
| 298 | for owner in self.expr.infer(context): |
|---|
| 299 | if owner is YES: |
|---|
| 300 | yield owner |
|---|
| 301 | one_infered = True |
|---|
| 302 | continue |
|---|
| 303 | try: |
|---|
| 304 | context.boundnode = owner |
|---|
| 305 | for obj in owner.igetattr(self.attrname, context): |
|---|
| 306 | yield obj |
|---|
| 307 | one_infered = True |
|---|
| 308 | context.boundnode = None |
|---|
| 309 | except (NotFoundError, InferenceError): |
|---|
| 310 | continue |
|---|
| 311 | except AttributeError: |
|---|
| 312 | # XXX method / function |
|---|
| 313 | continue |
|---|
| 314 | if not one_infered: |
|---|
| 315 | raise InferenceError() |
|---|
| 316 | |
|---|
| 317 | nodes.Getattr.infer = path_wrapper(infer_getattr) |
|---|
| 318 | |
|---|
| 319 | |
|---|
| 320 | def _imported_module_astng(node, modname): |
|---|
| 321 | """return the ast for a module whose name is <modname> imported by <node> |
|---|
| 322 | """ |
|---|
| 323 | # handle special case where we are on a package node importing a module |
|---|
| 324 | # using the same name as the package, which may end in an infinite loop |
|---|
| 325 | # on relative imports |
|---|
| 326 | # XXX: no more needed ? |
|---|
| 327 | mymodule = node.root() |
|---|
| 328 | if mymodule.relative_name(modname) == mymodule.name: |
|---|
| 329 | # FIXME: I don't know what to do here... |
|---|
| 330 | raise InferenceError(modname) |
|---|
| 331 | try: |
|---|
| 332 | return mymodule.import_module(modname) |
|---|
| 333 | except (ASTNGBuildingException, SyntaxError): |
|---|
| 334 | raise InferenceError(modname) |
|---|
| 335 | |
|---|
| 336 | def infer_import(self, context=None, asname=True): |
|---|
| 337 | """self resolve on From / Import nodes return the imported module/object""" |
|---|
| 338 | name = context.lookupname |
|---|
| 339 | if name is None: |
|---|
| 340 | raise InferenceError() |
|---|
| 341 | if asname: |
|---|
| 342 | yield _imported_module_astng(self, self.real_name(name)) |
|---|
| 343 | else: |
|---|
| 344 | yield _imported_module_astng(self, name) |
|---|
| 345 | |
|---|
| 346 | nodes.Import.infer = path_wrapper(infer_import) |
|---|
| 347 | |
|---|
| 348 | def infer_from(self, context=None, asname=True): |
|---|
| 349 | """self resolve on From / Import nodes return the imported module/object""" |
|---|
| 350 | name = context.lookupname |
|---|
| 351 | if name is None: |
|---|
| 352 | raise InferenceError() |
|---|
| 353 | if asname: |
|---|
| 354 | name = self.real_name(name) |
|---|
| 355 | module = _imported_module_astng(self, self.modname) |
|---|
| 356 | try: |
|---|
| 357 | context = copy_context(context) |
|---|
| 358 | context.lookupname = name |
|---|
| 359 | return _infer_stmts(module.getattr(name), context) |
|---|
| 360 | except NotFoundError: |
|---|
| 361 | raise InferenceError(name) |
|---|
| 362 | |
|---|
| 363 | nodes.From.infer = path_wrapper(infer_from) |
|---|
| 364 | |
|---|
| 365 | |
|---|
| 366 | def infer_global(self, context=None): |
|---|
| 367 | if context.lookupname is None: |
|---|
| 368 | raise InferenceError() |
|---|
| 369 | try: |
|---|
| 370 | return _infer_stmts(self.root().getattr(context.lookupname), context) |
|---|
| 371 | except NotFoundError: |
|---|
| 372 | raise InferenceError() |
|---|
| 373 | nodes.Global.infer = path_wrapper(infer_global) |
|---|
| 374 | |
|---|
| 375 | |
|---|
| 376 | def infer_subscript(self, context=None): |
|---|
| 377 | """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1] |
|---|
| 378 | """ |
|---|
| 379 | if len(self.subs) == 1: |
|---|
| 380 | index = self.subs[0].infer(context).next() |
|---|
| 381 | if index is YES: |
|---|
| 382 | yield YES |
|---|
| 383 | return |
|---|
| 384 | try: |
|---|
| 385 | # suppose it's a Tuple/List node (attribute error else) |
|---|
| 386 | assigned = self.expr.getitem(index.value) |
|---|
| 387 | except AttributeError: |
|---|
| 388 | raise InferenceError() |
|---|
| 389 | except IndexError: |
|---|
| 390 | yield YES |
|---|
| 391 | return |
|---|
| 392 | for infered in assigned.infer(context): |
|---|
| 393 | yield infered |
|---|
| 394 | else: |
|---|
| 395 | raise InferenceError() |
|---|
| 396 | nodes.Subscript.infer = path_wrapper(infer_subscript) |
|---|
| 397 | |
|---|
| 398 | def infer_unarysub(self, context=None): |
|---|
| 399 | for infered in self.expr.infer(context): |
|---|
| 400 | try: |
|---|
| 401 | value = -infered.value |
|---|
| 402 | except (TypeError, AttributeError): |
|---|
| 403 | yield YES |
|---|
| 404 | continue |
|---|
| 405 | node = copy(self.expr) |
|---|
| 406 | node.value = value |
|---|
| 407 | yield node |
|---|
| 408 | nodes.UnarySub.infer = path_wrapper(infer_unarysub) |
|---|
| 409 | |
|---|
| 410 | def infer_unaryadd(self, context=None): |
|---|
| 411 | return self.expr.infer(context) |
|---|
| 412 | nodes.UnaryAdd.infer = infer_unaryadd |
|---|
| 413 | |
|---|
| 414 | def _py_value(node): |
|---|
| 415 | try: |
|---|
| 416 | return node.value |
|---|
| 417 | except AttributeError: |
|---|
| 418 | # not a constant |
|---|
| 419 | if isinstance(node, nodes.Dict): |
|---|
| 420 | return {} |
|---|
| 421 | if isinstance(node, nodes.List): |
|---|
| 422 | return [] |
|---|
| 423 | if isinstance(node, nodes.Tuple): |
|---|
| 424 | return () |
|---|
| 425 | raise ValueError() |
|---|
| 426 | |
|---|
| 427 | def _infer_operator(self, context=None, impl=None, meth='__method__'): |
|---|
| 428 | for lhs in self.left.infer(context): |
|---|
| 429 | try: |
|---|
| 430 | lhsvalue = _py_value(lhs) |
|---|
| 431 | except ValueError: |
|---|
| 432 | # not a constant |
|---|
| 433 | try: |
|---|
| 434 | # XXX just suppose if the type implement meth, returned type |
|---|
| 435 | # will be the same |
|---|
| 436 | lhs.getattr(meth) |
|---|
| 437 | yield lhs |
|---|
| 438 | except: |
|---|
| 439 | yield YES |
|---|
| 440 | continue |
|---|
| 441 | for |
|---|