root / logilab.pylintinstaller / logilab / astng / inference.py

Revision 202:d67e86292521, 24.3 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

Line 
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"""this module contains a set of functions to handle inference on astng trees
14
15:author:    Sylvain Thenault
16:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE)
17:contact:   http://www.logilab.fr/ -- mailto:python-projects@logilab.org
18:copyright: 2003-2008 Sylvain Thenault
19:contact:   mailto:thenault@gmail.com
20"""
21
22from __future__ import generators
23
24__doctype__ = "restructuredtext en"
25
26from copy import copy
27
28from logilab.common.compat import imap, chain, set
29
30from logilab.astng import MANAGER, YES, InferenceContext, Instance, Generator, \
31     unpack_infer, _infer_stmts, nodes, copy_context
32from logilab.astng import ASTNGError, InferenceError, UnresolvableName, \
33     NoDefault, NotFoundError, ASTNGBuildingException
34
35   
36def path_wrapper(func):
37    """return the given infer function wrapped to handle the path"""
38    def wrapped(node, context=None, _func=func, **kwargs):
39        """wrapper function handling context"""
40        if context is None:
41            context = InferenceContext(node)
42        context.push(node)
43        yielded = set()
44        try:
45            for res in _func(node, context, **kwargs):
46                # unproxy only true instance, not const, tuple, dict...
47                if res.__class__ is Instance:
48                    ares = res._proxied
49                else:
50                    ares = res
51                if not ares in yielded:
52                    yield res
53                    yielded.add(ares)
54            context.pop()
55        except:
56            context.pop()
57            raise
58    return wrapped
59
60# .infer method ###############################################################
61
62def infer_default(self, context=None):
63    """we don't know how to resolve a statement by default"""
64    #print 'inference error', self, name, path
65    raise InferenceError(self.__class__.__name__)
66
67#infer_default = infer_default
68nodes.Node.infer = infer_default
69
70
71def infer_end(self, context=None):
72    """inference's end for node such as Module, Class, Function, Const...
73    """
74    yield self
75
76#infer_end = path_wrapper(infer_end)
77nodes.Module.infer = nodes.Class.infer = infer_end
78nodes.List.infer = infer_end
79nodes.Tuple.infer = infer_end
80nodes.Dict.infer = infer_end
81nodes.Const.infer = infer_end
82
83def infer_empty_node(self, context=None):
84    if not self.has_underlying_object():
85        yield YES
86    else:
87        try:
88            for infered in MANAGER.infer_astng_from_something(self.object,
89                                                              context=context):
90                yield infered
91        except ASTNGError:
92            yield YES
93nodes.EmptyNode.infer = path_wrapper(infer_empty_node)
94   
95
96
97class CallContext:
98    """when infering a function call, this class is used to remember values
99    given as argument
100    """
101    def __init__(self, args, starargs, dstarargs):
102        self.args = []
103        self.nargs = {}
104        for arg in args:
105            if isinstance(arg, nodes.Keyword):
106                self.nargs[arg.name] = arg.expr
107            else:
108                self.args.append(arg)
109        self.starargs = starargs
110        self.dstarargs = dstarargs
111
112    def infer_argument(self, funcnode, name, context):
113        """infer a function argument value according the the call context"""
114        # 1. search in named keywords
115        try:
116            return self.nargs[name].infer(context)
117        except KeyError:
118            # Function.argnames can be None in astng (means that we don't have
119            # information on argnames)
120            if funcnode.argnames is not None:
121                try:
122                    argindex = funcnode.argnames.index(name)
123                except ValueError:
124                    pass
125                else:
126                    # 2. first argument of instance/class method
127                    if argindex == 0 and funcnode.type in ('method', 'classmethod'):
128                        if context.boundnode is not None:
129                            boundnode = context.boundnode
130                        else:
131                            # XXX can do better ?
132                            boundnode = funcnode.parent.frame()
133                        if funcnode.type == 'method':
134                            return iter((Instance(boundnode),))
135                        if funcnode.type == 'classmethod':
136                            return iter((boundnode,))                           
137                    # 2. search arg index
138                    try:
139                        return self.args[argindex].infer(context)
140                    except IndexError:
141                        pass
142                    # 3. search in *args (.starargs)
143                    if self.starargs is not None:
144                        its = []
145                        for infered in self.starargs.infer(context):
146                            if infered is YES:
147                                its.append((YES,))
148                                continue
149                            try:
150                                its.append(infered.getitem(argindex).infer(context))
151                            except (InferenceError, AttributeError):
152                                its.append((YES,))
153                            except IndexError:
154                                continue
155                        if its:
156                            return chain(*its)
157        # 4. XXX search in **kwargs (.dstarargs)
158        if self.dstarargs is not None:
159            its = []
160            for infered in self.dstarargs.infer(context):
161                if infered is YES:
162                    its.append((YES,))
163                    continue
164                try:
165                    its.append(infered.getitem(name).infer(context))
166                except (InferenceError, AttributeError):
167                    its.append((YES,))
168                except IndexError:
169                    continue
170            if its:
171                return chain(*its)
172        # 5. */** argument, (Tuple or Dict)
173        mularg = funcnode.mularg_class(name)
174        if mularg is not None: 
175            # XXX should be able to compute values inside
176            return iter((mularg,))
177        # 6. return default value if any
178        try:
179            return funcnode.default_value(name).infer(context)
180        except NoDefault:
181            raise InferenceError(name)
182       
183       
184def infer_function(self, context=None):
185    """infer on Function nodes must be take with care since it
186    may be called to infer one of it's argument (in which case <name>
187    should be given)
188    """
189    name = context.lookupname
190    # no name is given, we are infering the function itself
191    if name is None:
192        yield self
193        return
194    if context.callcontext:
195        # reset call context/name
196        callcontext = context.callcontext
197        context = copy_context(context)
198        context.callcontext = None
199        for infered in callcontext.infer_argument(self, name, context):
200            yield infered
201        return
202    # Function.argnames can be None in astng (means that we don't have
203    # information on argnames), in which case we can't do anything more
204    if self.argnames is None:
205        yield YES
206        return
207    if not name in self.argnames:
208        raise InferenceError()
209    # first argument of instance/class method
210    if name == self.argnames[0]:
211        if self.type == 'method':
212            yield Instance(self.parent.frame())
213            return
214        if self.type == 'classmethod':
215            yield self.parent.frame()
216            return
217    mularg = self.mularg_class(name)
218    if mularg is not None: # */** argument, no doubt it's a Tuple or Dict
219        yield mularg
220        return
221    # if there is a default value, yield it. And then yield YES to reflect
222    # we can't guess given argument value
223    try:
224        context = copy_context(context)
225        for infered in self.default_value(name).infer(context):
226            yield infered
227        yield YES
228    except NoDefault:
229        yield YES
230
231nodes.Function.infer = path_wrapper(infer_function)
232nodes.Lambda.infer = path_wrapper(infer_function)
233
234
235def infer_name(self, context=None):
236    """infer a Name: use name lookup rules"""
237    context = context.clone()
238    context.lookupname = self.name
239    frame, stmts = self.lookup(self.name)
240    if not stmts:
241        raise UnresolvableName(self.name)
242    return _infer_stmts(stmts, context, frame)
243
244nodes.Name.infer = path_wrapper(infer_name)
245
246
247def infer_assname(self, context=None):
248    """infer a AssName/AssAttr: need to inspect the RHS part of the
249    assign node
250    """
251    stmts = self.assigned_stmts(context=context)
252    return _infer_stmts(stmts, context)
253   
254nodes.AssName.infer = path_wrapper(infer_assname)
255
256
257def infer_assattr(self, context=None):
258    """infer a AssName/AssAttr: need to inspect the RHS part of the
259    assign node
260    """
261    stmts = self.assigned_stmts(context=context)
262    return _infer_stmts(stmts, context)
263   
264nodes.AssAttr.infer = path_wrapper(infer_assattr)
265
266       
267def infer_callfunc(self, context=None):
268    """infer a CallFunc node by trying to guess what's the function is
269    returning
270    """
271    one_infered = False
272    context = context.clone()
273    context.callcontext = CallContext(self.args, self.star_args, self.dstar_args)
274    for callee in self.node.infer(context):
275        if callee is YES:
276            yield callee
277            one_infered = True
278            continue
279        try:
280            for infered in callee.infer_call_result(self, context):
281                yield infered
282                one_infered = True
283        except (AttributeError, InferenceError):
284            ## XXX log error ?
285            continue
286    if not one_infered:
287        raise InferenceError()
288
289nodes.CallFunc.infer = path_wrapper(infer_callfunc)
290
291
292def infer_getattr(self, context=None):
293    """infer a Getattr node by using getattr on the associated object
294    """
295    one_infered = False
296    # XXX
297    #context = context.clone()
298    for owner in self.expr.infer(context):
299        if owner is YES:
300            yield owner
301            one_infered = True
302            continue
303        try:
304            context.boundnode = owner
305            for obj in owner.igetattr(self.attrname, context):
306                yield obj
307                one_infered = True
308            context.boundnode = None
309        except (NotFoundError, InferenceError):
310            continue
311        except AttributeError:
312            # XXX method / function
313            continue
314    if not one_infered:
315        raise InferenceError()
316               
317nodes.Getattr.infer = path_wrapper(infer_getattr)
318
319
320def _imported_module_astng(node, modname):
321    """return the ast for a module whose name is <modname> imported by <node>
322    """
323    # handle special case where we are on a package node importing a module
324    # using the same name as the package, which may end in an infinite loop
325    # on relative imports
326    # XXX: no more needed ?
327    mymodule = node.root()
328    if mymodule.relative_name(modname) == mymodule.name:
329        # FIXME: I don't know what to do here...
330        raise InferenceError(modname)
331    try:
332        return mymodule.import_module(modname)
333    except (ASTNGBuildingException, SyntaxError):
334        raise InferenceError(modname)
335       
336def infer_import(self, context=None, asname=True):
337    """self resolve on From / Import nodes return the imported module/object"""
338    name = context.lookupname
339    if name is None:
340        raise InferenceError()
341    if asname:
342        yield _imported_module_astng(self, self.real_name(name))
343    else:
344        yield _imported_module_astng(self, name)
345   
346nodes.Import.infer = path_wrapper(infer_import)
347
348def infer_from(self, context=None, asname=True):
349    """self resolve on From / Import nodes return the imported module/object"""
350    name = context.lookupname
351    if name is None:
352        raise InferenceError()
353    if asname:
354        name = self.real_name(name)
355    module = _imported_module_astng(self, self.modname)
356    try:
357        context = copy_context(context)
358        context.lookupname = name
359        return _infer_stmts(module.getattr(name), context)
360    except NotFoundError:
361        raise InferenceError(name)
362
363nodes.From.infer = path_wrapper(infer_from)
364
365
366def infer_global(self, context=None):
367    if context.lookupname is None:
368        raise InferenceError()
369    try:
370        return _infer_stmts(self.root().getattr(context.lookupname), context)
371    except NotFoundError:
372        raise InferenceError()
373nodes.Global.infer = path_wrapper(infer_global)
374
375
376def infer_subscript(self, context=None):
377    """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]
378    """
379    if len(self.subs) == 1:
380        index = self.subs[0].infer(context).next()
381        if index is YES:
382            yield YES
383            return
384        try:
385            # suppose it's a Tuple/List node (attribute error else)
386            assigned = self.expr.getitem(index.value)
387        except AttributeError:
388            raise InferenceError()
389        except IndexError:
390            yield YES
391            return
392        for infered in assigned.infer(context):
393            yield infered
394    else:
395        raise InferenceError()
396nodes.Subscript.infer = path_wrapper(infer_subscript)
397
398def infer_unarysub(self, context=None):
399    for infered in self.expr.infer(context):
400        try:
401            value = -infered.value
402        except (TypeError, AttributeError):
403            yield YES
404            continue
405        node = copy(self.expr)
406        node.value = value
407        yield node
408nodes.UnarySub.infer = path_wrapper(infer_unarysub)
409
410def infer_unaryadd(self, context=None):
411    return self.expr.infer(context)
412nodes.UnaryAdd.infer = infer_unaryadd
413
414def _py_value(node):
415    try:
416        return node.value
417    except AttributeError:
418        # not a constant
419        if isinstance(node, nodes.Dict):
420            return {}
421        if isinstance(node, nodes.List):
422            return []
423        if isinstance(node, nodes.Tuple):
424            return ()
425    raise ValueError()
426
427def _infer_operator(self, context=None, impl=None, meth='__method__'):
428    for lhs in self.left.infer(context):
429        try:
430            lhsvalue = _py_value(lhs)
431        except ValueError:
432            # not a constant
433            try:
434                # XXX just suppose if the type implement meth, returned type
435                # will be the same
436                lhs.getattr(meth)
437                yield lhs
438            except:
439                yield YES
440            continue
441        for