OLD | NEW |
| (Empty) |
1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 # copyright 2003-2010 Sylvain Thenault, all rights reserved. | |
4 # contact mailto:thenault@gmail.com | |
5 # | |
6 # This file is part of logilab-astng. | |
7 # | |
8 # logilab-astng is free software: you can redistribute it and/or modify it | |
9 # under the terms of the GNU Lesser General Public License as published by the | |
10 # Free Software Foundation, either version 2.1 of the License, or (at your | |
11 # option) any later version. | |
12 # | |
13 # logilab-astng is distributed in the hope that it will be useful, but | |
14 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
16 # for more details. | |
17 # | |
18 # You should have received a copy of the GNU Lesser General Public License along | |
19 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. | |
20 """this module contains a set of functions to handle inference on astng trees | |
21 """ | |
22 | |
23 __doctype__ = "restructuredtext en" | |
24 | |
25 from itertools import chain | |
26 import sys | |
27 | |
28 from logilab.astng import nodes | |
29 | |
30 from logilab.astng.manager import ASTNGManager | |
31 from logilab.astng.exceptions import (ASTNGBuildingException, ASTNGError, | |
32 InferenceError, NoDefault, NotFoundError, UnresolvableName) | |
33 from logilab.astng.bases import YES, Instance, InferenceContext, Generator, \ | |
34 _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered | |
35 from logilab.astng.protocols import _arguments_infer_argname | |
36 | |
37 MANAGER = ASTNGManager() | |
38 | |
39 | |
40 class CallContext: | |
41 """when inferring a function call, this class is used to remember values | |
42 given as argument | |
43 """ | |
44 def __init__(self, args, starargs, dstarargs): | |
45 self.args = [] | |
46 self.nargs = {} | |
47 for arg in args: | |
48 if isinstance(arg, nodes.Keyword): | |
49 self.nargs[arg.arg] = arg.value | |
50 else: | |
51 self.args.append(arg) | |
52 self.starargs = starargs | |
53 self.dstarargs = dstarargs | |
54 | |
55 def infer_argument(self, funcnode, name, context): | |
56 """infer a function argument value according to the call context""" | |
57 # 1. search in named keywords | |
58 try: | |
59 return self.nargs[name].infer(context) | |
60 except KeyError: | |
61 # Function.args.args can be None in astng (means that we don't have | |
62 # information on argnames) | |
63 argindex = funcnode.args.find_argname(name)[0] | |
64 if argindex is not None: | |
65 # 2. first argument of instance/class method | |
66 if argindex == 0 and funcnode.type in ('method', 'classmethod'): | |
67 if context.boundnode is not None: | |
68 boundnode = context.boundnode | |
69 else: | |
70 # XXX can do better ? | |
71 boundnode = funcnode.parent.frame() | |
72 if funcnode.type == 'method': | |
73 if not isinstance(boundnode, Instance): | |
74 boundnode = Instance(boundnode) | |
75 return iter((boundnode,)) | |
76 if funcnode.type == 'classmethod': | |
77 return iter((boundnode,)) | |
78 # 2. search arg index | |
79 try: | |
80 return self.args[argindex].infer(context) | |
81 except IndexError: | |
82 pass | |
83 # 3. search in *args (.starargs) | |
84 if self.starargs is not None: | |
85 its = [] | |
86 for infered in self.starargs.infer(context): | |
87 if infered is YES: | |
88 its.append((YES,)) | |
89 continue | |
90 try: | |
91 its.append(infered.getitem(argindex, context).infer(
context)) | |
92 except (InferenceError, AttributeError): | |
93 its.append((YES,)) | |
94 except (IndexError, TypeError): | |
95 continue | |
96 if its: | |
97 return chain(*its) | |
98 # 4. XXX search in **kwargs (.dstarargs) | |
99 if self.dstarargs is not None: | |
100 its = [] | |
101 for infered in self.dstarargs.infer(context): | |
102 if infered is YES: | |
103 its.append((YES,)) | |
104 continue | |
105 try: | |
106 its.append(infered.getitem(name, context).infer(context)) | |
107 except (InferenceError, AttributeError): | |
108 its.append((YES,)) | |
109 except (IndexError, TypeError): | |
110 continue | |
111 if its: | |
112 return chain(*its) | |
113 # 5. */** argument, (Tuple or Dict) | |
114 if name == funcnode.args.vararg: | |
115 return iter((nodes.const_factory(()))) | |
116 if name == funcnode.args.kwarg: | |
117 return iter((nodes.const_factory({}))) | |
118 # 6. return default value if any | |
119 try: | |
120 return funcnode.args.default_value(name).infer(context) | |
121 except NoDefault: | |
122 raise InferenceError(name) | |
123 | |
124 | |
125 # .infer method ############################################################### | |
126 | |
127 | |
128 def infer_end(self, context=None): | |
129 """inference's end for node such as Module, Class, Function, Const... | |
130 """ | |
131 yield self | |
132 nodes.Module.infer = infer_end | |
133 nodes.Class.infer = infer_end | |
134 nodes.Function.infer = infer_end | |
135 nodes.Lambda.infer = infer_end | |
136 nodes.Const.infer = infer_end | |
137 nodes.List.infer = infer_end | |
138 nodes.Tuple.infer = infer_end | |
139 nodes.Dict.infer = infer_end | |
140 | |
141 | |
142 def infer_name(self, context=None): | |
143 """infer a Name: use name lookup rules""" | |
144 frame, stmts = self.lookup(self.name) | |
145 if not stmts: | |
146 raise UnresolvableName(self.name) | |
147 context = context.clone() | |
148 context.lookupname = self.name | |
149 return _infer_stmts(stmts, context, frame) | |
150 nodes.Name.infer = path_wrapper(infer_name) | |
151 nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper | |
152 | |
153 | |
154 def infer_callfunc(self, context=None): | |
155 """infer a CallFunc node by trying to guess what the function returns""" | |
156 callcontext = context.clone() | |
157 callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs) | |
158 callcontext.boundnode = None | |
159 for callee in self.func.infer(context): | |
160 if callee is YES: | |
161 yield callee | |
162 continue | |
163 try: | |
164 if hasattr(callee, 'infer_call_result'): | |
165 for infered in callee.infer_call_result(self, callcontext): | |
166 yield infered | |
167 except InferenceError: | |
168 ## XXX log error ? | |
169 continue | |
170 nodes.CallFunc.infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) | |
171 | |
172 | |
173 def infer_import(self, context=None, asname=True): | |
174 """infer an Import node: return the imported module/object""" | |
175 name = context.lookupname | |
176 if name is None: | |
177 raise InferenceError() | |
178 if asname: | |
179 yield self.do_import_module(self.real_name(name)) | |
180 else: | |
181 yield self.do_import_module(name) | |
182 nodes.Import.infer = path_wrapper(infer_import) | |
183 | |
184 def infer_name_module(self, name): | |
185 context = InferenceContext() | |
186 context.lookupname = name | |
187 return self.infer(context, asname=False) | |
188 nodes.Import.infer_name_module = infer_name_module | |
189 | |
190 | |
191 def infer_from(self, context=None, asname=True): | |
192 """infer a From nodes: return the imported module/object""" | |
193 name = context.lookupname | |
194 if name is None: | |
195 raise InferenceError() | |
196 if asname: | |
197 name = self.real_name(name) | |
198 module = self.do_import_module(self.modname) | |
199 try: | |
200 context = copy_context(context) | |
201 context.lookupname = name | |
202 return _infer_stmts(module.getattr(name, ignore_locals=module is self.ro
ot()), context) | |
203 except NotFoundError: | |
204 raise InferenceError(name) | |
205 nodes.From.infer = path_wrapper(infer_from) | |
206 | |
207 | |
208 def infer_getattr(self, context=None): | |
209 """infer a Getattr node by using getattr on the associated object""" | |
210 #context = context.clone() | |
211 for owner in self.expr.infer(context): | |
212 if owner is YES: | |
213 yield owner | |
214 continue | |
215 try: | |
216 context.boundnode = owner | |
217 for obj in owner.igetattr(self.attrname, context): | |
218 yield obj | |
219 context.boundnode = None | |
220 except (NotFoundError, InferenceError): | |
221 context.boundnode = None | |
222 except AttributeError: | |
223 # XXX method / function | |
224 context.boundnode = None | |
225 nodes.Getattr.infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) | |
226 nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work
with a path wrapper | |
227 | |
228 | |
229 def infer_global(self, context=None): | |
230 if context.lookupname is None: | |
231 raise InferenceError() | |
232 try: | |
233 return _infer_stmts(self.root().getattr(context.lookupname), context) | |
234 except NotFoundError: | |
235 raise InferenceError() | |
236 nodes.Global.infer = path_wrapper(infer_global) | |
237 | |
238 | |
239 def infer_subscript(self, context=None): | |
240 """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" | |
241 if isinstance(self.slice, nodes.Index): | |
242 index = self.slice.value.infer(context).next() | |
243 if index is YES: | |
244 yield YES | |
245 return | |
246 try: | |
247 # suppose it's a Tuple/List node (attribute error else) | |
248 assigned = self.value.getitem(index.value, context) | |
249 except AttributeError: | |
250 raise InferenceError() | |
251 except (IndexError, TypeError): | |
252 yield YES | |
253 return | |
254 for infered in assigned.infer(context): | |
255 yield infered | |
256 else: | |
257 raise InferenceError() | |
258 nodes.Subscript.infer = path_wrapper(infer_subscript) | |
259 nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) | |
260 | |
261 | |
262 UNARY_OP_METHOD = {'+': '__pos__', | |
263 '-': '__neg__', | |
264 '~': '__invert__', | |
265 'not': None, # XXX not '__nonzero__' | |
266 } | |
267 | |
268 def infer_unaryop(self, context=None): | |
269 for operand in self.operand.infer(context): | |
270 try: | |
271 yield operand.infer_unary_op(self.op) | |
272 except TypeError: | |
273 continue | |
274 except AttributeError: | |
275 meth = UNARY_OP_METHOD[self.op] | |
276 if meth is None: | |
277 yield YES | |
278 else: | |
279 try: | |
280 # XXX just suppose if the type implement meth, returned type | |
281 # will be the same | |
282 operand.getattr(meth) | |
283 yield operand | |
284 except GeneratorExit: | |
285 raise | |
286 except: | |
287 yield YES | |
288 nodes.UnaryOp.infer = path_wrapper(infer_unaryop) | |
289 | |
290 | |
291 BIN_OP_METHOD = {'+': '__add__', | |
292 '-': '__sub__', | |
293 '/': '__div__', | |
294 '//': '__floordiv__', | |
295 '*': '__mul__', | |
296 '**': '__power__', | |
297 '%': '__mod__', | |
298 '&': '__and__', | |
299 '|': '__or__', | |
300 '^': '__xor__', | |
301 '<<': '__lshift__', | |
302 '>>': '__rshift__', | |
303 } | |
304 | |
305 def _infer_binop(operator, operand1, operand2, context, failures=None): | |
306 if operand1 is YES: | |
307 yield operand1 | |
308 return | |
309 try: | |
310 for valnode in operand1.infer_binary_op(operator, operand2, context): | |
311 yield valnode | |
312 except AttributeError: | |
313 try: | |
314 # XXX just suppose if the type implement meth, returned type | |
315 # will be the same | |
316 operand1.getattr(BIN_OP_METHOD[operator]) | |
317 yield operand1 | |
318 except: | |
319 if failures is None: | |
320 yield YES | |
321 else: | |
322 failures.append(operand1) | |
323 | |
324 def infer_binop(self, context=None): | |
325 failures = [] | |
326 for lhs in self.left.infer(context): | |
327 for val in _infer_binop(self.op, lhs, self.right, context, failures): | |
328 yield val | |
329 for lhs in failures: | |
330 for rhs in self.right.infer(context): | |
331 for val in _infer_binop(self.op, rhs, lhs, context): | |
332 yield val | |
333 nodes.BinOp.infer = path_wrapper(infer_binop) | |
334 | |
335 | |
336 def infer_arguments(self, context=None): | |
337 name = context.lookupname | |
338 if name is None: | |
339 raise InferenceError() | |
340 return _arguments_infer_argname(self, name, context) | |
341 nodes.Arguments.infer = infer_arguments | |
342 | |
343 | |
344 def infer_ass(self, context=None): | |
345 """infer a AssName/AssAttr: need to inspect the RHS part of the | |
346 assign node | |
347 """ | |
348 stmt = self.statement() | |
349 if isinstance(stmt, nodes.AugAssign): | |
350 return stmt.infer(context) | |
351 stmts = list(self.assigned_stmts(context=context)) | |
352 return _infer_stmts(stmts, context) | |
353 nodes.AssName.infer = path_wrapper(infer_ass) | |
354 nodes.AssAttr.infer = path_wrapper(infer_ass) | |
355 | |
356 def infer_augassign(self, context=None): | |
357 failures = [] | |
358 for lhs in self.target.infer_lhs(context): | |
359 for val in _infer_binop(self.op, lhs, self.value, context, failures): | |
360 yield val | |
361 for lhs in failures: | |
362 for rhs in self.value.infer(context): | |
363 for val in _infer_binop(self.op, rhs, lhs, context): | |
364 yield val | |
365 nodes.AugAssign.infer = path_wrapper(infer_augassign) | |
366 | |
367 | |
368 # no infer method on DelName and DelAttr (expected InferenceError) | |
369 | |
370 | |
371 def infer_empty_node(self, context=None): | |
372 if not self.has_underlying_object(): | |
373 yield YES | |
374 else: | |
375 try: | |
376 for infered in MANAGER.infer_astng_from_something(self.object, | |
377 context=context): | |
378 yield infered | |
379 except ASTNGError: | |
380 yield YES | |
381 nodes.EmptyNode.infer = path_wrapper(infer_empty_node) | |
382 | |
OLD | NEW |