| 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 extends ast "scoped" node, i.e. which are opening a new |
|---|
| 14 | local scope in the language definition : Module, Class, Function (and |
|---|
| 15 | Lambda in some extends). |
|---|
| 16 | |
|---|
| 17 | Each new methods and attributes added on each class are documented |
|---|
| 18 | below. |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | :author: Sylvain Thenault |
|---|
| 22 | :copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 23 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 24 | :copyright: 2003-2007 Sylvain Thenault |
|---|
| 25 | :contact: mailto:thenault@gmail.com |
|---|
| 26 | """ |
|---|
| 27 | from __future__ import generators |
|---|
| 28 | |
|---|
| 29 | __doctype__ = "restructuredtext en" |
|---|
| 30 | |
|---|
| 31 | import sys |
|---|
| 32 | |
|---|
| 33 | from logilab.common.compat import chain, set |
|---|
| 34 | |
|---|
| 35 | from logilab.astng.utils import extend_class |
|---|
| 36 | from logilab.astng import YES, MANAGER, Instance, InferenceContext, copy_context, \ |
|---|
| 37 | unpack_infer, _infer_stmts, \ |
|---|
| 38 | Class, Const, Dict, Function, GenExpr, Lambda, \ |
|---|
| 39 | Module, Name, Pass, Raise, Tuple, Yield |
|---|
| 40 | from logilab.astng import NotFoundError, NoDefault, \ |
|---|
| 41 | ASTNGBuildingException, InferenceError |
|---|
| 42 | |
|---|
| 43 | # module class dict/iterator interface ######################################## |
|---|
| 44 | |
|---|
| 45 | class LocalsDictMixIn(object): |
|---|
| 46 | """ this class provides locals handling common to Module, Function |
|---|
| 47 | and Class nodes, including a dict like interface for direct access |
|---|
| 48 | to locals information |
|---|
| 49 | |
|---|
| 50 | /!\ this class should not be used directly /!\ it's |
|---|
| 51 | only used as a methods and attribute container, and update the |
|---|
| 52 | original class from the compiler.ast module using its dictionnary |
|---|
| 53 | (see below the class definition) |
|---|
| 54 | """ |
|---|
| 55 | |
|---|
| 56 | # attributes below are set by the builder module or by raw factories |
|---|
| 57 | |
|---|
| 58 | # dictionary of locals with name as key and node defining the local as |
|---|
| 59 | # value |
|---|
| 60 | locals = None |
|---|
| 61 | |
|---|
| 62 | def qname(self): |
|---|
| 63 | """return the 'qualified' name of the node, eg module.name, |
|---|
| 64 | module.class.name ... |
|---|
| 65 | """ |
|---|
| 66 | if self.parent is None: |
|---|
| 67 | return self.name |
|---|
| 68 | return '%s.%s' % (self.parent.frame().qname(), self.name) |
|---|
| 69 | |
|---|
| 70 | def frame(self): |
|---|
| 71 | """return the first parent frame node (i.e. Module, Function or Class) |
|---|
| 72 | """ |
|---|
| 73 | return self |
|---|
| 74 | |
|---|
| 75 | def scope(self): |
|---|
| 76 | """return the first node defining a new scope (i.e. Module, |
|---|
| 77 | Function, Class, Lambda but also GenExpr) |
|---|
| 78 | """ |
|---|
| 79 | return self |
|---|
| 80 | |
|---|
| 81 | def set_local(self, name, stmt): |
|---|
| 82 | """define <name> in locals (<stmt> is the node defining the name) |
|---|
| 83 | if the node is a Module node (i.e. has globals), add the name to |
|---|
| 84 | globals |
|---|
| 85 | |
|---|
| 86 | if the name is already defined, ignore it |
|---|
| 87 | """ |
|---|
| 88 | self.locals.setdefault(name, []).append(stmt) |
|---|
| 89 | |
|---|
| 90 | __setitem__ = set_local |
|---|
| 91 | |
|---|
| 92 | def add_local_node(self, child_node, name=None): |
|---|
| 93 | """append a child which should alter locals to the given node""" |
|---|
| 94 | if name != '__class__': |
|---|
| 95 | # add __class__ node as a child will cause infinite recursion later! |
|---|
| 96 | self._append_node(child_node) |
|---|
| 97 | self.set_local(name or child_node.name, child_node) |
|---|
| 98 | |
|---|
| 99 | def _append_node(self, child_node): |
|---|
| 100 | """append a child, linking it in the tree""" |
|---|
| 101 | self.code.nodes.append(child_node) |
|---|
| 102 | child_node.parent = self |
|---|
| 103 | |
|---|
| 104 | def __getitem__(self, item): |
|---|
| 105 | """method from the `dict` interface returning the first node |
|---|
| 106 | associated with the given name in the locals dictionnary |
|---|
| 107 | |
|---|
| 108 | :type item: str |
|---|
| 109 | :param item: the name of the locally defined object |
|---|
| 110 | :raises KeyError: if the name is not defined |
|---|
| 111 | """ |
|---|
| 112 | return self.locals[item][0] |
|---|
| 113 | |
|---|
| 114 | def __iter__(self): |
|---|
| 115 | """method from the `dict` interface returning an iterator on |
|---|
| 116 | `self.keys()` |
|---|
| 117 | """ |
|---|
| 118 | return iter(self.keys()) |
|---|
| 119 | |
|---|
| 120 | def keys(self): |
|---|
| 121 | """method from the `dict` interface returning a tuple containing |
|---|
| 122 | locally defined names |
|---|
| 123 | """ |
|---|
| 124 | return self.locals.keys() |
|---|
| 125 | ## associated to nodes which are instance of `Function` or |
|---|
| 126 | ## `Class` |
|---|
| 127 | ## """ |
|---|
| 128 | ## # FIXME: sort keys according to line number ? |
|---|
| 129 | ## try: |
|---|
| 130 | ## return self.__keys |
|---|
| 131 | ## except AttributeError: |
|---|
| 132 | ## keys = [member.name for member in self.locals.values() |
|---|
| 133 | ## if (isinstance(member, Function) |
|---|
| 134 | ## or isinstance(member, Class)) |
|---|
| 135 | ## and member.parent.frame() is self] |
|---|
| 136 | ## self.__keys = tuple(keys) |
|---|
| 137 | ## return keys |
|---|
| 138 | |
|---|
| 139 | def values(self): |
|---|
| 140 | """method from the `dict` interface returning a tuple containing |
|---|
| 141 | locally defined nodes which are instance of `Function` or `Class` |
|---|
| 142 | """ |
|---|
| 143 | return [self[key] for key in self.keys()] |
|---|
| 144 | |
|---|
| 145 | def items(self): |
|---|
| 146 | """method from the `dict` interface returning a list of tuple |
|---|
| 147 | containing each locally defined name with its associated node, |
|---|
| 148 | which is an instance of `Function` or `Class` |
|---|
| 149 | """ |
|---|
| 150 | return zip(self.keys(), self.values()) |
|---|
| 151 | |
|---|
| 152 | def has_key(self, name): |
|---|
| 153 | """method from the `dict` interface returning True if the given |
|---|
| 154 | name is defined in the locals dictionary |
|---|
| 155 | """ |
|---|
| 156 | return self.locals.has_key(name) |
|---|
| 157 | |
|---|
| 158 | __contains__ = has_key |
|---|
| 159 | |
|---|
| 160 | extend_class(Module, LocalsDictMixIn) |
|---|
| 161 | extend_class(Class, LocalsDictMixIn) |
|---|
| 162 | extend_class(Function, LocalsDictMixIn) |
|---|
| 163 | extend_class(Lambda, LocalsDictMixIn) |
|---|
| 164 | # GenExpr has it's own locals but isn't a frame |
|---|
| 165 | extend_class(GenExpr, LocalsDictMixIn) |
|---|
| 166 | def frame(self): |
|---|
| 167 | return self.parent.frame() |
|---|
| 168 | GenExpr.frame = frame |
|---|
| 169 | |
|---|
| 170 | |
|---|
| 171 | class GetattrMixIn(object): |
|---|
| 172 | def getattr(self, name, context=None): |
|---|
| 173 | try: |
|---|
| 174 | return self.locals[name] |
|---|
| 175 | except KeyError: |
|---|
| 176 | raise NotFoundError(name) |
|---|
| 177 | |
|---|
| 178 | def igetattr(self, name, context=None): |
|---|
| 179 | """infered getattr""" |
|---|
| 180 | # set lookup name since this is necessary to infer on import nodes for |
|---|
| 181 | # instance |
|---|
| 182 | context = copy_context(context) |
|---|
| 183 | context.lookupname = name |
|---|
| 184 | try: |
|---|
| 185 | return _infer_stmts(self.getattr(name, context), context, frame=self) |
|---|
| 186 | except NotFoundError: |
|---|
| 187 | raise InferenceError(name) |
|---|
| 188 | extend_class(Module, GetattrMixIn) |
|---|
| 189 | extend_class(Class, GetattrMixIn) |
|---|
| 190 | |
|---|
| 191 | # Module ##################################################################### |
|---|
| 192 | |
|---|
| 193 | class ModuleNG(object): |
|---|
| 194 | """/!\ this class should not be used directly /!\ it's |
|---|
| 195 | only used as a methods and attribute container, and update the |
|---|
| 196 | original class from the compiler.ast module using its dictionnary |
|---|
| 197 | (see below the class definition) |
|---|
| 198 | """ |
|---|
| 199 | |
|---|
| 200 | # attributes below are set by the builder module or by raw factories |
|---|
| 201 | |
|---|
| 202 | # the file from which as been extracted the astng representation. It may |
|---|
| 203 | # be None if the representation has been built from a built-in module |
|---|
| 204 | file = None |
|---|
| 205 | # the module name |
|---|
| 206 | name = None |
|---|
| 207 | # boolean for astng built from source (i.e. ast) |
|---|
| 208 | pure_python = None |
|---|
| 209 | # boolean for package module |
|---|
| 210 | package = None |
|---|
| 211 | # dictionary of globals with name as key and node defining the global |
|---|
| 212 | # as value |
|---|
| 213 | globals = None |
|---|
| 214 | |
|---|
| 215 | def pytype(self): |
|---|
| 216 | return '__builtin__.module' |
|---|
| 217 | |
|---|
| 218 | def getattr(self, name, context=None): |
|---|
| 219 | try: |
|---|
| 220 | return self.locals[name] |
|---|
| 221 | except KeyError: |
|---|
| 222 | if self.package: |
|---|
| 223 | try: |
|---|
| 224 | return [self.import_module(name, relative_only=True)] |
|---|
| 225 | except KeyboardInterrupt: |
|---|
| 226 | raise |
|---|
| 227 | except: |
|---|
| 228 | pass |
|---|
| 229 | raise NotFoundError(name) |
|---|
| 230 | |
|---|
| 231 | def _append_node(self, child_node): |
|---|
| 232 | """append a child version specific to Module node""" |
|---|
| 233 | self.node.nodes.append(child_node) |
|---|
| 234 | child_node.parent = self |
|---|
| 235 | |
|---|
| 236 | def source_line(self): |
|---|
| 237 | """return the source line number, 0 on a module""" |
|---|
| 238 | return 0 |
|---|
| 239 | |
|---|
| 240 | def fully_defined(self): |
|---|
| 241 | """return True if this module has been built from a .py file |
|---|
| 242 | and so contains a complete representation including the code |
|---|
| 243 | """ |
|---|
| 244 | return self.file is not None and self.file.endswith('.py') |
|---|
| 245 | |
|---|
| 246 | def statement(self): |
|---|
| 247 | """return the first parent node marked as statement node |
|---|
| 248 | consider a module as a statement... |
|---|
| 249 | """ |
|---|
| 250 | return self |
|---|
| 251 | |
|---|
| 252 | def import_module(self, modname, relative_only=False): |
|---|
| 253 | """import the given module considering self as context""" |
|---|
| 254 | try: |
|---|
| 255 | return MANAGER.astng_from_module_name(self.relative_name(modname)) |
|---|
| 256 | except ASTNGBuildingException: |
|---|
| 257 | if relative_only: |
|---|
| 258 | raise |
|---|
| 259 | module = MANAGER.astng_from_module_name(modname) |
|---|
| 260 | return module |
|---|
| 261 | |
|---|
| 262 | def relative_name(self, modname): |
|---|
| 263 | if self.package: |
|---|
| 264 | return '%s.%s' % (self.name, modname) |
|---|
| 265 | package_name = '.'.join(self.name.split('.')[:-1]) |
|---|
| 266 | if package_name: |
|---|
| 267 | return '%s.%s' % (package_name, modname) |
|---|
| 268 | return modname |
|---|
| 269 | |
|---|
| 270 | def wildcard_import_names(self): |
|---|
| 271 | """return the list of imported names when this module is 'wildard |
|---|
| 272 | imported' |
|---|
| 273 | |
|---|
| 274 | It doesn't include the '__builtins__' name which is added by the |
|---|
| 275 | current CPython implementation of wildcard imports. |
|---|
| 276 | """ |
|---|
| 277 | # take advantage of a living module if it exists |
|---|
| 278 | try: |
|---|
| 279 | living = sys.modules[self.name] |
|---|
| 280 | except KeyError: |
|---|
| 281 | pass |
|---|
| 282 | else: |
|---|
| 283 | try: |
|---|
| 284 | return living.__all__ |
|---|
| 285 | except AttributeError: |
|---|
| 286 | return [name for name in living.__dict__.keys() |
|---|
| 287 | if not name.startswith('_')] |
|---|
| 288 | # else lookup the astng |
|---|
| 289 | try: |
|---|
| 290 | explicit = self['__all__'].assigned_stmts().next() |
|---|
| 291 | # should be a tuple of constant string |
|---|
| 292 | return [const.value for const in explicit.nodes] |
|---|
| 293 | except (KeyError, AttributeError, InferenceError): |
|---|
| 294 | # XXX should admit we have lost if there is something like |
|---|
| 295 | # __all__ that we've not been able to analyse (such as |
|---|
| 296 | # dynamically constructed __all__) |
|---|
| 297 | return [name for name in self.keys() |
|---|
| 298 | if not name.startswith('_')] |
|---|
| 299 | |
|---|
| 300 | extend_class(Module, ModuleNG) |
|---|
| 301 | |
|---|
| 302 | # Function ################################################################### |
|---|
| 303 | |
|---|
| 304 | class FunctionNG(object): |
|---|
| 305 | """/!\ this class should not be used directly /!\ it's |
|---|
| 306 | only used as a methods and attribute container, and update the |
|---|
| 307 | original class from the compiler.ast module using its dictionnary |
|---|
| 308 | (see below the class definition) |
|---|
| 309 | """ |
|---|
| 310 | |
|---|
| 311 | # attributes below are set by the builder module or by raw factories |
|---|
| 312 | |
|---|
| 313 | # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod' |
|---|
| 314 | type = 'function' |
|---|
| 315 | # list of argument names. MAY BE NONE on some builtin functions where |
|---|
| 316 | # arguments are unknown |
|---|
| 317 | argnames = None |
|---|
| 318 | |
|---|
| 319 | def pytype(self): |
|---|
| 320 | if 'method' in self.type: |
|---|
| 321 | return '__builtin__.instancemethod' |
|---|
| 322 | return '__builtin__.function' |
|---|
| 323 | |
|---|
| 324 | def is_method(self): |
|---|
| 325 | """return true if the function node should be considered as a method""" |
|---|
| 326 | return self.type != 'function' |
|---|
| 327 | |
|---|
| 328 | def is_bound(self): |
|---|
| 329 | """return true if the function is bound to an Instance or a class""" |
|---|
| 330 | return self.type == 'classmethod' |
|---|
| 331 | |
|---|
| 332 | def is_abstract(self, pass_is_abstract=True): |
|---|
| 333 | """return true if the method is abstract |
|---|
| 334 | It's considered as abstract if the only statement is a raise of |
|---|
| 335 | NotImplementError, or, if pass_is_abstract, a pass statement |
|---|
| 336 | """ |
|---|
| 337 | for child_node in self.code.getChildNodes(): |
|---|
| 338 | if isinstance(child_node, Raise) and child_node.expr1: |
|---|
| 339 | try: |
|---|
| 340 | name = child_node.expr1.nodes_of_class(Name).next() |
|---|
| 341 | if name.name == 'NotImplementedError': |
|---|
| 342 | return True |
|---|
| 343 | except StopIteration: |
|---|
| 344 | pass |
|---|
| 345 | if pass_is_abstract and isinstance(child_node, Pass): |
|---|
| 346 | return True |
|---|
| 347 | return False |
|---|
| 348 | # empty function is the same as function with a single "pass" statement |
|---|
| 349 | if pass_is_abstract: |
|---|
| 350 | return True |
|---|
| 351 | |
|---|
| 352 | def is_generator(self): |
|---|
| 353 | """return true if this is a generator function""" |
|---|
| 354 | try: |
|---|
| 355 | return self.nodes_of_class(Yield, skip_klass=Function).next() |
|---|
| 356 | except StopIteration: |
|---|
| 357 | return False |
|---|
| 358 | |
|---|
| 359 | def format_args(self): |
|---|
| 360 | """return arguments formatted as string""" |
|---|
| 361 | if self.argnames is None: # information is missing |
|---|
| 362 | return '' |
|---|
| 363 | result = [] |
|---|
| 364 | args, kwargs, last, default_idx = self._pos_information() |
|---|
| 365 | for i in range(len(self.argnames)): |
|---|
| 366 | name = self.argnames[i] |
|---|
| 367 | if type(name) is type(()): |
|---|
| 368 | name = '(%s)' % ','.join(name) |
|---|
| 369 | if i == last and kwargs: |
|---|
| 370 | name = '**%s' % name |
|---|
| 371 | elif args and i == last or (kwargs and i == last - 1): |
|---|
| 372 | name = '*%s' % name |
|---|
| 373 | elif i >= default_idx: |
|---|
| 374 | default_str = self.defaults[i - default_idx].as_string() |
|---|
| 375 | name = '%s=%s' % (name, default_str) |
|---|
| 376 | result.append(name) |
|---|
| 377 | return ', '.join(result) |
|---|
| 378 | |
|---|
| 379 | def default_value(self, argname): |
|---|
| 380 | """return the default value for an argument |
|---|
| 381 | |
|---|
| 382 | :raise `NoDefault`: if there is no default value defined |
|---|
| 383 | """ |
|---|
| 384 | if self.argnames is None: # information is missing |
|---|
| 385 | raise NoDefault() |
|---|
| 386 | args, kwargs, last, defaultidx = self._pos_information() |
|---|
| 387 | try: |
|---|
| 388 | i = self.argnames.index(argname) |
|---|
| 389 | except ValueError: |
|---|
| 390 | raise NoDefault() # XXX |
|---|
| 391 | if i >= defaultidx and (i - defaultidx) < len(self.defaults): |
|---|
| 392 | return self.defaults[i - defaultidx] |
|---|
| 393 | raise NoDefault() |
|---|
| 394 | |
|---|
| 395 | def mularg_class(self, argname): |
|---|
| 396 | """if the given argument is a * or ** argument, return respectivly |
|---|
| 397 | a Tuple or Dict instance, else return None |
|---|
| 398 | """ |
|---|
| 399 | args, kwargs, last, defaultidx = self._pos_information() |
|---|
| 400 | try: |
|---|
| 401 | i = self.argnames.index(argname) |
|---|
| 402 | except ValueError: |
|---|
| 403 | return None # XXX |
|---|
| 404 | if i == last and kwargs: |
|---|
| 405 | valnode = Dict([]) |
|---|
| 406 | valnode.parent = self |
|---|
| 407 | return valnode |
|---|
| 408 | if args and (i == last or (kwargs and i == last - 1)): |
|---|
| 409 | valnode = Tuple([]) |
|---|
| 410 | valnode.parent = self |
|---|
| 411 | return valnode |
|---|
| 412 | return None |
|---|
| 413 | |
|---|
| 414 | def _pos_information(self): |
|---|
| 415 | """return a 4-uple with positional information about arguments: |
|---|
| 416 | (true if * is used, |
|---|
| 417 | true if ** is used, |
|---|
| 418 | index of the last argument, |
|---|
| 419 | index of the first argument having a default value) |
|---|
| 420 | """ |
|---|
| 421 | args = self.flags & 4 |
|---|
| 422 | kwargs = self.flags & 8 |
|---|
| 423 | last = len(self.argnames) - 1 |
|---|
| 424 | defaultidx = len(self.argnames) - (len(self.defaults) + |
|---|
| 425 | (args and 1 or 0) + |
|---|
| 426 | (kwargs and 1 or 0)) |
|---|
| 427 | return args, kwargs, last, defaultidx |
|---|
| 428 | |
|---|
| 429 | extend_class(Function, FunctionNG) |
|---|
| 430 | |
|---|
| 431 | # lambda nodes may also need some of the function members |
|---|
| 432 | Lambda._pos_information = FunctionNG._pos_information.im_func |
|---|
| 433 | Lambda.format_args = FunctionNG.format_args.im_func |
|---|
| 434 | Lambda.default_value = FunctionNG.default_value.im_func |
|---|
| 435 | Lambda.mularg_class = FunctionNG.mularg_class.im_func |
|---|
| 436 | Lambda.type = 'function' |
|---|
| 437 | Lambda.pytype = FunctionNG.pytype.im_func |
|---|
| 438 | |
|---|
| 439 | # Class ###################################################################### |
|---|
| 440 | |
|---|
| 441 | def _class_type(klass): |
|---|
| 442 | """return a Class node type to differ metaclass, interface and exception |
|---|
| 443 | from 'regular' classes |
|---|
| 444 | """ |
|---|
| 445 | if klass._type is not None: |
|---|
| 446 | return klass._type |
|---|
| 447 | if klass.name == 'type': |
|---|
| 448 | klass._type = 'metaclass' |
|---|
| 449 | elif klass.name.endswith('Interface'): |
|---|
| 450 | klass._type = 'interface' |
|---|
| 451 | elif klass.name.endswith('Exception'): |
|---|
| 452 | klass._type = 'exception' |
|---|
| 453 | else: |
|---|
| 454 | for base in klass.ancestors(recurs=False): |
|---|
| 455 | if base.type != 'class': |
|---|
| 456 | klass._type = base.type |
|---|
| 457 | break |
|---|
| 458 | if klass._type is None: |
|---|
| 459 | klass._type = 'class' |
|---|
| 460 | return klass._type |
|---|
| 461 | |
|---|
| 462 | def _iface_hdlr(iface_node): |
|---|
| 463 | """a handler function used by interfaces to handle suspicious |
|---|
| 464 | interface nodes |
|---|
| 465 | """ |
|---|
| 466 | return True |
|---|
| 467 | |
|---|
| 468 | class ClassNG(object): |
|---|
| 469 | """/!\ this class should not be used directly /!\ it's |
|---|
| 470 | only used as a methods and attribute container, and update the |
|---|
| 471 | original class from the compiler.ast module using its dictionnary |
|---|
| 472 | (see below the class definition) |
|---|
| 473 | """ |
|---|
| 474 | |
|---|
| 475 | _type = None |
|---|
| 476 | type = property(_class_type, |
|---|
| 477 | doc="class'type, possible values are 'class |
|---|