| 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 | """name lookup methods, available on Name ans scoped (Module, Class, |
|---|
| 14 | Function...) nodes: |
|---|
| 15 | |
|---|
| 16 | * .lookup(name) |
|---|
| 17 | * .ilookup(name) |
|---|
| 18 | |
|---|
| 19 | Be careful, lookup is kinda internal and return a tuple (scope, [stmts]), while |
|---|
| 20 | ilookup return an iterator on infered values |
|---|
| 21 | |
|---|
| 22 | :author: Sylvain Thenault |
|---|
| 23 | :copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 24 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 25 | :copyright: 2003-2007 Sylvain Thenault |
|---|
| 26 | :contact: mailto:thenault@gmail.com |
|---|
| 27 | """ |
|---|
| 28 | |
|---|
| 29 | from __future__ import generators |
|---|
| 30 | |
|---|
| 31 | __docformat__ = "restructuredtext en" |
|---|
| 32 | |
|---|
| 33 | import __builtin__ |
|---|
| 34 | |
|---|
| 35 | from logilab.astng.utils import are_exclusive |
|---|
| 36 | from logilab.astng import nodes, MANAGER, _infer_stmts, copy_context |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | def lookup(self, name): |
|---|
| 40 | """lookup a variable name |
|---|
| 41 | |
|---|
| 42 | return the scoope node and the list of assignments associated to the given |
|---|
| 43 | name according to the scope where it has been found (locals, globals or |
|---|
| 44 | builtin) |
|---|
| 45 | |
|---|
| 46 | The lookup is starting from self's scope. If self is not a frame itself and |
|---|
| 47 | the name is found in the inner frame locals, statements will be filtered |
|---|
| 48 | to remove ignorable statements according to self's location |
|---|
| 49 | """ |
|---|
| 50 | #assert ID_RGX.match(name), '%r is not a valid identifier' % name |
|---|
| 51 | return self.scope().scope_lookup(self, name) |
|---|
| 52 | |
|---|
| 53 | def scope_lookup(self, node, name, offset=0): |
|---|
| 54 | try: |
|---|
| 55 | stmts = node._filter_stmts(self.locals[name], self, offset) |
|---|
| 56 | except KeyError: |
|---|
| 57 | stmts = () |
|---|
| 58 | if stmts: |
|---|
| 59 | return self, stmts |
|---|
| 60 | if self.parent: |
|---|
| 61 | # nested scope: if parent scope is a function, that's fine |
|---|
| 62 | # else jump to the module |
|---|
| 63 | pscope = self.parent.scope() |
|---|
| 64 | if not isinstance(pscope, nodes.Function): |
|---|
| 65 | pscope = pscope.root() |
|---|
| 66 | return pscope.scope_lookup(node, name) |
|---|
| 67 | return builtin_lookup(name) |
|---|
| 68 | |
|---|
| 69 | def class_scope_lookup(self, node, name, offset=0): |
|---|
| 70 | if node in self.bases: |
|---|
| 71 | #print 'frame swaping' |
|---|
| 72 | frame = self.parent.frame() |
|---|
| 73 | # line offset to avoid that class A(A) resolve the ancestor to |
|---|
| 74 | # the defined class |
|---|
| 75 | offset = -1 |
|---|
| 76 | else: |
|---|
| 77 | frame = self |
|---|
| 78 | return scope_lookup(frame, node, name, offset) |
|---|
| 79 | |
|---|
| 80 | def function_scope_lookup(self, node, name, offset=0): |
|---|
| 81 | if node in self.defaults: |
|---|
| 82 | frame = self.parent.frame() |
|---|
| 83 | # line offset to avoid that def func(f=func) resolve the default |
|---|
| 84 | # value to the defined function |
|---|
| 85 | offset = -1 |
|---|
| 86 | else: |
|---|
| 87 | # check this is not used in function decorators |
|---|
| 88 | frame = self |
|---|
| 89 | return scope_lookup(frame, node, name, offset) |
|---|
| 90 | |
|---|
| 91 | def builtin_lookup(name): |
|---|
| 92 | """lookup a name into the builtin module |
|---|
| 93 | return the list of matching statements and the astng for the builtin |
|---|
| 94 | module |
|---|
| 95 | """ |
|---|
| 96 | builtinastng = MANAGER.astng_from_module(__builtin__) |
|---|
| 97 | try: |
|---|
| 98 | stmts = builtinastng.locals[name] |
|---|
| 99 | except KeyError: |
|---|
| 100 | stmts = () |
|---|
| 101 | return builtinastng, stmts |
|---|
| 102 | |
|---|
| 103 | def ilookup(self, name, context=None): |
|---|
| 104 | """infered lookup |
|---|
| 105 | |
|---|
| 106 | return an iterator on infered values of the statements returned by |
|---|
| 107 | the lookup method |
|---|
| 108 | """ |
|---|
| 109 | frame, stmts = self.lookup(name) |
|---|
| 110 | context = copy_context(context) |
|---|
| 111 | context.lookupname = name |
|---|
| 112 | return _infer_stmts(stmts, context, frame) |
|---|
| 113 | |
|---|
| 114 | |
|---|
| 115 | def _filter_stmts(self, stmts, frame, offset): |
|---|
| 116 | """filter statements: |
|---|
| 117 | |
|---|
| 118 | If self is not a frame itself and the name is found in the inner |
|---|
| 119 | frame locals, statements will be filtered to remove ignorable |
|---|
| 120 | statements according to self's location |
|---|
| 121 | """ |
|---|
| 122 | # if offset == -1, my actual frame is not the inner frame but its parent |
|---|
| 123 | # |
|---|
| 124 | # class A(B): pass |
|---|
| 125 | # |
|---|
| 126 | # we need this to resolve B correctly |
|---|
| 127 | if offset == -1: |
|---|
| 128 | myframe = self.frame().parent.frame() |
|---|
| 129 | else: |
|---|
| 130 | myframe = self.frame() |
|---|
| 131 | if not myframe is frame or self is frame: |
|---|
| 132 | return stmts |
|---|
| 133 | #print self.name, frame.name |
|---|
| 134 | mystmt = self.statement() |
|---|
| 135 | # line filtering if we are in the same frame |
|---|
| 136 | if myframe is frame: |
|---|
| 137 | mylineno = mystmt.source_line() + offset |
|---|
| 138 | else: |
|---|
| 139 | # disabling lineno filtering |
|---|
| 140 | print 'disabling lineno filtering' |
|---|
| 141 | mylineno = 0 |
|---|
| 142 | _stmts = [] |
|---|
| 143 | _stmt_parents = [] |
|---|
| 144 | #print '-'*60 |
|---|
| 145 | #print 'filtering', stmts, mylineno |
|---|
| 146 | for node in stmts: |
|---|
| 147 | stmt = node.statement() |
|---|
| 148 | # line filtering is on and we have reached our location, break |
|---|
| 149 | if mylineno > 0 and stmt.source_line() > mylineno: |
|---|
| 150 | #print 'break', mylineno, stmt.source_line() |
|---|
| 151 | break |
|---|
| 152 | if isinstance(node, Class) and self in node.bases: |
|---|
| 153 | #print 'breaking on', self, node.bases |
|---|
| 154 | break |
|---|
| 155 | try: |
|---|
| 156 | ass_type = node.ass_type() |
|---|
| 157 | if ass_type is mystmt: |
|---|
| 158 | if not isinstance(ass_type, (ListCompFor, GenExprFor)): |
|---|
| 159 | #print 'break now2', self, ass_type |
|---|
| 160 | break |
|---|
| 161 | if isinstance(self, (Const, Name)): |
|---|
| 162 | _stmts = [self] |
|---|
| 163 | #print 'break now', ass_type, self, node |
|---|
| 164 | break |
|---|
| 165 | except AttributeError: |
|---|
| 166 | ass_type = None |
|---|
| 167 | # a loop assigment is hidding previous assigment |
|---|
| 168 | if isinstance(ass_type, (For, ListCompFor, GenExprFor)) and \ |
|---|
| 169 | ass_type.parent_of(self): |
|---|
| 170 | _stmts = [node] |
|---|
| 171 | _stmt_parents = [stmt.parent] |
|---|
| 172 | continue |
|---|
| 173 | try: |
|---|
| 174 | pindex = _stmt_parents.index(stmt.parent) |
|---|
| 175 | except ValueError: |
|---|
| 176 | pass |
|---|
| 177 | else: |
|---|
| 178 | try: |
|---|
| 179 | if ass_type and _stmts[pindex].ass_type().parent_of(ass_type): |
|---|
| 180 | # print 'skipping', node, node.source_line() |
|---|
| 181 | continue |
|---|
| 182 | except AttributeError: |
|---|
| 183 | pass # name from Import, Function, Class... |
|---|
| 184 | if not are_exclusive(self, node): |
|---|
| 185 | ###print 'PARENT', stmt.parent |
|---|
| 186 | #print 'removing', _stmts[pindex] |
|---|
| 187 | del _stmt_parents[pindex] |
|---|
| 188 | del _stmts[pindex] |
|---|
| 189 | if isinstance(node, AssName): |
|---|
| 190 | if stmt.parent is mystmt.parent: |
|---|
| 191 | #print 'assign clear' |
|---|
| 192 | _stmts = [] |
|---|
| 193 | _stmt_parents = [] |
|---|
| 194 | if node.flags == 'OP_DELETE': |
|---|
| 195 | #print 'delete clear' |
|---|
| 196 | _stmts = [] |
|---|
| 197 | _stmt_parents = [] |
|---|
| 198 | continue |
|---|
| 199 | |
|---|
| 200 | if not are_exclusive(self, node): |
|---|
| 201 | #print 'append', node, node.source_line() |
|---|
| 202 | _stmts.append(node) |
|---|
| 203 | _stmt_parents.append(stmt.parent) |
|---|
| 204 | #print '->', _stmts |
|---|
| 205 | stmts = _stmts |
|---|
| 206 | return stmts |
|---|
| 207 | |
|---|
| 208 | |
|---|
| 209 | def _decorate(astmodule): |
|---|
| 210 | """add this module functionalities to necessary nodes""" |
|---|
| 211 | for klass in (astmodule.Name, astmodule.Module, astmodule.Class, |
|---|
| 212 | astmodule.Function, astmodule.Lambda): |
|---|
| 213 | klass.ilookup = ilookup |
|---|
| 214 | klass.lookup = lookup |
|---|
| 215 | klass._filter_stmts = _filter_stmts |
|---|
| 216 | astmodule.Class.scope_lookup = class_scope_lookup |
|---|
| 217 | astmodule.Function.scope_lookup = function_scope_lookup |
|---|
| 218 | astmodule.Lambda.scope_lookup = function_scope_lookup |
|---|
| 219 | astmodule.Module.scope_lookup = scope_lookup |
|---|
| 220 | astmodule.GenExpr.scope_lookup = scope_lookup |
|---|
| 221 | for name in ('Class', 'Function', 'Lambda', |
|---|
| 222 | 'For', 'ListCompFor', 'GenExprFor', |
|---|
| 223 | 'AssName', 'Name', 'Const'): |
|---|
| 224 | globals()[name] = getattr(astmodule, name) |
|---|