diff options
| -rw-r--r-- | __init__.py | 24 | ||||
| -rw-r--r-- | _inference_compiler.py | 158 | ||||
| -rw-r--r-- | _nodes_ast.py | 148 | ||||
| -rw-r--r-- | _nodes_compiler.py | 212 | ||||
| -rw-r--r-- | builder.py | 120 | ||||
| -rw-r--r-- | inference.py | 181 | ||||
| -rw-r--r-- | lookup.py | 8 | ||||
| -rw-r--r-- | nodes.py | 164 | ||||
| -rw-r--r-- | patchcomptransformer.py | 123 | ||||
| -rw-r--r-- | raw_building.py | 46 | ||||
| -rw-r--r-- | scoped_nodes.py | 11 | ||||
| -rw-r--r-- | test/unittest_builder.py | 8 | ||||
| -rw-r--r-- | utils.py | 24 |
13 files changed, 739 insertions, 488 deletions
diff --git a/__init__.py b/__init__.py index 38e69022..b1d27237 100644 --- a/__init__.py +++ b/__init__.py @@ -131,6 +131,30 @@ def _infer_stmts(stmts, context, frame=None): if not infered: raise InferenceError(str(stmt)) +def path_wrapper(func): + """return the given infer function wrapped to handle the path""" + def wrapped(node, context=None, _func=func, **kwargs): + """wrapper function handling context""" + if context is None: + context = InferenceContext(node) + context.push(node) + yielded = set() + try: + for res in _func(node, context, **kwargs): + # unproxy only true instance, not const, tuple, dict... + if res.__class__ is Instance: + ares = res._proxied + else: + ares = res + if not ares in yielded: + yield res + yielded.add(ares) + context.pop() + except: + context.pop() + raise + return wrapped + # special inference objects ################################################### class Yes(object): diff --git a/_inference_compiler.py b/_inference_compiler.py new file mode 100644 index 00000000..07cb86ac --- /dev/null +++ b/_inference_compiler.py @@ -0,0 +1,158 @@ + +from logilab.astng import MANAGER, YES, ASTNGError, _infer_stmts, path_wrapper +from logilab.astng import nodes +from logilab.astng.utils import infer_end, end_ass_type + + +nodes.Const.infer = infer_end + +def infer_empty_node(self, context=None): + if not self.has_underlying_object(): + yield YES + else: + try: + for infered in MANAGER.infer_astng_from_something(self.object, + context=context): + yield infered + except ASTNGError: + yield YES +nodes.EmptyNode.infer = path_wrapper(infer_empty_node) + + +def infer_assname(self, context=None): + """infer a AssName/AssAttr: need to inspect the RHS part of the + assign node + """ + stmts = self.assigned_stmts(context=context) + return _infer_stmts(stmts, context) +nodes.AssName.infer = path_wrapper(infer_assname) + + +def infer_assattr(self, context=None): + """infer a AssName/AssAttr: need to inspect the RHS part of the + assign node + """ + stmts = self.assigned_stmts(context=context) + return _infer_stmts(stmts, context) +nodes.AssAttr.infer = path_wrapper(infer_assattr) + + +def infer_getattr(self, context=None): + """infer a Getattr node by using getattr on the associated object + """ + one_infered = False + # XXX + #context = context.clone() + for owner in self.expr.infer(context): + if owner is YES: + yield owner + one_infered = True + continue + try: + context.boundnode = owner + for obj in owner.igetattr(self.attrname, context): + yield obj + one_infered = True + context.boundnode = None + except (NotFoundError, InferenceError): + continue + except AttributeError: + # XXX method / function + continue + if not one_infered: + raise InferenceError() +nodes.Getattr.infer = path_wrapper(infer_getattr) + + +def assend_assigned_stmts(self, context=None): + # only infer *real* assignments + if self.flags == 'OP_DELETE': + raise InferenceError() + return self.parent.assigned_stmts(self, context=context) +nodes.AssName.assigned_stmts = assend_assigned_stmts +nodes.AssAttr.assigned_stmts = assend_assigned_stmts + + +def mulass_assigned_stmts(self, node, context=None, asspath=None): + if asspath is None: + asspath = [] + node_idx = self.nodes.index(node) + asspath.insert(0, node_idx) + return self.parent.assigned_stmts(self, context, asspath) +nodes.AssTuple.assigned_stmts = mulass_assigned_stmts +nodes.AssList.assigned_stmts = mulass_assigned_stmts + + +def _resolve_looppart(parts, asspath, context): + """recursive function to resolve multiple assignments on loops""" + asspath = asspath[:] + index = asspath.pop(0) + for part in parts: + if part is YES: + continue + if not hasattr(part, 'iter_stmts'): + continue + for stmt in part.iter_stmts(): + try: + assigned = stmt.getitem(index) + except (AttributeError, IndexError): + continue + if not asspath: + # we acheived to resolved the assigment path, + # don't infer the last part + found = True + yield assigned + elif assigned is YES: + break + else: + # we are not yet on the last part of the path + # search on each possibly infered value + try: + for infered in _resolve_looppart(assigned.infer(context), asspath, context): + yield infered + except InferenceError: + break + +def for_assigned_stmts(self, node, context=None, asspath=None): + found = False + if asspath is None: + for lst in self.loop_node().infer(context): + if isinstance(lst, (nodes.Tuple, nodes.List)): + for item in lst.nodes: + found = True + yield item + else: + for infered in _resolve_looppart(self.loop_node().infer(context), asspath, context): + found = True + yield infered + if not found: + raise InferenceError() +nodes.For.assigned_stmts = for_assigned_stmts +nodes.ListCompFor.assigned_stmts = for_assigned_stmts +nodes.GenExprFor.assigned_stmts = for_assigned_stmts + +nodes.ListCompFor.ass_type = end_ass_type +nodes.GenExprFor.ass_type = end_ass_type +def parent_ass_type(self): + return self.parent.ass_type() +nodes.AssName.ass_type = parent_ass_type +nodes.AssAttr.ass_type = parent_ass_type +nodes.AssTuple.ass_type = parent_ass_type +nodes.AssList.ass_type = parent_ass_type +def assend_ass_type(self, context=None): + # only infer *real* assignments + if self.flags == 'OP_DELETE': + return self + return self.parent.ass_type() +nodes.AssName.ass_type = assend_ass_type +nodes.AssAttr.ass_type = assend_ass_type + + + +def for_loop_node(self): + return self.list +nodes.ListCompFor.loop_node = for_loop_node + +def gen_loop_nodes(self): + return self.iter +nodes.GenExprFor.loop_node = gen_loop_nodes diff --git a/_nodes_ast.py b/_nodes_ast.py new file mode 100644 index 00000000..3747f0ec --- /dev/null +++ b/_nodes_ast.py @@ -0,0 +1,148 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""python 2.5 builtin _ast compatibility module + +:author: Sylvain Thenault +:copyright: 2008 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2008 Sylvain Thenault +:contact: mailto:thenault@gmail.com +""" + +__docformat__ = "restructuredtext en" + +from logilab.astng.utils import infer_end + +from _ast import (Add, And, Assert, Assign, AugAssign, + Break, + Compare, Continue, + Dict, Div, + Ellipsis, Exec, + FloorDiv, For, + Global, + If, Import, Invert, + Lambda, List, ListComp, + Mod, Module, + Name, Not, + Or, + Pass, Print, + Raise, Return, + Slice, Sub, Subscript, + TryExcept, TryFinally, Tuple, + While, With, + Yield, + ) + +from _ast import (AST as Node, + BitAnd as Bitand, BitOr as Bitor, BitXor as Bitxor, + Call as CallFunc, + ClassDef as Class, + FunctionDef as Function, + GeneratorExp as GenExpr, + ImportFrom as From, + LShift as LeftShift, + Mult as Mul, + Repr as Backquote, + Pow as Power, + RShift as RightShift, + UAdd as UnaryAdd, + USub as UnarySub, + ) +# XXX : AugLoad, AugStore, Attribute +# BinOp, BoolOp +# Del, Delete +# Eq, Expr, Expression, ExtSlice +# Gt, GtE +# IfExp, In, Index, Interactive, Is, IsNot +# Load, Lt, LtE +# NotEq, NotIn, Num +# Param +# Store, Str, Suite +# UnaryOp +from _ast import Num, Str, Expr, alias +Const = (Num, Str) + +class EmptyNode(Node): pass + +# scoped nodes ################################################################ + +def module_append_node(self, child_node): + """append a child version specific to Module node""" + self.body.append(child_node) + child_node.parent = self +Module._append_node = module_append_node + +def _append_node(self, child_node): + """append a child, linking it in the tree""" + # XXX + self.code.nodes.append(child_node) + child_node.parent = self +Class._append_node = _append_node +Function._append_node = _append_node + +# inferences ################################################################## + +from logilab.astng.utils import infer_end + +Num.infer = infer_end +Str.infer = infer_end + +# raw building ################################################################ + +def module_factory(doc): + node = Module() + node.body = [] + if doc: + expr = Expr() + node.body.append(expr) + expr.parent = None + docstr = Str() + docstr.s = doc + expr.value = docstr + docstr.parent = expr + return node + +def dict_factory(): + return Dict() + +def import_from_factory(modname, membername): + node = From() + node.level = 0 + node.module = modname + aliasnode = alias() + aliasnode.parent = node + aliasnode.name = membername + aliasnode.asname = None + node.names = [aliasnode] + return node + +def const_factory(value): + if value is None: + node = Name() + node.id = 'None' + elif value is True: + node = Name() + node.id = 'True' + elif value is False: + node = Name() + node.id = 'False' + elif isinstance(value, (int, long, complex)): + node = Num() + node.n = value + elif isinstance(value, basestring): + node = Str() + node.s = value + else: + raise Exception(repr(value)) + return node + diff --git a/_nodes_compiler.py b/_nodes_compiler.py new file mode 100644 index 00000000..93765e9d --- /dev/null +++ b/_nodes_compiler.py @@ -0,0 +1,212 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""python < 2.5 compiler package compatibility module + +:author: Sylvain Thenault +:copyright: 2008 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2008 Sylvain Thenault +:contact: mailto:thenault@gmail.com +""" + +__docformat__ = "restructuredtext en" + +from __future__ import generators + +import sys +from compiler.ast import Add, And, AssAttr, AssList, AssName, \ + AssTuple, Assert, Assign, AugAssign, \ + Backquote, Bitand, Bitor, Bitxor, Break, CallFunc, Class, \ + Compare, Const, Continue, Dict, Discard, Div, \ + Ellipsis, EmptyNode, Exec, FloorDiv, \ + For, From, Function, Getattr, Global, \ + If, Import, Invert, Keyword, Lambda, LeftShift, \ + List, ListComp, ListCompFor, ListCompIf, Mod, Module, Mul, Name, Node, \ + Not, Or, Pass, Power, Print, Printnl, Raise, Return, RightShift, Slice, \ + Sliceobj, Stmt, Sub, Subscript, TryExcept, TryFinally, Tuple, UnaryAdd, \ + UnarySub, While, Yield +try: + # introduced in python 2.4 + from compiler.ast import GenExpr, GenExprFor, GenExprIf, GenExprInner +except: + class GenExpr: + """dummy GenExpr node, shouldn't be used with py < 2.4""" + class GenExprFor: + """dummy GenExprFor node, shouldn't be used with py < 2.4""" + class GenExprIf: + """dummy GenExprIf node, shouldn't be used with py < 2.4""" + class GenExprInner: + """dummy GenExprInner node, shouldn't be used with py < 2.4""" +try: + # introduced in python 2.4 + from compiler.ast import Decorators +except: + class Decorators: + """dummy Decorators node, shouldn't be used with py < 2.4""" + +try: + # introduced in python 2.5 + from compiler.ast import With +except: + class With: + """dummy With node, shouldn't be used since py < 2.5""" + + +def assattr_as_string(node): + """return an ast.AssAttr node as string""" + if node.flags == 'OP_DELETE': + return 'del %s.%s' % (node.expr.as_string(), node.attrname) + return '%s.%s' % (node.expr.as_string(), node.attrname) +AssAttr.as_string = assattr_as_string + +def asslist_as_string(node): + """return an ast.AssList node as string""" + string = ', '.join([n.as_string() for n in node.nodes]) + return '[%s]' % string +AssList.as_string = asslist_as_string + +def assname_as_string(node): + """return an ast.AssName node as string""" + if node.flags == 'OP_DELETE': + return 'del %s' % node.name + return node.name +AssName.as_string = assname_as_string + +def asstuple_as_string(node): + """return an ast.AssTuple node as string""" + string = ', '.join([n.as_string() for n in node.nodes]) + # fix for del statement + return string.replace(', del ', ', ') +AssTuple.as_string = asstuple_as_string + +Const.eq = lambda self, value: self.value == value + +def const_as_string(node): + """return an ast.Const node as string""" + return repr(node.value) +Const.as_string = const_as_string + +def decorators_scope(self): + # skip the function node to go directly to the upper level scope + return self.parent.parent.scope() +Decorators.scope = decorators_scope + +def discard_as_string(node): + """return an ast.Discard node as string""" + return node.expr.as_string() +Discard.as_string = discard_as_string + +def empty_as_string(node): + return '' +EmptyNode.as_string = empty_as_string + +EmptyNode.getChildNodes = lambda self: () + +# introduced in python 2.5 +From.level = 0 # will be overiden by instance attribute with py>=2.5 + +def genexprinner_as_string(node): + """return an ast.GenExpr node as string""" + return '%s %s' % (node.expr.as_string(), ' '.join([n.as_string() + for n in node.quals])) +GenExprInner.as_string = genexprinner_as_string + +def genexprfor_as_string(node): + """return an ast.GenExprFor node as string""" + return 'for %s in %s %s' % (node.assign.as_string(), + node.iter.as_string(), + ' '.join([n.as_string() for n in node.ifs])) +GenExprFor.as_string = genexprfor_as_string + +def genexprif_as_string(node): + """return an ast.GenExprIf node as string""" + return 'if %s' % node.test.as_string() +GenExprIf.as_string = genexprif_as_string + +def getattr_as_string(node): + """return an ast.Getattr node as string""" + return '%s.%s' % (node.expr.as_string(), node.attrname) +Getattr.as_string = getattr_as_string + +def keyword_as_string(node): + """return an ast.Keyword node as string""" + return '%s=%s' % (node.name, node.expr.as_string()) +Keyword.as_string = keyword_as_string + +def listcompfor_as_string(node): + """return an ast.ListCompFor node as string""" + return 'for %s in %s %s' % (node.assign.as_string(), + node.list.as_string(), + ' '.join([n.as_string() for n in node.ifs])) +ListCompFor.as_string = listcompfor_as_string + +def listcompif_as_string(node): + """return an ast.ListCompIf node as string""" + return 'if %s' % node.test.as_string() +ListCompIf.as_string = listcompif_as_string + +def printnl_as_string(node): + """return an ast.Printnl node as string""" + nodes = ', '.join([n.as_string() for n in node.nodes]) + if node.dest: + return 'print >> %s, %s' % (node.dest.as_string(), nodes) + return 'print %s' % nodes +Printnl.as_string = printnl_as_string + +def sliceobj_as_string(node): + """return an ast.Sliceobj node as string""" + return ':'.join([n.as_string() for n in node.nodes]) +Sliceobj.as_string = sliceobj_as_string + +def stmt_as_string(node): + """return an ast.Stmt node as string""" + stmts = '\n'.join([n.as_string() for n in node.nodes]) + if isinstance(node.parent, Module): + return stmts + return stmts.replace('\n', '\n ') +Stmt.as_string = stmt_as_string + +# scoped nodes ################################################################ + +def module_append_node(self, child_node): + """append a child version specific to Module node""" + self.node.nodes.append(child_node) + child_node.parent = self +Module._append_node = module_append_node + +def _append_node(self, child_node): + """append a child, linking it in the tree""" + self.code.nodes.append(child_node) + child_node.parent = self +Class._append_node = _append_node +Function._append_node = _append_node + +# raw building ################################################################ + +def module_factory(doc): + node = Module(doc, Stmt([])) + node.node.parent = node + return node + +def dict_factory(): + return Dict([]) + +if sys.version_info < (2, 5): + def import_from_factory(modname, membername): + return From(modname, ( (membername, None), ) ) +else: + def import_from_factory(modname, membername): + return From(modname, ( (membername, None), ), 0) + +def const_factory(value): + return Const(value) @@ -12,13 +12,11 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """The ASTNGBuilder makes astng from living object and / or from compiler.ast +With python >= 2.5, the internal _ast module is used instead + The builder is not thread safe and can't be used to parse different sources at the same time. -TODO: - - more complet representation on inspect build - (imported modules ? use dis.dis ?) - :author: Sylvain Thenault :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) @@ -31,8 +29,6 @@ __docformat__ = "restructuredtext en" import sys from os.path import splitext, basename, dirname, exists, abspath -from parser import ParserError -from compiler import parse from inspect import isfunction, ismethod, ismethoddescriptor, isclass, \ isbuiltin from inspect import isdatadescriptor @@ -44,108 +40,15 @@ from logilab.astng import nodes, YES, Instance from logilab.astng.utils import ASTWalker from logilab.astng._exceptions import ASTNGBuildingException, InferenceError from logilab.astng.raw_building import * -from logilab.astng.astutils import cvrtr - -import token -from compiler import transformer, consts -from types import TupleType - -def fromto_lineno(asttuple): - """return the minimum and maximum line number of the given ast tuple""" - return from_lineno(asttuple), to_lineno(asttuple) -def from_lineno(asttuple): - """return the minimum line number of the given ast tuple""" - if type(asttuple[1]) is TupleType: - return from_lineno(asttuple[1]) - return asttuple[2] -def to_lineno(asttuple): - """return the maximum line number of the given ast tuple""" - if type(asttuple[-1]) is TupleType: - return to_lineno(asttuple[-1]) - return asttuple[2] - -def fix_lineno(node, fromast, toast=None): - if 'fromlineno' in node.__dict__: - return node - #print 'fixing', id(node), id(node.__dict__), node.__dict__.keys(), repr(node) - if isinstance(node, nodes.Stmt): - node.fromlineno = from_lineno(fromast)#node.nodes[0].fromlineno - node.tolineno = node.nodes[-1].tolineno - return node - if toast is None: - node.fromlineno, node.tolineno = fromto_lineno(fromast) - else: - node.fromlineno, node.tolineno = from_lineno(fromast), to_lineno(toast) - #print 'fixed', id(node) - return node -BaseTransformer = transformer.Transformer - -COORD_MAP = { - # if: test ':' suite ('elif' test ':' suite)* ['else' ':' suite] - 'if': (0, 0), - # 'while' test ':' suite ['else' ':' suite] - 'while': (0, 1), - # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite] - 'for': (0, 3), - # 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] - 'try': (0, 0), - # | 'try' ':' suite 'finally' ':' suite - - } - -def fixlineno_wrap(function, stype): - def fixlineno_wrapper(self, nodelist): - node = function(self, nodelist) - idx1, idx2 = COORD_MAP.get(stype, (0, -1)) - return fix_lineno(node, nodelist[idx1], nodelist[idx2]) - return fixlineno_wrapper -nodes.Module.fromlineno = 0 -nodes.Module.tolineno = 0 -class ASTNGTransformer(BaseTransformer): - """ovverides transformer for a better source line number handling""" - def com_NEWLINE(self, *args): - # A ';' at the end of a line can make a NEWLINE token appear - # here, Render it harmless. (genc discards ('discard', - # ('const', xxxx)) Nodes) - lineno = args[0][1] - # don't put fromlineno/tolineno on Const None to mark it as dynamically - # added, without "physical" reference in the source - n = nodes.Discard(nodes.Const(None)) - n.fromlineno = n.tolineno = lineno - return n - def com_node(self, node): - res = self._dispatch[node[0]](node[1:]) - return fix_lineno(res, node) - def com_assign(self, node, assigning): - res = BaseTransformer.com_assign(self, node, assigning) - return fix_lineno(res, node) - def com_apply_trailer(self, primaryNode, nodelist): - node = BaseTransformer.com_apply_trailer(self, primaryNode, nodelist) - return fix_lineno(node, nodelist) - -## def atom(self, nodelist): -## node = BaseTransformer.atom(self, nodelist) -## return fix_lineno(node, nodelist[0], nodelist[-1]) +try: + from _ast import PyCF_ONLY_AST + def parse(string): + return compile(string, "<string>", 'exec', PyCF_ONLY_AST) +except: + from compiler import parse + from logilab.astng import patchcomptransformer - def funcdef(self, nodelist): - node = BaseTransformer.funcdef(self, nodelist) - # XXX decorators - return fix_lineno(node, nodelist[-5], nodelist[-3]) - def classdef(self, nodelist): - node = BaseTransformer.classdef(self, nodelist) - return fix_lineno(node, nodelist[0], nodelist[-2]) - -# wrap *_stmt methods -for name in dir(BaseTransformer): - if name.endswith('_stmt') and not (name in ('com_stmt', - 'com_append_stmt') - or name in ASTNGTransformer.__dict__): - setattr(BaseTransformer, name, - fixlineno_wrap(getattr(BaseTransformer, name), name[:-5])) - -transformer.Transformer = ASTNGTransformer - # ast NG builder ############################################################## class ASTNGBuilder: @@ -590,8 +493,3 @@ def imported_member(node, member, name): else: attach_import_node(node, member_module, name) -# optimize the tokenize module -#from logilab.common.bind import optimize_module -#import tokenize -#optimize_module(sys.modules['tokenize'], tokenize.__dict__) -#optimize_module(sys.modules[__name__], sys.modules[__name__].__dict__) diff --git a/inference.py b/inference.py index 024bde55..fba9cde3 100644 --- a/inference.py +++ b/inference.py @@ -28,34 +28,11 @@ from copy import copy from logilab.common.compat import imap, chain, set from logilab.astng import MANAGER, YES, InferenceContext, Instance, Generator, \ - unpack_infer, _infer_stmts, nodes, copy_context + unpack_infer, _infer_stmts, nodes, copy_context, path_wrapper from logilab.astng import ASTNGError, InferenceError, UnresolvableName, \ NoDefault, NotFoundError, ASTNGBuildingException - +from logilab.astng.utils import infer_end, end_ass_type -def path_wrapper(func): - """return the given infer function wrapped to handle the path""" - def wrapped(node, context=None, _func=func, **kwargs): - """wrapper function handling context""" - if context is None: - context = InferenceContext(node) - context.push(node) - yielded = set() - try: - for res in _func(node, context, **kwargs): - # unproxy only true instance, not const, tuple, dict... - if res.__class__ is Instance: - ares = res._proxied - else: - ares = res - if not ares in yielded: - yield res - yielded.add(ares) - context.pop() - except: - context.pop() - raise - return wrapped # .infer method ############################################################### @@ -67,31 +44,11 @@ def infer_default(self, context=None): #infer_default = infer_default nodes.Node.infer = infer_default - -def infer_end(self, context=None): - """inference's end for node such as Module, Class, Function, Const... - """ - yield self - #infer_end = path_wrapper(infer_end) nodes.Module.infer = nodes.Class.infer = infer_end nodes.List.infer = infer_end nodes.Tuple.infer = infer_end nodes.Dict.infer = infer_end -nodes.Const.infer = infer_end - -def infer_empty_node(self, context=None): - if not self.has_underlying_object(): - yield YES - else: - try: - for infered in MANAGER.infer_astng_from_something(self.object, - context=context): - yield infered - except ASTNGError: - yield YES -nodes.EmptyNode.infer = path_wrapper(infer_empty_node) - class CallContext: @@ -243,26 +200,6 @@ def infer_name(self, context=None): nodes.Name.infer = path_wrapper(infer_name) - -def infer_assname(self, context=None): - """infer a AssName/AssAttr: need to inspect the RHS part of the - assign node - """ - stmts = self.assigned_stmts(context=context) - return _infer_stmts(stmts, context) - -nodes.AssName.infer = path_wrapper(infer_assname) - - -def infer_assattr(self, context=None): - """infer a AssName/AssAttr: need to inspect the RHS part of the - assign node - """ - stmts = self.assigned_stmts(context=context) - return _infer_stmts(stmts, context) - -nodes.AssAttr.infer = path_wrapper(infer_assattr) - def infer_callfunc(self, context=None): """infer a CallFunc node by trying to guess what's the function is @@ -289,34 +226,6 @@ def infer_callfunc(self, context=None): nodes.CallFunc.infer = path_wrapper(infer_callfunc) -def infer_getattr(self, context=None): - """infer a Getattr node by using getattr on the associated object - """ - one_infered = False - # XXX - #context = context.clone() - for owner in self.expr.infer(context): - if owner is YES: - yield owner - one_infered = True - continue - try: - context.boundnode = owner - for obj in owner.igetattr(self.attrname, context): - yield obj - one_infered = True - context.boundnode = None - except (NotFoundError, InferenceError): - continue - except AttributeError: - # XXX method / function - continue - if not one_infered: - raise InferenceError() - -nodes.Getattr.infer = path_wrapper(infer_getattr) - - def _imported_module_astng(node, modname): """return the ast for a module whose name is <modname> imported by <node> """ @@ -526,23 +435,6 @@ will be [1, 1] once arrived to the Assign node. The `context` argument is the current inference context which should be given to any intermediary inference necessary. """ -def assend_assigned_stmts(self, context=None): - # only infer *real* assignments - if self.flags == 'OP_DELETE': - raise InferenceError() - return self.parent.assigned_stmts(self, context=context) - -nodes.AssName.assigned_stmts = assend_assigned_stmts -nodes.AssAttr.assigned_stmts = assend_assigned_stmts - -def mulass_assigned_stmts(self, node, context=None, asspath=None): - if asspath is None: - asspath = [] - node_idx = self.nodes.index(node) - asspath.insert(0, node_idx) - return self.parent.assigned_stmts(self, context, asspath) -nodes.AssTuple.assigned_stmts = mulass_assigned_stmts -nodes.AssList.assigned_stmts = mulass_assigned_stmts def assign_assigned_stmts(self, node, context=None, asspath=None): if not asspath: @@ -597,53 +489,7 @@ def tryexcept_assigned_stmts(self, node, context=None, asspath=None): nodes.TryExcept.assigned_stmts = tryexcept_assigned_stmts -def _resolve_looppart(parts, asspath, context): - """recursive function to resolve multiple assignments on loops""" - asspath = asspath[:] - index = asspath.pop(0) - for part in parts: - if part is YES: - continue - if not hasattr(part, 'iter_stmts'): - continue - for stmt in part.iter_stmts(): - try: - assigned = stmt.getitem(index) - except (AttributeError, IndexError): - continue - if not asspath: - # we acheived to resolved the assigment path, - # don't infer the last part - found = True - yield assigned - elif assigned is YES: - break - else: - # we are not yet on the last part of the path - # search on each possibly infered value - try: - for infered in _resolve_looppart(assigned.infer(context), asspath, context): - yield infered - except InferenceError: - break -def for_assigned_stmts(self, node, context=None, asspath=None): - found = False - if asspath is None: - for lst in self.loop_node().infer(context): - if isinstance(lst, (nodes.Tuple, nodes.List)): - for item in lst.nodes: - found = True - yield item - else: - for infered in _resolve_looppart(self.loop_node().infer(context), asspath, context): - found = True - yield infered - if not found: - raise InferenceError() -nodes.For.assigned_stmts = for_assigned_stmts -nodes.ListCompFor.assigned_stmts = for_assigned_stmts -nodes.GenExprFor.assigned_stmts = for_assigned_stmts def with_assigned_stmts(self, node, context=None, asspath=None): found = False @@ -660,28 +506,11 @@ def with_assigned_stmts(self, node, context=None, asspath=None): nodes.With.assigned_stmts = with_assigned_stmts -def end_ass_type(self): - return self nodes.With.ass_type = end_ass_type nodes.For.ass_type = end_ass_type -nodes.ListCompFor.ass_type = end_ass_type -nodes.GenExprFor.ass_type = end_ass_type nodes.TryExcept.ass_type = end_ass_type nodes.Assign.ass_type = end_ass_type nodes.AugAssign.ass_type = end_ass_type -def parent_ass_type(self): - return self.parent.ass_type() -nodes.AssName.ass_type = parent_ass_type -nodes.AssAttr.ass_type = parent_ass_type -nodes.AssTuple.ass_type = parent_ass_type -nodes.AssList.ass_type = parent_ass_type -def assend_ass_type(self, context=None): - # only infer *real* assignments - if self.flags == 'OP_DELETE': - return self - return self.parent.ass_type() -nodes.AssName.ass_type = assend_ass_type -nodes.AssAttr.ass_type = assend_ass_type # subscription protocol ####################################################### @@ -716,8 +545,6 @@ nodes.Dict.iter_stmts = dict_iter_stmts def for_loop_node(self): return self.list nodes.For.loop_node = for_loop_node -nodes.ListCompFor.loop_node = for_loop_node -def gen_loop_nodes(self): - return self.iter -nodes.GenExprFor.loop_node = gen_loop_nodes +if nodes.AST_MODE == 'compiler': + from logilab.astng._inference_compiler import * @@ -222,6 +222,10 @@ def _decorate(astmodule): astmodule.Module.scope_lookup = scope_lookup astmodule.GenExpr.scope_lookup = scope_lookup for name in ('Class', 'Function', 'Lambda', - 'For', 'ListCompFor', 'GenExprFor', - 'AssName', 'Name', 'Const'): + 'For', 'Name', 'Const'): globals()[name] = getattr(astmodule, name) + if hasattr(astmodule, 'ListCompFor'): + for name in ('ListCompFor', 'GenExprFor', 'AssName',): + globals()[name] = getattr(astmodule, name) + #else: XXX + @@ -32,9 +32,9 @@ on From and Import : [1] http://docs.python.org/lib/module-compiler.ast.html :author: Sylvain Thenault -:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) +:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org -:copyright: 2003-2007 Sylvain Thenault +:copyright: 2003-2008 Sylvain Thenault :contact: mailto:thenault@gmail.com """ @@ -42,56 +42,24 @@ from __future__ import generators __docformat__ = "restructuredtext en" -from compiler.ast import Assign, Add, And, AssAttr, AssList, AssName, \ - AssTuple, Assert, Assign, AugAssign, \ - Backquote, Bitand, Bitor, Bitxor, Break, CallFunc, Class, \ - Compare, Const, Continue, Dict, Discard, Div, FloorDiv, \ - Ellipsis, EmptyNode, Exec, \ - For, From, Function, Getattr, Global, \ - If, Import, Invert, Keyword, Lambda, LeftShift, \ - List, ListComp, ListCompFor, ListCompIf, Mod, Module, Mul, Name, Node, \ - Not, Or, Pass, Power, Print, Printnl, Raise, Return, RightShift, Slice, \ - Sliceobj, Stmt, Sub, Subscript, TryExcept, TryFinally, Tuple, UnaryAdd, \ - UnarySub, While, Yield try: - # introduced in python 2.4 - from compiler.ast import GenExpr, GenExprFor, GenExprIf, GenExprInner + from logilab.astng._nodes_ast import * + AST_MODE = '_ast' except: - class GenExpr: - """dummy GenExpr node, shouldn't be used since py < 2.4""" - class GenExprFor: - """dummy GenExprFor node, shouldn't be used since py < 2.4""" - class GenExprIf: - """dummy GenExprIf node, shouldn't be used since py < 2.4""" - class GenExprInner: - """dummy GenExprInner node, shouldn't be used since py < 2.4""" + from logilab.astng._nodes_compiler import * + AST_MODE = 'compiler' -try: - # introduced in python 2.4 - from compiler.ast import Decorators -except: - class Decorators: - """dummy Decorators node, shouldn't be used since py < 2.4""" -try: - # introduced in python 2.5 - from compiler.ast import With -except: - class With: - """dummy With node, shouldn't be used since py < 2.5""" - -from logilab.astng._exceptions import NotFoundError, InferenceError -from logilab.astng.utils import extend_class from logilab.astng import InferenceContext +from logilab.astng._exceptions import NotFoundError +from logilab.astng.utils import extend_class -# introduced in python 2.5 -From.level = 0 # will be overiden by instance attribute with py>=2.5 +INFER_NEED_NAME_STMTS = (From, Import, Global, TryExcept) import re ID_RGX = re.compile('^[a-zA-Z_][a-zA-Z_0-9]*$') del re -INFER_NEED_NAME_STMTS = (From, Import, Global, TryExcept) # Node ###################################################################### @@ -260,12 +228,8 @@ class NodeNG: extend_class(Node, NodeNG) -Const.eq = lambda self, value: self.value == value - -def decorators_scope(self): - # skip the function node to go directly to the upper level scope - return self.parent.parent.scope() -Decorators.scope = decorators_scope +Module.fromlineno = 0 +Module.tolineno = 0 # block range overrides ####################################################### @@ -363,33 +327,6 @@ def and_as_string(node): return ' and '.join(['(%s)' % n.as_string() for n in node.nodes]) And.as_string = and_as_string -def assattr_as_string(node): - """return an ast.AssAttr node as string""" - if node.flags == 'OP_DELETE': - return 'del %s.%s' % (node.expr.as_string(), node.attrname) - return '%s.%s' % (node.expr.as_string(), node.attrname) -AssAttr.as_string = assattr_as_string - -def asslist_as_string(node): - """return an ast.AssList node as string""" - string = ', '.join([n.as_string() for n in node.nodes]) - return '[%s]' % string -AssList.as_string = asslist_as_string - -def assname_as_string(node): - """return an ast.AssName node as string""" - if node.flags == 'OP_DELETE': - return 'del %s' % node.name - return node.name -AssName.as_string = assname_as_string - -def asstuple_as_string(node): - """return an ast.AssTuple node as string""" - string = ', '.join([n.as_string() for n in node.nodes]) - # fix for del statement - return string.replace(', del ', ', ') -AssTuple.as_string = asstuple_as_string - def assert_as_string(node): """return an ast.Assert node as string""" if node.fail: @@ -460,11 +397,6 @@ def compare_as_string(node): return '%s %s' % (node.expr.as_string(), rhs_str) Compare.as_string = compare_as_string -def const_as_string(node): - """return an ast.Const node as string""" - return repr(node.value) -Const.as_string = const_as_string - def continue_as_string(node): """return an ast.Continue node as string""" return 'continue' @@ -476,11 +408,6 @@ def dict_as_string(node): for key, value in node.items]) Dict.as_string = dict_as_string -def discard_as_string(node): - """return an ast.Discard node as string""" - return node.expr.as_string() -Discard.as_string = discard_as_string - def div_as_string(node): """return an ast.Div node as string""" return '(%s) / (%s)' % (node.left.as_string(), node.right.as_string()) @@ -496,10 +423,6 @@ def ellipsis_as_string(node): return '...' Ellipsis.as_string = ellipsis_as_string -def empty_as_string(node): - return '' -EmptyNode.as_string = empty_as_string - def exec_as_string(node): """return an ast.Exec node as string""" if node.globals: @@ -524,6 +447,7 @@ For.as_string = for_as_string def from_as_string(node): """return an ast.From node as string""" + # XXX level return 'from %s import %s' % (node.modname, _import_string(node.names)) From.as_string = from_as_string @@ -540,29 +464,6 @@ def genexpr_as_string(node): return '(%s)' % node.code.as_string() GenExpr.as_string = genexpr_as_string -def genexprinner_as_string(node): - """return an ast.GenExpr node as string""" - return '%s %s' % (node.expr.as_string(), ' '.join([n.as_string() - for n in node.quals])) -GenExprInner.as_string = genexprinner_as_string - -def genexprfor_as_string(node): - """return an ast.GenExprFor node as string""" - return 'for %s in %s %s' % (node.assign.as_string(), - node.iter.as_string(), - ' '.join([n.as_string() for n in node.ifs])) -GenExprFor.as_string = genexprfor_as_string - -def genexprif_as_string(node): - """return an ast.GenExprIf node as string""" - return 'if %s' % node.test.as_string() -GenExprIf.as_string = genexprif_as_string - -def getattr_as_string(node): - """return an ast.Getattr node as string""" - return '%s.%s' % (node.expr.as_string(), node.attrname) -Getattr.as_string = getattr_as_string - def global_as_string(node): """return an ast.Global node as string""" return 'global %s' % ', '.join(node.names) @@ -589,11 +490,6 @@ def invert_as_string(node): return '~%s' % node.expr.as_string() Invert.as_string = invert_as_string -def keyword_as_string(node): - """return an ast.Keyword node as string""" - return '%s=%s' % (node.name, node.expr.as_string()) -Keyword.as_string = keyword_as_string - def lambda_as_string(node): """return an ast.Lambda node as string""" return 'lambda %s: %s' % (node.format_args(), node.code.as_string()) @@ -615,18 +511,6 @@ def listcomp_as_string(node): for n in node.quals])) ListComp.as_string = listcomp_as_string -def listcompfor_as_string(node): - """return an ast.ListCompFor node as string""" - return 'for %s in %s %s' % (node.assign.as_string(), - node.list.as_string(), - ' '.join([n.as_string() for n in node.ifs])) -ListCompFor.as_string = listcompfor_as_string - -def listcompif_as_string(node): - """return an ast.ListCompIf node as string""" - return 'if %s' % node.test.as_string() -ListCompIf.as_string = listcompif_as_string - def mod_as_string(node): """return an ast.Mod node as string""" return '(%s) %% (%s)' % (node.left.as_string(), node.right.as_string()) @@ -676,14 +560,6 @@ def print_as_string(node): return 'print %s,' % nodes Print.as_string = print_as_string -def printnl_as_string(node): - """return an ast.Printnl node as string""" - nodes = ', '.join([n.as_string() for n in node.nodes]) - if node.dest: - return 'print >> %s, %s' % (node.dest.as_string(), nodes) - return 'print %s' % nodes -Printnl.as_string = printnl_as_string - def raise_as_string(node): """return an ast.Raise node as string""" if node.expr1: @@ -716,19 +592,6 @@ def slice_as_string(node): return '%s[%s:%s]' % (node.expr.as_string(), lower, upper) Slice.as_string = slice_as_string -def sliceobj_as_string(node): - """return an ast.Sliceobj node as string""" - return ':'.join([n.as_string() for n in node.nodes]) -Sliceobj.as_string = sliceobj_as_string - -def stmt_as_string(node): - """return an ast.Stmt node as string""" - stmts = '\n'.join([n.as_string() for n in node.nodes]) - if isinstance(node.parent, Module): - return stmts - return stmts.replace('\n', '\n ') -Stmt.as_string = stmt_as_string - def sub_as_string(node): """return an ast.Sub node as string""" return '(%s) - (%s)' % (node.left.as_string(), node.right.as_string()) @@ -812,6 +675,3 @@ def _import_string(names): _names.append(name) return ', '.join(_names) -# to backport into compiler ################################################### - -EmptyNode.getChildNodes = lambda self: () diff --git a/patchcomptransformer.py b/patchcomptransformer.py new file mode 100644 index 00000000..d060b68c --- /dev/null +++ b/patchcomptransformer.py @@ -0,0 +1,123 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""Monkey patch compiler.transformer to fix line numbering bugs + +:author: Sylvain Thenault +:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2003-2008 Sylvain Thenault +:contact: mailto:thenault@gmail.com +""" + +from types import TupleType +from compiler import transformer + +from logilab.astng import nodes + +def fromto_lineno(asttuple): + """return the minimum and maximum line number of the given ast tuple""" + return from_lineno(asttuple), to_lineno(asttuple) + +def from_lineno(asttuple): + """return the minimum line number of the given ast tuple""" + if type(asttuple[1]) is TupleType: + return from_lineno(asttuple[1]) + return asttuple[2] + +def to_lineno(asttuple): + """return the maximum line number of the given ast tuple""" + if type(asttuple[-1]) is TupleType: + return to_lineno(asttuple[-1]) + return asttuple[2] + +def fix_lineno(node, fromast, toast=None): + if 'fromlineno' in node.__dict__: + return node + #print 'fixing', id(node), id(node.__dict__), node.__dict__.keys(), repr(node) + if isinstance(node, nodes.Stmt): + node.fromlineno = from_lineno(fromast)#node.nodes[0].fromlineno + node.tolineno = node.nodes[-1].tolineno + return node + if toast is None: + node.fromlineno, node.tolineno = fromto_lineno(fromast) + else: + node.fromlineno, node.tolineno = from_lineno(fromast), to_lineno(toast) + #print 'fixed', id(node) + return node + +BaseTransformer = transformer.Transformer + +COORD_MAP = { + # if: test ':' suite ('elif' test ':' suite)* ['else' ':' suite] + 'if': (0, 0), + # 'while' test ':' suite ['else' ':' suite] + 'while': (0, 1), + # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite] + 'for': (0, 3), + # 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] + 'try': (0, 0), + # | 'try' ':' suite 'finally' ':' suite + + } + +def fixlineno_wrap(function, stype): + def fixlineno_wrapper(self, nodelist): + node = function(self, nodelist) + idx1, idx2 = COORD_MAP.get(stype, (0, -1)) + return fix_lineno(node, nodelist[idx1], nodelist[idx2]) + return fixlineno_wrapper + +class ASTNGTransformer(BaseTransformer): + """ovverides transformer for a better source line number handling""" + def com_NEWLINE(self, *args): + # A ';' at the end of a line can make a NEWLINE token appear + # here, Render it harmless. (genc discards ('discard', + # ('const', xxxx)) Nodes) + lineno = args[0][1] + # don't put fromlineno/tolineno on Const None to mark it as dynamically + # added, without "physical" reference in the source + n = nodes.Discard(nodes.Const(None)) + n.fromlineno = n.tolineno = lineno + return n + def com_node(self, node): + res = self._dispatch[node[0]](node[1:]) + return fix_lineno(res, node) + def com_assign(self, node, assigning): + res = BaseTransformer.com_assign(self, node, assigning) + return fix_lineno(res, node) + def com_apply_trailer(self, primaryNode, nodelist): + node = BaseTransformer.com_apply_trailer(self, primaryNode, nodelist) + return fix_lineno(node, nodelist) + +## def atom(self, nodelist): +## node = BaseTransformer.atom(self, nodelist) +## return fix_lineno(node, nodelist[0], nodelist[-1]) + + def funcdef(self, nodelist): + node = BaseTransformer.funcdef(self, nodelist) + # XXX decorators + return fix_lineno(node, nodelist[-5], nodelist[-3]) + def classdef(self, nodelist): + node = BaseTransformer.classdef(self, nodelist) + return fix_lineno(node, nodelist[0], nodelist[-2]) + +# wrap *_stmt methods +for name in dir(BaseTransformer): + if name.endswith('_stmt') and not (name in ('com_stmt', + 'com_append_stmt') + or name in ASTNGTransformer.__dict__): + setattr(BaseTransformer, name, + fixlineno_wrap(getattr(BaseTransformer, name), name[:-5])) + +transformer.Transformer = ASTNGTransformer + diff --git a/raw_building.py b/raw_building.py index 502ad8d8..1ac5715f 100644 --- a/raw_building.py +++ b/raw_building.py @@ -14,9 +14,9 @@ (build_* functions) or from living object (object_build_* functions) :author: Sylvain Thenault -:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) +:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org -:copyright: 2003-2007 Sylvain Thenault +:copyright: 2003-2008 Sylvain Thenault :contact: mailto:thenault@gmail.com """ @@ -27,9 +27,16 @@ from inspect import getargspec from logilab.astng import nodes + +def _attach_local_node(parent, node, name): + node.name = name # needed by add_local_node + node.parent = parent + node.lineno = 1 + parent.add_local_node(node) + def attach___dict__(node): """attach the __dict__ attribute to Class and Module objects""" - dictn = nodes.Dict([]) + dictn = nodes.dict_factory() dictn.parent = node node.locals['__dict__'] = [dictn] @@ -49,36 +56,19 @@ def attach_const_node(node, name, value): """create a Const node and register it in the locals of the given node with the specified name """ - _attach_local_node(node, nodes.Const(value), name) + _attach_local_node(node, nodes.const_factory(value), name) -if sys.version_info < (2, 5): - def attach_import_node(node, modname, membername): - """create a From node and register it in the locals of the given - node with the specified name - """ - _attach_local_node(node, - nodes.From(modname, ( (membername, None), ) ), - membername) -else: - def attach_import_node(node, modname, membername): - """create a From node and register it in the locals of the given - node with the specified name - """ - _attach_local_node(node, - nodes.From(modname, ( (membername, None), ), 0), - membername) - -def _attach_local_node(parent, node, name): - node.name = name # needed by add_local_node - node.parent = parent - node.lineno = 1 - parent.add_local_node(node) +def attach_import_node(node, modname, membername): + """create a From node and register it in the locals of the given + node with the specified name + """ + _attach_local_node(node, nodes.import_from_factory(modname, membername), + membername) def build_module(name, doc=None): """create and initialize a astng Module node""" - node = nodes.Module(doc, nodes.Stmt([])) - node.node.parent = node + node = nodes.module_factory(doc) node.name = name node.pure_python = False node.package = False diff --git a/scoped_nodes.py b/scoped_nodes.py index 8895538c..fef17fd5 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -96,10 +96,6 @@ class LocalsDictMixIn(object): self._append_node(child_node) self.set_local(name or child_node.name, child_node) - def _append_node(self, child_node): - """append a child, linking it in the tree""" - self.code.nodes.append(child_node) - child_node.parent = self def __getitem__(self, item): """method from the `dict` interface returning the first node @@ -226,12 +222,7 @@ class ModuleNG(object): raise except: pass - raise NotFoundError(name) - - def _append_node(self, child_node): - """append a child version specific to Module node""" - self.node.nodes.append(child_node) - child_node.parent = self + raise NotFoundError(name) def source_line(self): """return the source line number, 0 on a module""" diff --git a/test/unittest_builder.py b/test/unittest_builder.py index 3b98a71b..0a62f262 100644 --- a/test/unittest_builder.py +++ b/test/unittest_builder.py @@ -21,8 +21,8 @@ from logilab.common.testlib import TestCase, unittest_main from unittest_inference import get_name_node from pprint import pprint -from logilab.astng.astutils import cvrtr -from logilab.astng import builder, nodes, Module, YES +from logilab.astng import builder, nodes, patchcomptransformer +from logilab.astng import Module, YES, InferenceError import data from data import module as test_module @@ -30,7 +30,7 @@ from data import module as test_module class TransformerTC(TestCase): def setUp(self): - transformer = builder.ASTNGTransformer() + transformer = patchcomptransformer.ASTNGTransformer() self.astng = transformer.parsesuite(open('data/format.py').read()) def test_callfunc_lineno(self): @@ -293,7 +293,7 @@ def global_no_effect(): self.failUnlessEqual(astng.getattr('CSTE')[1].source_line(), 6) self.assertRaises(nodes.NotFoundError, astng.getattr, 'CSTE2') - self.assertRaises(nodes.InferenceError, + self.assertRaises(InferenceError, astng['global_no_effect'].ilookup('CSTE2').next) def test_socket_build(self): @@ -14,9 +14,9 @@ extract information from it :author: Sylvain Thenault -:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) +:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org -:copyright: 2003-2007 Sylvain Thenault +:copyright: 2003-2008 Sylvain Thenault :contact: mailto:thenault@gmail.com """ @@ -30,10 +30,15 @@ def extend_class(original, addons): class """ brain = addons.__dict__.copy() - for special_key in ('__doc__', '__module__'): + for special_key in ('__doc__', '__module__', '__dict__'): if special_key in addons.__dict__: del brain[special_key] - original.__dict__.update(brain) + try: + original.__dict__.update(brain) + except AttributeError: + # dictproxy object + for k, v in brain.iteritems(): + setattr(original, k, v) class ASTWalker: """a walker visiting a tree in preorder, calling on the handler: @@ -171,3 +176,14 @@ def _try_except_from_branch(node, stmt): for i, block_nodes in enumerate(node.handlers): if stmt in block_nodes: return 'except', i + + +# inference utilities ######################################################### + +def infer_end(self, context=None): + """inference's end for node such as Module, Class, Function, Const... + """ + yield self + +def end_ass_type(self): + return self |
