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 |