OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
| 3 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 4 # copyright 2003-2010 Sylvain Thenault, all rights reserved. |
| 5 # contact mailto:thenault@gmail.com |
| 6 # |
| 7 # This file is part of logilab-astng. |
| 8 # |
| 9 # logilab-astng is free software: you can redistribute it and/or modify it |
| 10 # under the terms of the GNU Lesser General Public License as published by the |
| 11 # Free Software Foundation, either version 2.1 of the License, or (at your |
| 12 # option) any later version. |
| 13 # |
| 14 # logilab-astng is distributed in the hope that it will be useful, but |
| 15 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 16 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
| 17 # for more details. |
| 18 # |
| 19 # You should have received a copy of the GNU Lesser General Public License along |
| 20 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. |
| 21 """This module contains base classes and functions for the nodes and some |
| 22 inference utils. |
| 23 """ |
| 24 |
| 25 __docformat__ = "restructuredtext en" |
| 26 |
| 27 from contextlib import contextmanager |
| 28 |
| 29 from logilab.common.compat import builtins |
| 30 |
| 31 from logilab.astng import BUILTINS_MODULE |
| 32 from logilab.astng.exceptions import InferenceError, ASTNGError, \ |
| 33 NotFoundError, UnresolvableName |
| 34 from logilab.astng.as_string import as_string |
| 35 |
| 36 BUILTINS_NAME = builtins.__name__ |
| 37 |
| 38 class Proxy(object): |
| 39 """a simple proxy object""" |
| 40 _proxied = None |
| 41 |
| 42 def __init__(self, proxied=None): |
| 43 if proxied is not None: |
| 44 self._proxied = proxied |
| 45 |
| 46 def __getattr__(self, name): |
| 47 if name == '_proxied': |
| 48 return getattr(self.__class__, '_proxied') |
| 49 if name in self.__dict__: |
| 50 return self.__dict__[name] |
| 51 return getattr(self._proxied, name) |
| 52 |
| 53 def infer(self, context=None): |
| 54 yield self |
| 55 |
| 56 |
| 57 # Inference ################################################################## |
| 58 |
| 59 class InferenceContext(object): |
| 60 __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode') |
| 61 |
| 62 def __init__(self, path=None): |
| 63 if path is None: |
| 64 self.path = set() |
| 65 else: |
| 66 self.path = path |
| 67 self.lookupname = None |
| 68 self.callcontext = None |
| 69 self.boundnode = None |
| 70 |
| 71 def push(self, node): |
| 72 name = self.lookupname |
| 73 if (node, name) in self.path: |
| 74 raise StopIteration() |
| 75 self.path.add( (node, name) ) |
| 76 |
| 77 def clone(self): |
| 78 # XXX copy lookupname/callcontext ? |
| 79 clone = InferenceContext(self.path) |
| 80 clone.callcontext = self.callcontext |
| 81 clone.boundnode = self.boundnode |
| 82 return clone |
| 83 |
| 84 @contextmanager |
| 85 def restore_path(self): |
| 86 path = set(self.path) |
| 87 yield |
| 88 self.path = path |
| 89 |
| 90 def copy_context(context): |
| 91 if context is not None: |
| 92 return context.clone() |
| 93 else: |
| 94 return InferenceContext() |
| 95 |
| 96 |
| 97 def _infer_stmts(stmts, context, frame=None): |
| 98 """return an iterator on statements inferred by each statement in <stmts> |
| 99 """ |
| 100 stmt = None |
| 101 infered = False |
| 102 if context is not None: |
| 103 name = context.lookupname |
| 104 context = context.clone() |
| 105 else: |
| 106 name = None |
| 107 context = InferenceContext() |
| 108 for stmt in stmts: |
| 109 if stmt is YES: |
| 110 yield stmt |
| 111 infered = True |
| 112 continue |
| 113 context.lookupname = stmt._infer_name(frame, name) |
| 114 try: |
| 115 for infered in stmt.infer(context): |
| 116 yield infered |
| 117 infered = True |
| 118 except UnresolvableName: |
| 119 continue |
| 120 except InferenceError: |
| 121 yield YES |
| 122 infered = True |
| 123 if not infered: |
| 124 raise InferenceError(str(stmt)) |
| 125 |
| 126 |
| 127 # special inference objects (e.g. may be returned as nodes by .infer()) ####### |
| 128 |
| 129 class _Yes(object): |
| 130 """a yes object""" |
| 131 def __repr__(self): |
| 132 return 'YES' |
| 133 def __getattribute__(self, name): |
| 134 if name.startswith('__') and name.endswith('__'): |
| 135 # to avoid inspection pb |
| 136 return super(_Yes, self).__getattribute__(name) |
| 137 return self |
| 138 def __call__(self, *args, **kwargs): |
| 139 return self |
| 140 |
| 141 |
| 142 YES = _Yes() |
| 143 |
| 144 |
| 145 class Instance(Proxy): |
| 146 """a special node representing a class instance""" |
| 147 def getattr(self, name, context=None, lookupclass=True): |
| 148 try: |
| 149 values = self._proxied.instance_attr(name, context) |
| 150 except NotFoundError: |
| 151 if name == '__class__': |
| 152 return [self._proxied] |
| 153 if lookupclass: |
| 154 # class attributes not available through the instance |
| 155 # unless they are explicitly defined |
| 156 if name in ('__name__', '__bases__', '__mro__', '__subclasses__'
): |
| 157 return self._proxied.local_attr(name) |
| 158 return self._proxied.getattr(name, context) |
| 159 raise NotFoundError(name) |
| 160 # since we've no context information, return matching class members as |
| 161 # well |
| 162 if lookupclass: |
| 163 try: |
| 164 return values + self._proxied.getattr(name, context) |
| 165 except NotFoundError: |
| 166 pass |
| 167 return values |
| 168 |
| 169 def igetattr(self, name, context=None): |
| 170 """inferred getattr""" |
| 171 try: |
| 172 # XXX frame should be self._proxied, or not ? |
| 173 get_attr = self.getattr(name, context, lookupclass=False) |
| 174 return _infer_stmts(self._wrap_attr(get_attr, context), context, |
| 175 frame=self) |
| 176 except NotFoundError: |
| 177 try: |
| 178 # fallback to class'igetattr since it has some logic to handle |
| 179 # descriptors |
| 180 return self._wrap_attr(self._proxied.igetattr(name, context), |
| 181 context) |
| 182 except NotFoundError: |
| 183 raise InferenceError(name) |
| 184 |
| 185 def _wrap_attr(self, attrs, context=None): |
| 186 """wrap bound methods of attrs in a InstanceMethod proxies""" |
| 187 for attr in attrs: |
| 188 if isinstance(attr, UnboundMethod): |
| 189 if BUILTINS_NAME + '.property' in attr.decoratornames(): |
| 190 for infered in attr.infer_call_result(self, context): |
| 191 yield infered |
| 192 else: |
| 193 yield BoundMethod(attr, self) |
| 194 else: |
| 195 yield attr |
| 196 |
| 197 def infer_call_result(self, caller, context=None): |
| 198 """infer what a class instance is returning when called""" |
| 199 infered = False |
| 200 for node in self._proxied.igetattr('__call__', context): |
| 201 for res in node.infer_call_result(caller, context): |
| 202 infered = True |
| 203 yield res |
| 204 if not infered: |
| 205 raise InferenceError() |
| 206 |
| 207 def __repr__(self): |
| 208 return '<Instance of %s.%s at 0x%s>' % (self._proxied.root().name, |
| 209 self._proxied.name, |
| 210 id(self)) |
| 211 def __str__(self): |
| 212 return 'Instance of %s.%s' % (self._proxied.root().name, |
| 213 self._proxied.name) |
| 214 |
| 215 def callable(self): |
| 216 try: |
| 217 self._proxied.getattr('__call__') |
| 218 return True |
| 219 except NotFoundError: |
| 220 return False |
| 221 |
| 222 def pytype(self): |
| 223 return self._proxied.qname() |
| 224 |
| 225 def display_type(self): |
| 226 return 'Instance of' |
| 227 |
| 228 |
| 229 class UnboundMethod(Proxy): |
| 230 """a special node representing a method not bound to an instance""" |
| 231 def __repr__(self): |
| 232 frame = self._proxied.parent.frame() |
| 233 return '<%s %s of %s at 0x%s' % (self.__class__.__name__, |
| 234 self._proxied.name, |
| 235 frame.qname(), id(self)) |
| 236 |
| 237 def is_bound(self): |
| 238 return False |
| 239 |
| 240 def getattr(self, name, context=None): |
| 241 if name == 'im_func': |
| 242 return [self._proxied] |
| 243 return super(UnboundMethod, self).getattr(name, context) |
| 244 |
| 245 def igetattr(self, name, context=None): |
| 246 if name == 'im_func': |
| 247 return iter((self._proxied,)) |
| 248 return super(UnboundMethod, self).igetattr(name, context) |
| 249 |
| 250 def infer_call_result(self, caller, context): |
| 251 # If we're unbound method __new__ of builtin object, the result is an |
| 252 # instance of the class given as first argument. |
| 253 if (self._proxied.name == '__new__' and |
| 254 self._proxied.parent.frame().qname() == '%s.object' % BUILTINS_M
ODULE): |
| 255 return (x is YES and x or Instance(x) for x in caller.args[0].infer(
)) |
| 256 return self._proxied.infer_call_result(caller, context) |
| 257 |
| 258 |
| 259 class BoundMethod(UnboundMethod): |
| 260 """a special node representing a method bound to an instance""" |
| 261 def __init__(self, proxy, bound): |
| 262 UnboundMethod.__init__(self, proxy) |
| 263 self.bound = bound |
| 264 |
| 265 def is_bound(self): |
| 266 return True |
| 267 |
| 268 def infer_call_result(self, caller, context): |
| 269 context = context.clone() |
| 270 context.boundnode = self.bound |
| 271 return self._proxied.infer_call_result(caller, context) |
| 272 |
| 273 |
| 274 class Generator(Instance): |
| 275 """a special node representing a generator""" |
| 276 def callable(self): |
| 277 return True |
| 278 |
| 279 def pytype(self): |
| 280 return '%s.generator' % BUILTINS_MODULE |
| 281 |
| 282 def display_type(self): |
| 283 return 'Generator' |
| 284 |
| 285 def __repr__(self): |
| 286 return '<Generator(%s) l.%s at 0x%s>' % (self._proxied.name, self.lineno
, id(self)) |
| 287 |
| 288 def __str__(self): |
| 289 return 'Generator(%s)' % (self._proxied.name) |
| 290 |
| 291 |
| 292 # decorators ################################################################## |
| 293 |
| 294 def path_wrapper(func): |
| 295 """return the given infer function wrapped to handle the path""" |
| 296 def wrapped(node, context=None, _func=func, **kwargs): |
| 297 """wrapper function handling context""" |
| 298 if context is None: |
| 299 context = InferenceContext() |
| 300 context.push(node) |
| 301 yielded = set() |
| 302 for res in _func(node, context, **kwargs): |
| 303 # unproxy only true instance, not const, tuple, dict... |
| 304 if res.__class__ is Instance: |
| 305 ares = res._proxied |
| 306 else: |
| 307 ares = res |
| 308 if not ares in yielded: |
| 309 yield res |
| 310 yielded.add(ares) |
| 311 return wrapped |
| 312 |
| 313 def yes_if_nothing_infered(func): |
| 314 def wrapper(*args, **kwargs): |
| 315 infered = False |
| 316 for node in func(*args, **kwargs): |
| 317 infered = True |
| 318 yield node |
| 319 if not infered: |
| 320 yield YES |
| 321 return wrapper |
| 322 |
| 323 def raise_if_nothing_infered(func): |
| 324 def wrapper(*args, **kwargs): |
| 325 infered = False |
| 326 for node in func(*args, **kwargs): |
| 327 infered = True |
| 328 yield node |
| 329 if not infered: |
| 330 raise InferenceError() |
| 331 return wrapper |
| 332 |
| 333 |
| 334 # Node ###################################################################### |
| 335 |
| 336 class NodeNG(object): |
| 337 """Base Class for all ASTNG node classes. |
| 338 |
| 339 It represents a node of the new abstract syntax tree. |
| 340 """ |
| 341 is_statement = False |
| 342 optional_assign = False # True for For (and for Comprehension if py <3.0) |
| 343 is_function = False # True for Function nodes |
| 344 # attributes below are set by the builder module or by raw factories |
| 345 lineno = None |
| 346 fromlineno = None |
| 347 tolineno = None |
| 348 col_offset = None |
| 349 # parent node in the tree |
| 350 parent = None |
| 351 # attributes containing child node(s) redefined in most concrete classes: |
| 352 _astng_fields = () |
| 353 |
| 354 def _repr_name(self): |
| 355 """return self.name or self.attrname or '' for nice representation""" |
| 356 return getattr(self, 'name', getattr(self, 'attrname', '')) |
| 357 |
| 358 def __str__(self): |
| 359 return '%s(%s)' % (self.__class__.__name__, self._repr_name()) |
| 360 |
| 361 def __repr__(self): |
| 362 return '<%s(%s) l.%s [%s] at Ox%x>' % (self.__class__.__name__, |
| 363 self._repr_name(), |
| 364 self.fromlineno, |
| 365 self.root().name, |
| 366 id(self)) |
| 367 |
| 368 |
| 369 def accept(self, visitor): |
| 370 klass = self.__class__.__name__ |
| 371 func = getattr(visitor, "visit_" + self.__class__.__name__.lower()) |
| 372 return func(self) |
| 373 |
| 374 def get_children(self): |
| 375 for field in self._astng_fields: |
| 376 attr = getattr(self, field) |
| 377 if attr is None: |
| 378 continue |
| 379 if isinstance(attr, (list, tuple)): |
| 380 for elt in attr: |
| 381 yield elt |
| 382 else: |
| 383 yield attr |
| 384 |
| 385 def last_child(self): |
| 386 """an optimized version of list(get_children())[-1]""" |
| 387 for field in self._astng_fields[::-1]: |
| 388 attr = getattr(self, field) |
| 389 if not attr: # None or empty listy / tuple |
| 390 continue |
| 391 if isinstance(attr, (list, tuple)): |
| 392 return attr[-1] |
| 393 else: |
| 394 return attr |
| 395 return None |
| 396 |
| 397 def parent_of(self, node): |
| 398 """return true if i'm a parent of the given node""" |
| 399 parent = node.parent |
| 400 while parent is not None: |
| 401 if self is parent: |
| 402 return True |
| 403 parent = parent.parent |
| 404 return False |
| 405 |
| 406 def statement(self): |
| 407 """return the first parent node marked as statement node""" |
| 408 if self.is_statement: |
| 409 return self |
| 410 return self.parent.statement() |
| 411 |
| 412 def frame(self): |
| 413 """return the first parent frame node (i.e. Module, Function or Class) |
| 414 """ |
| 415 return self.parent.frame() |
| 416 |
| 417 def scope(self): |
| 418 """return the first node defining a new scope (i.e. Module, Function, |
| 419 Class, Lambda but also GenExpr) |
| 420 """ |
| 421 return self.parent.scope() |
| 422 |
| 423 def root(self): |
| 424 """return the root node of the tree, (i.e. a Module)""" |
| 425 if self.parent: |
| 426 return self.parent.root() |
| 427 return self |
| 428 |
| 429 def child_sequence(self, child): |
| 430 """search for the right sequence where the child lies in""" |
| 431 for field in self._astng_fields: |
| 432 node_or_sequence = getattr(self, field) |
| 433 if node_or_sequence is child: |
| 434 return [node_or_sequence] |
| 435 # /!\ compiler.ast Nodes have an __iter__ walking over child nodes |
| 436 if isinstance(node_or_sequence, (tuple, list)) and child in node_or_
sequence: |
| 437 return node_or_sequence |
| 438 else: |
| 439 msg = 'Could not found %s in %s\'s children' |
| 440 raise ASTNGError(msg % (repr(child), repr(self))) |
| 441 |
| 442 def locate_child(self, child): |
| 443 """return a 2-uple (child attribute name, sequence or node)""" |
| 444 for field in self._astng_fields: |
| 445 node_or_sequence = getattr(self, field) |
| 446 # /!\ compiler.ast Nodes have an __iter__ walking over child nodes |
| 447 if child is node_or_sequence: |
| 448 return field, child |
| 449 if isinstance(node_or_sequence, (tuple, list)) and child in node_or_
sequence: |
| 450 return field, node_or_sequence |
| 451 msg = 'Could not found %s in %s\'s children' |
| 452 raise ASTNGError(msg % (repr(child), repr(self))) |
| 453 # FIXME : should we merge child_sequence and locate_child ? locate_child |
| 454 # is only used in are_exclusive, child_sequence one time in pylint. |
| 455 |
| 456 def next_sibling(self): |
| 457 """return the next sibling statement""" |
| 458 return self.parent.next_sibling() |
| 459 |
| 460 def previous_sibling(self): |
| 461 """return the previous sibling statement""" |
| 462 return self.parent.previous_sibling() |
| 463 |
| 464 def nearest(self, nodes): |
| 465 """return the node which is the nearest before this one in the |
| 466 given list of nodes |
| 467 """ |
| 468 myroot = self.root() |
| 469 mylineno = self.fromlineno |
| 470 nearest = None, 0 |
| 471 for node in nodes: |
| 472 assert node.root() is myroot, \ |
| 473 'nodes %s and %s are not from the same module' % (self, node) |
| 474 lineno = node.fromlineno |
| 475 if node.fromlineno > mylineno: |
| 476 break |
| 477 if lineno > nearest[1]: |
| 478 nearest = node, lineno |
| 479 # FIXME: raise an exception if nearest is None ? |
| 480 return nearest[0] |
| 481 |
| 482 def set_line_info(self, lastchild): |
| 483 if self.lineno is None: |
| 484 self.fromlineno = self._fixed_source_line() |
| 485 else: |
| 486 self.fromlineno = self.lineno |
| 487 if lastchild is None: |
| 488 self.tolineno = self.fromlineno |
| 489 else: |
| 490 self.tolineno = lastchild.tolineno |
| 491 return |
| 492 # TODO / FIXME: |
| 493 assert self.fromlineno is not None, self |
| 494 assert self.tolineno is not None, self |
| 495 |
| 496 def _fixed_source_line(self): |
| 497 """return the line number where the given node appears |
| 498 |
| 499 we need this method since not all nodes have the lineno attribute |
| 500 correctly set... |
| 501 """ |
| 502 line = self.lineno |
| 503 _node = self |
| 504 try: |
| 505 while line is None: |
| 506 _node = _node.get_children().next() |
| 507 line = _node.lineno |
| 508 except StopIteration: |
| 509 _node = self.parent |
| 510 while _node and line is None: |
| 511 line = _node.lineno |
| 512 _node = _node.parent |
| 513 return line |
| 514 |
| 515 def block_range(self, lineno): |
| 516 """handle block line numbers range for non block opening statements |
| 517 """ |
| 518 return lineno, self.tolineno |
| 519 |
| 520 def set_local(self, name, stmt): |
| 521 """delegate to a scoped parent handling a locals dictionary""" |
| 522 self.parent.set_local(name, stmt) |
| 523 |
| 524 def nodes_of_class(self, klass, skip_klass=None): |
| 525 """return an iterator on nodes which are instance of the given class(es) |
| 526 |
| 527 klass may be a class object or a tuple of class objects |
| 528 """ |
| 529 if isinstance(self, klass): |
| 530 yield self |
| 531 for child_node in self.get_children(): |
| 532 if skip_klass is not None and isinstance(child_node, skip_klass): |
| 533 continue |
| 534 for matching in child_node.nodes_of_class(klass, skip_klass): |
| 535 yield matching |
| 536 |
| 537 def _infer_name(self, frame, name): |
| 538 # overridden for From, Import, Global, TryExcept and Arguments |
| 539 return None |
| 540 |
| 541 def infer(self, context=None): |
| 542 """we don't know how to resolve a statement by default""" |
| 543 # this method is overridden by most concrete classes |
| 544 raise InferenceError(self.__class__.__name__) |
| 545 |
| 546 def infered(self): |
| 547 '''return list of infered values for a more simple inference usage''' |
| 548 return list(self.infer()) |
| 549 |
| 550 def instanciate_class(self): |
| 551 """instanciate a node if it is a Class node, else return self""" |
| 552 return self |
| 553 |
| 554 def has_base(self, node): |
| 555 return False |
| 556 |
| 557 def callable(self): |
| 558 return False |
| 559 |
| 560 def eq(self, value): |
| 561 return False |
| 562 |
| 563 def as_string(self): |
| 564 return as_string(self) |
| 565 |
| 566 def repr_tree(self, ids=False): |
| 567 """print a nice astng tree representation. |
| 568 |
| 569 :param ids: if true, we also print the ids (usefull for debugging)""" |
| 570 result = [] |
| 571 _repr_tree(self, result, ids=ids) |
| 572 return "\n".join(result) |
| 573 |
| 574 |
| 575 class Statement(NodeNG): |
| 576 """Statement node adding a few attributes""" |
| 577 is_statement = True |
| 578 |
| 579 def next_sibling(self): |
| 580 """return the next sibling statement""" |
| 581 stmts = self.parent.child_sequence(self) |
| 582 index = stmts.index(self) |
| 583 try: |
| 584 return stmts[index +1] |
| 585 except IndexError: |
| 586 pass |
| 587 |
| 588 def previous_sibling(self): |
| 589 """return the previous sibling statement""" |
| 590 stmts = self.parent.child_sequence(self) |
| 591 index = stmts.index(self) |
| 592 if index >= 1: |
| 593 return stmts[index -1] |
| 594 |
| 595 INDENT = " " |
| 596 |
| 597 def _repr_tree(node, result, indent='', _done=None, ids=False): |
| 598 """built a tree representation of a node as a list of lines""" |
| 599 if _done is None: |
| 600 _done = set() |
| 601 if not hasattr(node, '_astng_fields'): # not a astng node |
| 602 return |
| 603 if node in _done: |
| 604 result.append( indent + 'loop in tree: %s' % node ) |
| 605 return |
| 606 _done.add(node) |
| 607 node_str = str(node) |
| 608 if ids: |
| 609 node_str += ' . \t%x' % id(node) |
| 610 result.append( indent + node_str ) |
| 611 indent += INDENT |
| 612 for field in node._astng_fields: |
| 613 value = getattr(node, field) |
| 614 if isinstance(value, (list, tuple) ): |
| 615 result.append( indent + field + " = [" ) |
| 616 for child in value: |
| 617 if isinstance(child, (list, tuple) ): |
| 618 # special case for Dict # FIXME |
| 619 _repr_tree(child[0], result, indent, _done, ids) |
| 620 _repr_tree(child[1], result, indent, _done, ids) |
| 621 result.append(indent + ',') |
| 622 else: |
| 623 _repr_tree(child, result, indent, _done, ids) |
| 624 result.append( indent + "]" ) |
| 625 else: |
| 626 result.append( indent + field + " = " ) |
| 627 _repr_tree(value, result, indent, _done, ids) |
| 628 |
| 629 |
OLD | NEW |