Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: third_party/logilab/astng/bases.py

Issue 739393004: Revert "Revert "pylint: upgrade to 1.3.1"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/logilab/astng/as_string.py ('k') | third_party/logilab/astng/builder.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « third_party/logilab/astng/as_string.py ('k') | third_party/logilab/astng/builder.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698