diff options
-rw-r--r-- | __init__.py | 150 | ||||
-rw-r--r-- | _inference_ast.py | 46 | ||||
-rw-r--r-- | _inference_compiler.py | 29 | ||||
-rw-r--r-- | _nodes_ast.py | 170 | ||||
-rw-r--r-- | _nodes_compiler.py | 71 | ||||
-rw-r--r-- | builder.py | 51 | ||||
-rw-r--r-- | inference.py | 18 | ||||
-rw-r--r-- | lookup.py | 3 | ||||
-rw-r--r-- | manager.py | 3 | ||||
-rw-r--r-- | nodes.py | 2 | ||||
-rw-r--r-- | raw_building.py | 64 | ||||
-rw-r--r-- | scoped_nodes.py | 8 | ||||
-rw-r--r-- | test/unittest_builder.py | 220 | ||||
-rw-r--r-- | utils.py | 144 |
14 files changed, 611 insertions, 368 deletions
diff --git a/__init__.py b/__init__.py index b1d27237..98692115 100644 --- a/__init__.py +++ b/__init__.py @@ -155,116 +155,6 @@ def path_wrapper(func): raise return wrapped -# special inference objects ################################################### - -class Yes(object): - """a yes object""" - def __repr__(self): - return 'YES' - def __getattribute__(self, name): - return self - def __call__(self, *args, **kwargs): - return self -YES = Yes() - -class Proxy: - """a simple proxy object""" - def __init__(self, proxied): - self._proxied = proxied - - def __getattr__(self, name): - return getattr(self._proxied, name) - - def infer(self, context=None): - yield self - - -class InstanceMethod(Proxy): - """a special node representing a function bound to an instance""" - def __repr__(self): - instance = self._proxied.parent.frame() - return 'Bound method %s of %s.%s' % (self._proxied.name, - instance.root().name, - instance.name) - __str__ = __repr__ - - def is_bound(self): - return True - - -class Instance(Proxy): - """a special node representing a class instance""" - def getattr(self, name, context=None, lookupclass=True): - try: - return self._proxied.instance_attr(name, context) - except NotFoundError: - if name == '__class__': - return [self._proxied] - if name == '__name__': - # access to __name__ gives undefined member on class - # instances but not on class objects - raise NotFoundError(name) - if lookupclass: - return self._proxied.getattr(name, context) - raise NotFoundError(name) - - def igetattr(self, name, context=None): - """infered getattr""" - try: - # XXX frame should be self._proxied, or not ? - return _infer_stmts( - self._wrap_attr(self.getattr(name, context, lookupclass=False)), - context, frame=self) - except NotFoundError: - try: - # fallback to class'igetattr since it has some logic to handle - # descriptors - return self._wrap_attr(self._proxied.igetattr(name, context)) - except NotFoundError: - raise InferenceError(name) - - def _wrap_attr(self, attrs): - """wrap bound methods of attrs in a InstanceMethod proxies""" - # Guess which attrs are used in inference. - def wrap(attr): - if isinstance(attr, Function) and attr.type == 'method': - return InstanceMethod(attr) - else: - return attr - return imap(wrap, attrs) - - def infer_call_result(self, caller, context=None): - """infer what's a class instance is returning when called""" - infered = False - for node in self._proxied.igetattr('__call__', context): - for res in node.infer_call_result(caller, context): - infered = True - yield res - if not infered: - raise InferenceError() - - def __repr__(self): - return 'Instance of %s.%s' % (self._proxied.root().name, - self._proxied.name) - __str__ = __repr__ - - def callable(self): - try: - self._proxied.getattr('__call__') - return True - except NotFoundError: - return False - - def pytype(self): - return self._proxied.qname() - -class Generator(Proxy): - """a special node representing a generator""" - def callable(self): - return True - - def pytype(self): - return '__builtin__.generator' # imports ##################################################################### @@ -278,42 +168,4 @@ from logilab.astng import inference from logilab.astng import lookup lookup._decorate(nodes) -List._proxied = MANAGER.astng_from_class(list) -List.__bases__ += (inference.Instance,) -List.pytype = lambda x: '__builtin__.list' - -Tuple._proxied = MANAGER.astng_from_class(tuple) -Tuple.__bases__ += (inference.Instance,) -Tuple.pytype = lambda x: '__builtin__.tuple' - -Dict.__bases__ += (inference.Instance,) -Dict._proxied = MANAGER.astng_from_class(dict) -Dict.pytype = lambda x: '__builtin__.dict' - -builtin_astng = Dict._proxied.root() - -Const.__bases__ += (inference.Instance,) -Const._proxied = None -def Const___getattr__(self, name): - if self.value is None: - raise AttributeError(name) - if self._proxied is None: - self._proxied = MANAGER.astng_from_class(self.value.__class__) - return getattr(self._proxied, name) -Const.__getattr__ = Const___getattr__ -def Const_getattr(self, name, context=None, lookupclass=None): - if self.value is None: - raise NotFoundError(name) - if self._proxied is None: - self._proxied = MANAGER.astng_from_class(self.value.__class__) - return self._proxied.getattr(name, context) -Const.getattr = Const_getattr -Const.has_dynamic_getattr = lambda x: False - -def Const_pytype(self): - if self.value is None: - return '__builtin__.NoneType' - if self._proxied is None: - self._proxied = MANAGER.astng_from_class(self.value.__class__) - return self._proxied.qname() -Const.pytype = Const_pytype +from logilab.astng.utils import Instance, InstanceMethod, YES, NONE, TRUE, FALSE diff --git a/_inference_ast.py b/_inference_ast.py new file mode 100644 index 00000000..9cea5626 --- /dev/null +++ b/_inference_ast.py @@ -0,0 +1,46 @@ +from logilab.astng import MANAGER, YES, ASTNGError, Instance, _infer_stmts, path_wrapper +from logilab.astng import nodes +from logilab.astng.utils import infer_end, end_ass_type + +nodes.Num.__bases__ += (Instance,) +nodes.Num._proxied = None +nodes.Num.has_dynamic_getattr = lambda x: False +def _Num_value_proxy(node): + if node._proxied is None: + node._proxied = MANAGER.astng_from_class(node.n.__class__) + return node._proxied +nodes.Num._value_proxy = _Num_value_proxy + + +nodes.Str.__bases__ += (Instance,) +nodes.Str._proxied = None +nodes.Str.has_dynamic_getattr = lambda x: False +def _Str_value_proxy(node): + if node._proxied is None: + node._proxied = MANAGER.astng_from_class(node.s.__class__) + return node._proxied +nodes.Str._value_proxy = _Str_value_proxy + + +def Const___getattr__(node, name): + #if node.value is None: + # raise AttributeError(name) + return getattr(node._value_proxy(), name) +nodes.Str.__getattr__ = Const___getattr__ +nodes.Num.__getattr__ = Const___getattr__ + +def Const_getattr(node, name, context=None, lookupclass=None): + #if node.value is None: + # raise NotFoundError(name) + return node._value_proxy().getattr(name, context) +nodes.Str.getattr = Const_getattr +nodes.Num.getattr = Const_getattr + +def Const_pytype(node): + return node._value_proxy().qname() +nodes.Num.pytype = Const_pytype +nodes.Str.pytype = Const_pytype + + +nodes.Num.infer = infer_end +nodes.Str.infer = infer_end diff --git a/_inference_compiler.py b/_inference_compiler.py index 07cb86ac..81758bae 100644 --- a/_inference_compiler.py +++ b/_inference_compiler.py @@ -1,8 +1,35 @@ -from logilab.astng import MANAGER, YES, ASTNGError, _infer_stmts, path_wrapper from logilab.astng import nodes +from logilab.astng import (MANAGER, YES, ASTNGError, InferenceError, Instance, + _infer_stmts, path_wrapper) from logilab.astng.utils import infer_end, end_ass_type +nodes.Const.__bases__ += (Instance,) +nodes.Const._proxied = None +def Const___getattr__(self, name): + if self.value is None: + raise AttributeError(name) + if self._proxied is None: + self._proxied = MANAGER.astng_from_class(self.value.__class__) + return getattr(self._proxied, name) +nodes.Const.__getattr__ = Const___getattr__ +def Const_getattr(self, name, context=None, lookupclass=None): + if self.value is None: + raise NotFoundError(name) + if self._proxied is None: + self._proxied = MANAGER.astng_from_class(self.value.__class__) + return self._proxied.getattr(name, context) +nodes.Const.getattr = Const_getattr +nodes.Const.has_dynamic_getattr = lambda x: False + +def Const_pytype(self): + if self.value is None: + return '__builtin__.NoneType' + if self._proxied is None: + self._proxied = MANAGER.astng_from_class(self.value.__class__) + return self._proxied.qname() +nodes.Const.pytype = Const_pytype + nodes.Const.infer = infer_end diff --git a/_nodes_ast.py b/_nodes_ast.py index 3747f0ec..0dd135e4 100644 --- a/_nodes_ast.py +++ b/_nodes_ast.py @@ -1,3 +1,4 @@ + # 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 @@ -21,7 +22,7 @@ __docformat__ = "restructuredtext en" -from logilab.astng.utils import infer_end +from logilab.astng.utils import infer_end, NoneType, Bool from _ast import (Add, And, Assert, Assign, AugAssign, Break, @@ -33,7 +34,7 @@ from _ast import (Add, And, Assert, Assign, AugAssign, If, Import, Invert, Lambda, List, ListComp, Mod, Module, - Name, Not, + Not, Or, Pass, Print, Raise, Return, @@ -44,6 +45,7 @@ from _ast import (Add, And, Assert, Assign, AugAssign, ) from _ast import (AST as Node, + Attribute as Getattr, BitAnd as Bitand, BitOr as Bitor, BitXor as Bitxor, Call as CallFunc, ClassDef as Class, @@ -57,6 +59,8 @@ from _ast import (AST as Node, RShift as RightShift, UAdd as UnaryAdd, USub as UnarySub, + + Name, ) # XXX : AugLoad, AugStore, Attribute # BinOp, BoolOp @@ -69,39 +73,117 @@ from _ast import (AST as Node, # Param # Store, Str, Suite # UnaryOp -from _ast import Num, Str, Expr, alias +from _ast import Num, Str, Eq, Expr, alias, arguments, comprehension Const = (Num, Str) class EmptyNode(Node): pass +def Name__init__(self, name): + self.id = name +Name.__init__ = Name__init__ + +def Name_get_name(self): + return self.id +Name.name = property(Name_get_name) + +def _get_children_value(self): + return (self.value,) +Expr.getChildNodes = _get_children_value +Getattr.getChildNodes = _get_children_value + +def _get_children_nochildren(self): + return () +Import.getChildNodes = _get_children_nochildren +print 'patching', Name, id(Name) +Name.getChildNodes = _get_children_nochildren +Str.getChildNodes = _get_children_nochildren +Num.getChildNodes = _get_children_nochildren +NoneType.getChildNodes = _get_children_nochildren +Bool.getChildNodes = _get_children_nochildren +Pass.getChildNodes = _get_children_nochildren +Eq.getChildNodes = _get_children_nochildren + +def _get_children_call(self): + children = [self.func] + children.extend(self.args) + children.extend(self.keywords) + if self.starargs: + children.extend(self.starargs) + if self.kwargs: + children.extend(self.kwargs) + return children +CallFunc.getChildNodes = _get_children_call + + +def _get_children_assign(self): + return self.targets + [self.value] +Assign.getChildNodes = _get_children_assign + +def _get_children_if(self): + return [self.test] + self.body + self.orelse +If.getChildNodes = _get_children_if + +def _get_children_print(self): + if self.dest: + return [self.dest] + self.values + return self.values +Print.getChildNodes = _get_children_print + +def _get_children_compare(self): + return [self.left] + self.ops + self.comparators +Compare.getChildNodes = _get_children_compare + +def _get_children_generatorexp(self): + return [self.elt] + self.generators +GenExpr.getChildNodes = _get_children_generatorexp + +def _get_children_comprehension(self): + return [self.target] + [self.iter] + self.ifs +comprehension.getChildNodes = _get_children_comprehension + + +def getattr_as_string(node): + """return an ast.Getattr node as string""" + return '%s.%s' % (node.value.as_string(), node.attr) +Getattr.as_string = getattr_as_string + # 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 _get_children_body(self): + return self.body +Module.getChildNodes = _get_children_body +Class.getChildNodes = _get_children_body +Function.getChildNodes = _get_children_body def _append_node(self, child_node): """append a child, linking it in the tree""" - # XXX - self.code.nodes.append(child_node) + self.body.append(child_node) child_node.parent = self +Module._append_node = _append_node Class._append_node = _append_node Function._append_node = _append_node -# inferences ################################################################## - -from logilab.astng.utils import infer_end +# +def _init_set_doc(node): + node.doc = None + if isinstance(node.body[0], Expr) and isinstance(node.body[0].value, Str): + node.doc = node.body[0].value.s + print 'set doc', node + return node +init_module = init_function = init_class = _init_set_doc -Num.infer = infer_end -Str.infer = infer_end +def init_import(node): + node.names = [(alias.name, alias.asname) for alias in node.names] + return node +def init_assign(node): + return node + # raw building ################################################################ +from logilab.astng.utils import NoneType, Bool -def module_factory(doc): - node = Module() - node.body = [] +def _add_docstring(node, doc): + node.doc = doc if doc: expr = Expr() node.body.append(expr) @@ -110,6 +192,11 @@ def module_factory(doc): docstr.s = doc expr.value = docstr docstr.parent = expr + +def module_factory(doc): + node = Module() + node.body = [] + _add_docstring(node, doc) return node def dict_factory(): @@ -128,21 +215,52 @@ def import_from_factory(modname, membername): def const_factory(value): if value is None: - node = Name() - node.id = 'None' + node = NoneType(None) elif value is True: - node = Name() - node.id = 'True' + node = Bool(False) elif value is False: - node = Name() - node.id = 'False' + node = Bool(True) 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)) + node.value = value return node +def function_factory(name, args, defaults, flag=0, doc=None): + """create and initialize a astng Function node""" + node = Function() + node.body = [] + node.name = name + argsnode = arguments() + argsnode.args = [] + for arg in args: + argsnode.args.append(Name(arg)) + argsnode.args[-1].parent = argsnode + argsnode.defaults = [] + for default in defaults: + argsnode.defaults.append(const_factory(default)) + argsnode.defaults[-1].parent = argsnode + argsnode.kwarg = None # XXX + argsnode.vararg = None # XXX + argsnode.parent = node + node.args = argsnode + _add_docstring(node, doc) + return node + + +def class_factory(name, basenames=None, doc=None): + """create and initialize a astng Class node""" + node = Class() + node.body = [] + node.name = name + # XXX to check + node.bases = [] + for base in basenames: + basenode = Name(base) + node.bases.append(basenode) + basenode.parent = node + _add_docstring(node, doc) + return node diff --git a/_nodes_compiler.py b/_nodes_compiler.py index 93765e9d..477f0eca 100644 --- a/_nodes_compiler.py +++ b/_nodes_compiler.py @@ -18,11 +18,10 @@ :copyright: 2008 Sylvain Thenault :contact: mailto:thenault@gmail.com """ +from __future__ import generators __docformat__ = "restructuredtext en" -from __future__ import generators - import sys from compiler.ast import Add, And, AssAttr, AssList, AssName, \ AssTuple, Assert, Assign, AugAssign, \ @@ -191,13 +190,33 @@ def _append_node(self, child_node): Class._append_node = _append_node Function._append_node = _append_node +# +def init_module(node): + return node + +def init_function(node): + return node + +def init_class(node): + return node + +def init_import(node): + return node + +def init_assign(node): + node.value = node.expr + node.targets = node.nodes + return node + # raw building ################################################################ +from logilab.astng.utils import NoneType, Bool + def module_factory(doc): node = Module(doc, Stmt([])) node.node.parent = node return node - + def dict_factory(): return Dict([]) @@ -209,4 +228,48 @@ else: return From(modname, ( (membername, None), ), 0) def const_factory(value): - return Const(value) + if value is None: + nodecls = NoneType + elif value is True: + nodecls = Bool + elif value is False: + nodecls = Bool + else: + nodecls = Const + return nodecls(value) + +# introduction of decorators has changed the Function initializer arguments +if sys.version_info >= (2, 4): + def function_factory(name, args, defaults, flag=0, doc=None): + """create and initialize a astng Function node""" + # first argument is now a list of decorators + func = Function(Decorators([]), name, args, defaults, flag, doc, + Stmt([])) + func.code.parent = func + return func + +else: + def function_factory(name, args, defaults, flag=0, doc=None): + """create and initialize a astng Function node""" + func = Function(name, args, defaults, flag, doc, Stmt([])) + func.code.parent = func + return func + +def class_factory(name, basenames=None, doc=None): + """create and initialize a astng Class node""" + klass = Class(name, [], doc, Stmt([])) + bases = [Name(base) for base in basenames] + for base in bases: + base.parent = klass + klass.basenames = basenames + klass.bases = bases + klass.code.parent = klass + klass.locals = {} + klass.instance_attrs = {} + for name, value in ( ('__name__', name), + #('__module__', node.root().name), + ): + const = Const(value) + const.parent = klass + klass.locals[name] = [const] + return klass @@ -166,21 +166,20 @@ class ASTNGBuilder: self._par_stack = [node] self._metaclass = [''] self._global_names = [] - node.parent = None node.globals = node.locals = {} + nodes.init_module(node) for name, value in ( ('__name__', node.name), ('__file__', node.path), ('__doc__', node.doc) ): - const = nodes.Const(value) + const = nodes.const_factory(value) const.parent = node node.locals[name] = [const] - attach___dict__(node) if node.package: # FIXME: List(Const()) - const = nodes.Const(dirname(node.path)) + const = const_factory(value) const.parent = node node.locals['__path__'] = [const] - + attach___dict__(node) def leave_module(self, _): """leave a stmt.Module node -> pop the last item on the stack and check @@ -200,14 +199,16 @@ class ASTNGBuilder: node.instance_attrs = {} node.basenames = [b_node.as_string() for b_node in node.bases] self._push(node) + nodes.init_class(node) for name, value in ( ('__name__', node.name), ('__module__', node.root().name), ('__doc__', node.doc) ): - const = nodes.Const(value) + const = nodes.const_factory(value) const.parent = node node.locals[name] = [const] attach___dict__(node) self._metaclass.append(self._metaclass[-1]) + visit_classdef = visit_class def leave_class(self, node): """leave a stmt.Class node -> pop the last item on the stack @@ -219,6 +220,7 @@ class ASTNGBuilder: # no base classes, detect new / style old style according to # current scope node._newstyle = metaclass == 'type' + leave_classdef = leave_class def visit_function(self, node): """visit a stmt.Function node -> init node and push the corresponding @@ -227,19 +229,22 @@ class ASTNGBuilder: self.visit_default(node) self._global_names.append({}) node.argnames = list(node.argnames) + nodes.init_function(node) if isinstance(node.parent.frame(), nodes.Class): node.type = 'method' if node.name == '__new__': node.type = 'classmethod' self._push(node) register_arguments(node, node.argnames) - + visit_funcdef = visit_function + def leave_function(self, node): """leave a stmt.Function node -> pop the last item on the stack """ self.leave_default(node) self._stack.pop() self._global_names.pop() + leave_funcdef = leave_function def visit_lambda(self, node): """visit a stmt.Lambda node -> init node locals @@ -254,7 +259,8 @@ class ASTNGBuilder: """ self.visit_default(node) node.locals = {} - + visit_generatorexp = visit_genexpr + def visit_global(self, node): """visit a stmt.Global node -> add declared names to locals """ @@ -273,6 +279,7 @@ class ASTNGBuilder: """visit a stmt.Import node -> add imported names to locals """ self.visit_default(node) + nodes.init_import(node) for (name, asname) in node.names: name = asname or name node.parent.set_local(name.split('.')[0], node) @@ -318,23 +325,21 @@ class ASTNGBuilder: klass = node.parent.frame() #print node if isinstance(klass, nodes.Class) and \ - isinstance(node.expr, nodes.CallFunc) and \ - isinstance(node.expr.node, nodes.Name): - func_name = node.expr.node.name + isinstance(node.value, nodes.CallFunc) and \ + isinstance(node.value.node, nodes.Name): + func_name = node.value.node.name if func_name in ('classmethod', 'staticmethod'): for ass_node in node.nodes: - if isinstance(ass_node, nodes.AssName): - try: - meth = klass[ass_node.name] - if isinstance(meth, nodes.Function): - meth.type = func_name - #else: - # print >> sys.stderr, 'FIXME 1', meth - except KeyError: - #print >> sys.stderr, 'FIXME 2', ass_node.name - continue - elif (isinstance(node.nodes[0], nodes.AssName) - and node.nodes[0].name == '__metaclass__'): # XXX check more... + try: + meth = klass[ass_node.name] + if isinstance(meth, nodes.Function): + meth.type = func_name + #else: + # print >> sys.stderr, 'FIXME 1', meth + except (AttributeError, KeyError): + #print >> sys.stderr, 'FIXME 2', ass_node.name + continue + elif getattr(node.targets[0], 'name', None) == '__metaclass__': # XXX check more... self._metaclass[-1] = 'type' # XXX get the actual metaclass def visit_assname(self, node): diff --git a/inference.py b/inference.py index fba9cde3..22f546db 100644 --- a/inference.py +++ b/inference.py @@ -27,12 +27,24 @@ from copy import copy from logilab.common.compat import imap, chain, set -from logilab.astng import MANAGER, YES, InferenceContext, Instance, Generator, \ +from logilab.astng import MANAGER, InferenceContext, \ 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 +from logilab.astng.utils import YES, Instance, Generator, infer_end, end_ass_type +nodes.List._proxied = MANAGER.astng_from_class(list) +nodes.List.__bases__ += (Instance,) +nodes.List.pytype = lambda x: '__builtin__.list' +nodes.Tuple._proxied = MANAGER.astng_from_class(tuple) +nodes.Tuple.__bases__ += (Instance,) +nodes.Tuple.pytype = lambda x: '__builtin__.tuple' +nodes.Dict.__bases__ += (Instance,) +nodes.Dict._proxied = MANAGER.astng_from_class(dict) +nodes.Dict.pytype = lambda x: '__builtin__.dict' + +builtin_astng = nodes.Dict._proxied.root() + # .infer method ############################################################### @@ -548,3 +560,5 @@ nodes.For.loop_node = for_loop_node if nodes.AST_MODE == 'compiler': from logilab.astng._inference_compiler import * +else: #nodes.AST_MODE == '_ast' + from logilab.astng._inference_ast import * @@ -55,6 +55,9 @@ def scope_lookup(self, node, name, offset=0): stmts = node._filter_stmts(self.locals[name], self, offset) except KeyError: stmts = () + except : + print self, self.locals + raise if stmts: return self, stmts if self.parent: @@ -33,7 +33,8 @@ from logilab.common.modutils import NoSourceFile, is_python_source, \ get_module_files, get_source_file from logilab.common.configuration import OptionsProviderMixIn -from logilab.astng import ASTNGBuildingException, Instance, nodes +from logilab.astng import ASTNGBuildingException, nodes +from logilab.astng.utils import Instance def astng_wrapper(func, modname): """wrapper to give to ASTNGManager.project_from_files""" @@ -45,7 +45,7 @@ __docformat__ = "restructuredtext en" try: from logilab.astng._nodes_ast import * AST_MODE = '_ast' -except: +except ImportError: from logilab.astng._nodes_compiler import * AST_MODE = 'compiler' diff --git a/raw_building.py b/raw_building.py index 1ac5715f..6cb905f1 100644 --- a/raw_building.py +++ b/raw_building.py @@ -51,7 +51,7 @@ def attach_dummy_node(node, name, object=_marker): _attach_local_node(node, enode, name) nodes.EmptyNode.has_underlying_object = lambda self: self.object is not _marker - + 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 @@ -78,55 +78,27 @@ def build_module(name, doc=None): def build_class(name, basenames=None, doc=None): """create and initialize a astng Class node""" - klass = nodes.Class(name, [], doc, nodes.Stmt([])) - bases = [nodes.Name(base) for base in basenames] - for base in bases: - base.parent = klass - klass.basenames = basenames - klass.bases = bases - klass.code.parent = klass - klass.locals = {} - klass.instance_attrs = {} + node = nodes.class_factory(name, basenames, doc) + node.basenames = basenames + node.locals = {} + node.instance_attrs = {} for name, value in ( ('__name__', name), #('__module__', node.root().name), ): - const = nodes.Const(value) - const.parent = klass - klass.locals[name] = [const] - return klass + const = nodes.const_factory(value) + const.parent = node + node.locals[name] = [const] + return node -# introduction of decorators has changed the Function initializer arguments -if sys.version_info >= (2, 4): - try: - from compiler.ast import Decorators as BaseDecorators - class Decorators(BaseDecorators): - def __init__(self): - BaseDecorators.__init__(self, [], 0) - except ImportError: - Decorators = list - - def build_function(name, args=None, defaults=None, flag=0, doc=None): - """create and initialize a astng Function node""" - args, defaults = args or [], defaults or [] - # first argument is now a list of decorators - func = nodes.Function(Decorators(), name, args, defaults, flag, doc, - nodes.Stmt([])) - func.code.parent = func - func.locals = {} - if args: - register_arguments(func, args) - return func - -else: - def build_function(name, args=None, defaults=None, flag=0, doc=None): - """create and initialize a astng Function node""" - args, defaults = args or [], defaults or [] - func = nodes.Function(name, args, defaults, flag, doc, nodes.Stmt([])) - func.code.parent = func - func.locals = {} - if args: - register_arguments(func, args) - return func +def build_function(name, args=None, defaults=None, flag=0, doc=None): + """create and initialize a astng Function node""" + args, defaults = args or [], defaults or [] + # first argument is now a list of decorators + func = nodes.function_factory(name, args, defaults, flag, doc) + func.locals = {} + if args: + register_arguments(func, args) + return func def build_name_assign(name, value): diff --git a/scoped_nodes.py b/scoped_nodes.py index fef17fd5..184dc24f 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -19,9 +19,9 @@ below. :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 """ from __future__ import generators @@ -32,13 +32,13 @@ import sys from logilab.common.compat import chain, set -from logilab.astng.utils import extend_class -from logilab.astng import YES, MANAGER, Instance, InferenceContext, copy_context, \ +from logilab.astng import MANAGER, InferenceContext, copy_context, \ unpack_infer, _infer_stmts, \ Class, Const, Dict, Function, GenExpr, Lambda, \ Module, Name, Pass, Raise, Tuple, Yield from logilab.astng import NotFoundError, NoDefault, \ ASTNGBuildingException, InferenceError +from logilab.astng.utils import YES, extend_class, Instance # module class dict/iterator interface ######################################## diff --git a/test/unittest_builder.py b/test/unittest_builder.py index 0a62f262..b75a72af 100644 --- a/test/unittest_builder.py +++ b/test/unittest_builder.py @@ -27,113 +27,115 @@ from logilab.astng import Module, YES, InferenceError import data from data import module as test_module -class TransformerTC(TestCase): - - def setUp(self): - transformer = patchcomptransformer.ASTNGTransformer() - self.astng = transformer.parsesuite(open('data/format.py').read()) - - def test_callfunc_lineno(self): - stmts = self.astng.getChildNodes()[0].nodes - # on line 4: - # function('aeozrijz\ - # earzer', hop) - discard = stmts[0] - self.assertIsInstance(discard, nodes.Discard) - self.assertEquals(discard.fromlineno, 4) - self.assertEquals(discard.tolineno, 5) - callfunc = discard.expr - self.assertIsInstance(callfunc, nodes.CallFunc) - self.assertEquals(callfunc.fromlineno, 4) - self.assertEquals(callfunc.tolineno, 5) - name = callfunc.node - self.assertIsInstance(name, nodes.Name) - self.assertEquals(name.fromlineno, 4) - self.assertEquals(name.tolineno, 4) - strarg = callfunc.args[0] - self.assertIsInstance(strarg, nodes.Const) - self.assertEquals(strarg.fromlineno, 5) # no way for this one (is 4 actually) - self.assertEquals(strarg.tolineno, 5) - namearg = callfunc.args[1] - self.assertIsInstance(namearg, nodes.Name) - self.assertEquals(namearg.fromlineno, 5) - self.assertEquals(namearg.tolineno, 5) - # on line 10: - # fonction(1, - # 2, - # 3, - # 4) - discard = stmts[2] - self.assertIsInstance(discard, nodes.Discard) - self.assertEquals(discard.fromlineno, 10) - self.assertEquals(discard.tolineno, 13) - callfunc = discard.expr - self.assertIsInstance(callfunc, nodes.CallFunc) - self.assertEquals(callfunc.fromlineno, 10) - self.assertEquals(callfunc.tolineno, 13) - name = callfunc.node - self.assertIsInstance(name, nodes.Name) - self.assertEquals(name.fromlineno, 10) - self.assertEquals(name.tolineno, 10) - for i, arg in enumerate(callfunc.args): - self.assertIsInstance(arg, nodes.Const) - self.assertEquals(arg.fromlineno, 10+i) - self.assertEquals(arg.tolineno, 10+i) - - def test_function_lineno(self): - stmts = self.astng.getChildNodes()[0].nodes - # on line 15: - # def definition(a, - # b, - # c): - # return a + b + c - function = stmts[3] - self.assertIsInstance(function, nodes.Function) - self.assertEquals(function.fromlineno, 15) - self.assertEquals(function.tolineno, 17) - code = function.code - self.assertIsInstance(code, nodes.Stmt) -## self.assertEquals(code.fromlineno, 18) -## self.assertEquals(code.tolineno, 18) - return_ = code.nodes[0] - self.assertIsInstance(return_, nodes.Return) - self.assertEquals(return_.fromlineno, 18) - self.assertEquals(return_.tolineno, 18) - - def test_class_lineno(self): - stmts = self.astng.getChildNodes()[0].nodes - # on line 20: - # class debile(dict, - # object): - # pass - class_ = stmts[4] - self.assertIsInstance(class_, nodes.Class) - self.assertEquals(class_.fromlineno, 20) - self.assertEquals(class_.tolineno, 21) - code = class_.code - self.assertIsInstance(code, nodes.Stmt) -## self.assertEquals(code.fromlineno, 18) -## self.assertEquals(code.tolineno, 18) - pass_ = code.nodes[0] - self.assertIsInstance(pass_, nodes.Pass) - self.assertEquals(pass_.fromlineno, 22) - self.assertEquals(pass_.tolineno, 22) - - def test_if_lineno(self): - stmts = self.astng.getChildNodes()[0].nodes - # on line 20: - # if aaaa: pass - # else: - # aaaa,bbbb = 1,2 - # aaaa,bbbb = bbbb,aaaa - if_ = stmts[5] - self.assertIsInstance(if_, nodes.If) - self.assertEquals(if_.fromlineno, 24) - self.assertEquals(if_.tolineno, 24) - else_ = if_.else_ - self.assertIsInstance(else_, nodes.Stmt) - self.assertEquals(else_.fromlineno, 25) - self.assertEquals(else_.tolineno, 27) +if nodes.AST_MODE == 'compiler': + + class TransformerTC(TestCase): + + def setUp(self): + transformer = patchcomptransformer.ASTNGTransformer() + self.astng = transformer.parsesuite(open('data/format.py').read()) + + def test_callfunc_lineno(self): + stmts = self.astng.getChildNodes()[0].nodes + # on line 4: + # function('aeozrijz\ + # earzer', hop) + discard = stmts[0] + self.assertIsInstance(discard, nodes.Discard) + self.assertEquals(discard.fromlineno, 4) + self.assertEquals(discard.tolineno, 5) + callfunc = discard.expr + self.assertIsInstance(callfunc, nodes.CallFunc) + self.assertEquals(callfunc.fromlineno, 4) + self.assertEquals(callfunc.tolineno, 5) + name = callfunc.node + self.assertIsInstance(name, nodes.Name) + self.assertEquals(name.fromlineno, 4) + self.assertEquals(name.tolineno, 4) + strarg = callfunc.args[0] + self.assertIsInstance(strarg, nodes.Const) + self.assertEquals(strarg.fromlineno, 5) # no way for this one (is 4 actually) + self.assertEquals(strarg.tolineno, 5) + namearg = callfunc.args[1] + self.assertIsInstance(namearg, nodes.Name) + self.assertEquals(namearg.fromlineno, 5) + self.assertEquals(namearg.tolineno, 5) + # on line 10: + # fonction(1, + # 2, + # 3, + # 4) + discard = stmts[2] + self.assertIsInstance(discard, nodes.Discard) + self.assertEquals(discard.fromlineno, 10) + self.assertEquals(discard.tolineno, 13) + callfunc = discard.expr + self.assertIsInstance(callfunc, nodes.CallFunc) + self.assertEquals(callfunc.fromlineno, 10) + self.assertEquals(callfunc.tolineno, 13) + name = callfunc.node + self.assertIsInstance(name, nodes.Name) + self.assertEquals(name.fromlineno, 10) + self.assertEquals(name.tolineno, 10) + for i, arg in enumerate(callfunc.args): + self.assertIsInstance(arg, nodes.Const) + self.assertEquals(arg.fromlineno, 10+i) + self.assertEquals(arg.tolineno, 10+i) + + def test_function_lineno(self): + stmts = self.astng.getChildNodes()[0].nodes + # on line 15: + # def definition(a, + # b, + # c): + # return a + b + c + function = stmts[3] + self.assertIsInstance(function, nodes.Function) + self.assertEquals(function.fromlineno, 15) + self.assertEquals(function.tolineno, 17) + code = function.code + self.assertIsInstance(code, nodes.Stmt) + ## self.assertEquals(code.fromlineno, 18) + ## self.assertEquals(code.tolineno, 18) + return_ = code.nodes[0] + self.assertIsInstance(return_, nodes.Return) + self.assertEquals(return_.fromlineno, 18) + self.assertEquals(return_.tolineno, 18) + + def test_class_lineno(self): + stmts = self.astng.getChildNodes()[0].nodes + # on line 20: + # class debile(dict, + # object): + # pass + class_ = stmts[4] + self.assertIsInstance(class_, nodes.Class) + self.assertEquals(class_.fromlineno, 20) + self.assertEquals(class_.tolineno, 21) + code = class_.code + self.assertIsInstance(code, nodes.Stmt) + ## self.assertEquals(code.fromlineno, 18) + ## self.assertEquals(code.tolineno, 18) + pass_ = code.nodes[0] + self.assertIsInstance(pass_, nodes.Pass) + self.assertEquals(pass_.fromlineno, 22) + self.assertEquals(pass_.tolineno, 22) + + def test_if_lineno(self): + stmts = self.astng.getChildNodes()[0].nodes + # on line 20: + # if aaaa: pass + # else: + # aaaa,bbbb = 1,2 + # aaaa,bbbb = bbbb,aaaa + if_ = stmts[5] + self.assertIsInstance(if_, nodes.If) + self.assertEquals(if_.fromlineno, 24) + self.assertEquals(if_.tolineno, 24) + else_ = if_.else_ + self.assertIsInstance(else_, nodes.Stmt) + self.assertEquals(else_.fromlineno, 25) + self.assertEquals(else_.tolineno, 27) class BuilderTC(TestCase): @@ -178,7 +180,9 @@ class BuilderTC(TestCase): self.assert_(time_astng) # unittest_astng = self.builder.inspect_build(unittest) - self.failUnless(isinstance(builtin_astng['None'], nodes.Const), builtin_astng['None']) + self.failUnless(isinstance(builtin_astng['None'], NoneType), builtin_astng['None']) + self.failUnless(isinstance(builtin_astng['True'], Bool), builtin_astng['True']) + self.failUnless(isinstance(builtin_astng['False'], Bool), builtin_astng['False']) self.failUnless(isinstance(builtin_astng['Exception'], nodes.From), builtin_astng['Exception']) self.failUnless(isinstance(builtin_astng['NotImplementedError'], nodes.From)) @@ -53,17 +53,29 @@ class ASTWalker: self.handler = handler self._cache = {} - def walk(self, node): + def walk(self, node, _done=None): """walk on the tree from <node>, getting callbacks from handler """ + if _done is None: + _done = set() + if node in _done: + raise AssertionError((id(node), node.parent)) + _done.add(node) try: self.visit(node) except IgnoreChild: pass else: - for child_node in node.getChildNodes(): - self.walk(child_node) + print 'visit', node, id(node) + try: + for child_node in node.getChildNodes(): + assert child_node is not node + self.walk(child_node, _done) + except AttributeError: + print node.__class__, id(node.__class__) + raise self.leave(node) + assert node.parent is not node def get_callbacks(self, node): """get callbacks from handler for the visited node @@ -178,6 +190,132 @@ def _try_except_from_branch(node, stmt): return 'except', i +# special inference objects ################################################### + +class Yes(object): + """a yes object""" + def __repr__(self): + return 'YES' + def __getattribute__(self, name): + return self + def __call__(self, *args, **kwargs): + return self + +YES = Yes() + +class Proxy: + """a simple proxy object""" + def __init__(self, proxied): + self._proxied = proxied + + def __getattr__(self, name): + return getattr(self._proxied, name) + + def infer(self, context=None): + yield self + + +class InstanceMethod(Proxy): + """a special node representing a function bound to an instance""" + def __repr__(self): + instance = self._proxied.parent.frame() + return 'Bound method %s of %s.%s' % (self._proxied.name, + instance.root().name, + instance.name) + __str__ = __repr__ + + def is_bound(self): + return True + + +class Instance(Proxy): + """a special node representing a class instance""" + def getattr(self, name, context=None, lookupclass=True): + try: + return self._proxied.instance_attr(name, context) + except NotFoundError: + if name == '__class__': + return [self._proxied] + if name == '__name__': + # access to __name__ gives undefined member on class + # instances but not on class objects + raise NotFoundError(name) + if lookupclass: + return self._proxied.getattr(name, context) + raise NotFoundError(name) + + def igetattr(self, name, context=None): + """infered getattr""" + try: + # XXX frame should be self._proxied, or not ? + return _infer_stmts( + self._wrap_attr(self.getattr(name, context, lookupclass=False)), + context, frame=self) + except NotFoundError: + try: + # fallback to class'igetattr since it has some logic to handle + # descriptors + return self._wrap_attr(self._proxied.igetattr(name, context)) + except NotFoundError: + raise InferenceError(name) + + def _wrap_attr(self, attrs): + """wrap bound methods of attrs in a InstanceMethod proxies""" + # Guess which attrs are used in inference. + def wrap(attr): + if isinstance(attr, Function) and attr.type == 'method': + return InstanceMethod(attr) + else: + return attr + return imap(wrap, attrs) + + def infer_call_result(self, caller, context=None): + """infer what's a class instance is returning when called""" + infered = False + for node in self._proxied.igetattr('__call__', context): + for res in node.infer_call_result(caller, context): + infered = True + yield res + if not infered: + raise InferenceError() + + def __repr__(self): + return 'Instance of %s.%s' % (self._proxied.root().name, + self._proxied.name) + __str__ = __repr__ + + def callable(self): + try: + self._proxied.getattr('__call__') + return True + except NotFoundError: + return False + + def pytype(self): + return self._proxied.qname() + +class Generator(Proxy): + """a special node representing a generator""" + def callable(self): + return True + + def pytype(self): + return '__builtin__.generator' + +# additional nodes ########################################################## + +class NoneType(Instance): + """None value (instead of Name('None')""" + +NONE = NoneType(None) + +class Bool(Instance): + """None value (instead of Name('True') / Name('False')""" + def __init__(self, value): + self.value = value +TRUE = Bool(True) +FALSE = Bool(True) + # inference utilities ######################################################### def infer_end(self, context=None): |