| 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 | """Python Abstract Syntax Tree New Generation |
|---|
| 14 | |
|---|
| 15 | The aim of this module is to provide a common base representation of |
|---|
| 16 | python source code for projects such as pychecker, pyreverse, |
|---|
| 17 | pylint... Well, actually the development of this library is essentialy |
|---|
| 18 | governed by pylint's needs. |
|---|
| 19 | |
|---|
| 20 | It extends class defined in the compiler.ast [1] module with some |
|---|
| 21 | additional methods and attributes. Instance attributes are added by a |
|---|
| 22 | builder object, which can either generate extended ast (let's call |
|---|
| 23 | them astng ;) by visiting an existant ast tree or by inspecting living |
|---|
| 24 | object. Methods are added by monkey patching ast classes. |
|---|
| 25 | |
|---|
| 26 | Main modules are: |
|---|
| 27 | |
|---|
| 28 | * nodes and scoped_nodes for more information about methods and |
|---|
| 29 | attributes added to different node classes |
|---|
| 30 | |
|---|
| 31 | * the manager contains a high level object to get astng trees from |
|---|
| 32 | source files and living objects. It maintains a cache of previously |
|---|
| 33 | constructed tree for quick access |
|---|
| 34 | |
|---|
| 35 | * builder contains the class responsible to build astng trees |
|---|
| 36 | |
|---|
| 37 | |
|---|
| 38 | :author: Sylvain Thenault |
|---|
| 39 | :copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 40 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 41 | :copyright: 2003-2007 Sylvain Thenault |
|---|
| 42 | :contact: mailto:thenault@gmail.com |
|---|
| 43 | """ |
|---|
| 44 | from __future__ import generators |
|---|
| 45 | |
|---|
| 46 | __doctype__ = "restructuredtext en" |
|---|
| 47 | |
|---|
| 48 | from logilab.common.compat import chain, imap |
|---|
| 49 | |
|---|
| 50 | # WARNING: internal imports order matters ! |
|---|
| 51 | |
|---|
| 52 | from logilab.astng._exceptions import * |
|---|
| 53 | |
|---|
| 54 | |
|---|
| 55 | class InferenceContext(object): |
|---|
| 56 | __slots__ = ('startingfrom', 'path', 'lookupname', 'callcontext', 'boundnode') |
|---|
| 57 | |
|---|
| 58 | def __init__(self, node=None, path=None): |
|---|
| 59 | self.startingfrom = node # XXX useful ? |
|---|
| 60 | if path is None: |
|---|
| 61 | self.path = [] |
|---|
| 62 | else: |
|---|
| 63 | self.path = path |
|---|
| 64 | self.lookupname = None |
|---|
| 65 | self.callcontext = None |
|---|
| 66 | self.boundnode = None |
|---|
| 67 | |
|---|
| 68 | def push(self, node): |
|---|
| 69 | name = self.lookupname |
|---|
| 70 | if (node, name) in self.path: |
|---|
| 71 | raise StopIteration() |
|---|
| 72 | self.path.append( (node, name) ) |
|---|
| 73 | |
|---|
| 74 | def pop(self): |
|---|
| 75 | return self.path.pop() |
|---|
| 76 | |
|---|
| 77 | def clone(self): |
|---|
| 78 | # XXX copy lookupname/callcontext ? |
|---|
| 79 | clone = InferenceContext(self.startingfrom, self.path) |
|---|
| 80 | clone.callcontext = self.callcontext |
|---|
| 81 | clone.boundnode = self.boundnode |
|---|
| 82 | return clone |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | def unpack_infer(stmt, context=None): |
|---|
| 86 | """return an iterator on nodes infered by the given statement |
|---|
| 87 | if the infered value is a list or a tuple, recurse on it to |
|---|
| 88 | get values infered by its content |
|---|
| 89 | """ |
|---|
| 90 | if isinstance(stmt, (List, Tuple)): |
|---|
| 91 | # XXX loosing context |
|---|
| 92 | return chain(*imap(unpack_infer, stmt.nodes)) |
|---|
| 93 | infered = stmt.infer(context).next() |
|---|
| 94 | if infered is stmt: |
|---|
| 95 | return iter( (stmt,) ) |
|---|
| 96 | return chain(*imap(unpack_infer, stmt.infer(context))) |
|---|
| 97 | |
|---|
| 98 | def copy_context(context): |
|---|
| 99 | if context is not None: |
|---|
| 100 | return context.clone() |
|---|
| 101 | else: |
|---|
| 102 | return InferenceContext() |
|---|
| 103 | |
|---|
| 104 | def _infer_stmts(stmts, context, frame=None): |
|---|
| 105 | """return an iterator on statements infered by each statement in <stmts> |
|---|
| 106 | """ |
|---|
| 107 | stmt = None |
|---|
| 108 | infered = False |
|---|
| 109 | if context is not None: |
|---|
| 110 | name = context.lookupname |
|---|
| 111 | context = context.clone() |
|---|
| 112 | else: |
|---|
| 113 | name = None |
|---|
| 114 | context = InferenceContext() |
|---|
| 115 | for stmt in stmts: |
|---|
| 116 | if stmt is YES: |
|---|
| 117 | yield stmt |
|---|
| 118 | infered = True |
|---|
| 119 | continue |
|---|
| 120 | context.lookupname = stmt._infer_name(frame, name) |
|---|
| 121 | try: |
|---|
| 122 | for infered in stmt.infer(context): |
|---|
| 123 | yield infered |
|---|
| 124 | infered = True |
|---|
| 125 | except UnresolvableName: |
|---|
| 126 | continue |
|---|
| 127 | except InferenceError: |
|---|
| 128 | yield YES |
|---|
| 129 | infered = True |
|---|
| 130 | if not infered: |
|---|
| 131 | raise InferenceError(str(stmt)) |
|---|
| 132 | |
|---|
| 133 | # special inference objects ################################################### |
|---|
| 134 | |
|---|
| 135 | class Yes(object): |
|---|
| 136 | """a yes object""" |
|---|
| 137 | def __repr__(self): |
|---|
| 138 | return 'YES' |
|---|
| 139 | def __getattribute__(self, name): |
|---|
| 140 | return self |
|---|
| 141 | def __call__(self, *args, **kwargs): |
|---|
| 142 | return self |
|---|
| 143 | YES = Yes() |
|---|
| 144 | |
|---|
| 145 | class Proxy: |
|---|
| 146 | """a simple proxy object""" |
|---|
| 147 | def __init__(self, proxied): |
|---|
| 148 | self._proxied = proxied |
|---|
| 149 | |
|---|
| 150 | def __getattr__(self, name): |
|---|
| 151 | return getattr(self._proxied, name) |
|---|
| 152 | |
|---|
| 153 | def infer(self, context=None): |
|---|
| 154 | yield self |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | class InstanceMethod(Proxy): |
|---|
| 158 | """a special node representing a function bound to an instance""" |
|---|
| 159 | def __repr__(self): |
|---|
| 160 | instance = self._proxied.parent.frame() |
|---|
| 161 | return 'Bound method %s of %s.%s' % (self._proxied.name, |
|---|
| 162 | instance.root().name, |
|---|
| 163 | instance.name) |
|---|
| 164 | __str__ = __repr__ |
|---|
| 165 | |
|---|
| 166 | def is_bound(self): |
|---|
| 167 | return True |
|---|
| 168 | |
|---|
| 169 | |
|---|
| 170 | class Instance(Proxy): |
|---|
| 171 | """a special node representing a class instance""" |
|---|
| 172 | def getattr(self, name, context=None, lookupclass=True): |
|---|
| 173 | try: |
|---|
| 174 | return self._proxied.instance_attr(name, context) |
|---|
| 175 | except NotFoundError: |
|---|
| 176 | if name == '__class__': |
|---|
| 177 | return [self._proxied] |
|---|
| 178 | if name == '__name__': |
|---|
| 179 | # access to __name__ gives undefined member on class |
|---|
| 180 | # instances but not on class objects |
|---|
| 181 | raise NotFoundError(name) |
|---|
| 182 | if lookupclass: |
|---|
| 183 | return self._proxied.getattr(name, context) |
|---|
| 184 | raise NotFoundError(name) |
|---|
| 185 | |
|---|
| 186 | def igetattr(self, name, context=None): |
|---|
| 187 | """infered getattr""" |
|---|
| 188 | try: |
|---|
| 189 | # XXX frame should be self._proxied, or not ? |
|---|
| 190 | return _infer_stmts( |
|---|
| 191 | self._wrap_attr(self.getattr(name, context, lookupclass=False)), |
|---|
| 192 | context, frame=self) |
|---|
| 193 | except NotFoundError: |
|---|
| 194 | try: |
|---|
| 195 | # fallback to class'igetattr since it has some logic to handle |
|---|
| 196 | # descriptors |
|---|
| 197 | return self._wrap_attr(self._proxied.igetattr(name, context)) |
|---|
| 198 | except NotFoundError: |
|---|
| 199 | raise InferenceError(name) |
|---|
| 200 | |
|---|
| 201 | def _wrap_attr(self, attrs): |
|---|
| 202 | """wrap bound methods of attrs in a InstanceMethod proxies""" |
|---|
| 203 | # Guess which attrs are used in inference. |
|---|
| 204 | def wrap(attr): |
|---|
| 205 | if isinstance(attr, Function) and attr.type == 'method': |
|---|
| 206 | return InstanceMethod(attr) |
|---|
| 207 | else: |
|---|
| 208 | return attr |
|---|
| 209 | return imap(wrap, attrs) |
|---|
| 210 | |
|---|
| 211 | def infer_call_result(self, caller, context=None): |
|---|
| 212 | """infer what's a class instance is returning when called""" |
|---|
| 213 | infered = False |
|---|
| 214 | for node in self._proxied.igetattr('__call__', context): |
|---|
| 215 | for res in node.infer_call_result(caller, context): |
|---|
| 216 | infered = True |
|---|
| 217 | yield res |
|---|
| 218 | if not infered: |
|---|
| 219 | raise InferenceError() |
|---|
| 220 | |
|---|
| 221 | def __repr__(self): |
|---|
| 222 | return 'Instance of %s.%s' % (self._proxied.root().name, |
|---|
| 223 | self._proxied.name) |
|---|
| 224 | __str__ = __repr__ |
|---|
| 225 | |
|---|
| 226 | def callable(self): |
|---|
| 227 | try: |
|---|
| 228 | self._proxied.getattr('__call__') |
|---|
| 229 | return True |
|---|
| 230 | except NotFoundError: |
|---|
| 231 | return False |
|---|
| 232 | |
|---|
| 233 | def pytype(self): |
|---|
| 234 | return self._proxied.qname() |
|---|
| 235 | |
|---|
| 236 | class Generator(Proxy): |
|---|
| 237 | """a special node representing a generator""" |
|---|
| 238 | def callable(self): |
|---|
| 239 | return True |
|---|
| 240 | |
|---|
| 241 | def pytype(self): |
|---|
| 242 | return '__builtin__.generator' |
|---|
| 243 | |
|---|
| 244 | # imports ##################################################################### |
|---|
| 245 | |
|---|
| 246 | from logilab.astng.manager import ASTNGManager, Project, Package |
|---|
| 247 | MANAGER = ASTNGManager() |
|---|
| 248 | |
|---|
| 249 | from logilab.astng.nodes import * |
|---|
| 250 | from logilab.astng import nodes |
|---|
| 251 | from logilab.astng.scoped_nodes import * |
|---|
| 252 | from logilab.astng import inference |
|---|
| 253 | from logilab.astng import lookup |
|---|
| 254 | lookup._decorate(nodes) |
|---|
| 255 | |
|---|
| 256 | List._proxied = MANAGER.astng_from_class(list) |
|---|
| 257 | List.__bases__ += (inference.Instance,) |
|---|
| 258 | List.pytype = lambda x: '__builtin__.list' |
|---|
| 259 | |
|---|
| 260 | Tuple._proxied = MANAGER.astng_from_class(tuple) |
|---|
| 261 | Tuple.__bases__ += (inference.Instance,) |
|---|
| 262 | Tuple.pytype = lambda x: '__builtin__.tuple' |
|---|
| 263 | |
|---|
| 264 | Dict.__bases__ += (inference.Instance,) |
|---|
| 265 | Dict._proxied = MANAGER.astng_from_class(dict) |
|---|
| 266 | Dict.pytype = lambda x: '__builtin__.dict' |
|---|
| 267 | |
|---|
| 268 | builtin_astng = Dict._proxied.root() |
|---|
| 269 | |
|---|
| 270 | Const.__bases__ += (inference.Instance,) |
|---|
| 271 | Const._proxied = None |
|---|
| 272 | def Const___getattr__(self, name): |
|---|
| 273 | if self.value is None: |
|---|
| 274 | raise AttributeError(name) |
|---|
| 275 | if self._proxied is None: |
|---|
| 276 | self._proxied = MANAGER.astng_from_class(self.value.__class__) |
|---|
| 277 | return getattr(self._proxied, name) |
|---|
| 278 | Const.__getattr__ = Const___getattr__ |
|---|
| 279 | def Const_getattr(self, name, context=None, lookupclass=None): |
|---|
| 280 | if self.value is None: |
|---|
| 281 | raise NotFoundError(name) |
|---|
| 282 | if self._proxied is None: |
|---|
| 283 | self._proxied = MANAGER.astng_from_class(self.value.__class__) |
|---|
| 284 | return self._proxied.getattr(name, context) |
|---|
| 285 | Const.getattr = Const_getattr |
|---|
| 286 | Const.has_dynamic_getattr = lambda x: False |
|---|
| 287 | |
|---|
| 288 | def Const_pytype(self): |
|---|
| 289 | if self.value is None: |
|---|
| 290 | return '__builtin__.NoneType' |
|---|
| 291 | if self._proxied is None: |
|---|
| 292 | self._proxied = MANAGER.astng_from_class(self.value.__class__) |
|---|
| 293 | return self._proxied.qname() |
|---|
| 294 | Const.pytype = Const_pytype |
|---|