diff options
author | Sylvain Th?nault <thenault@gmail.com> | 2014-03-26 14:20:42 +0100 |
---|---|---|
committer | Sylvain Th?nault <thenault@gmail.com> | 2014-03-26 14:20:42 +0100 |
commit | 64c7cd458ed099b24a5e323eef679a9419439c08 (patch) | |
tree | 4c077a4082b35a3b152e31cef57b5b8a9c3c6593 | |
parent | 1175e1a698768aeaddb37dafc916a90f15f86b7a (diff) | |
parent | 0b64b5075802793dc68da98f9591ec927e14ffe4 (diff) | |
download | astroid-64c7cd458ed099b24a5e323eef679a9419439c08.tar.gz |
Merged in flyingsheep/astroid (pull request #15)
AstroidBuilder.string_build was incompatible with file_stream
-rw-r--r-- | ChangeLog | 16 | ||||
-rw-r--r-- | as_string.py | 23 | ||||
-rw-r--r-- | brain/py2stdlib.py | 24 | ||||
-rw-r--r-- | builder.py | 7 | ||||
-rw-r--r-- | manager.py | 27 | ||||
-rw-r--r-- | node_classes.py | 3 | ||||
-rw-r--r-- | nodes.py | 4 | ||||
-rw-r--r-- | protocols.py | 2 | ||||
-rw-r--r-- | raw_building.py | 15 | ||||
-rw-r--r-- | rebuilder.py | 92 | ||||
-rw-r--r-- | scoped_nodes.py | 27 | ||||
-rw-r--r-- | test/unittest_brain.py | 44 | ||||
-rw-r--r-- | test/unittest_builder.py | 95 | ||||
-rw-r--r-- | test/unittest_inference.py | 7 | ||||
-rw-r--r-- | test/unittest_nodes.py | 14 | ||||
-rw-r--r-- | test/unittest_python3.py | 93 | ||||
-rw-r--r-- | test/unittest_regrtest.py | 21 | ||||
-rw-r--r-- | test/unittest_scoped_nodes.py | 74 | ||||
-rw-r--r-- | test_utils.py | 5 |
19 files changed, 493 insertions, 100 deletions
@@ -1,6 +1,22 @@ Change log for the astroid package (used to be astng) ===================================================== +-- + * All class nodes are marked as new style classes for Py3k. + + * Add a `metaclass` function to `Class` nodes to + retrieve their metaclass. + + * Add support for inferring arguments to namedtuple invocations. + + * Make sure that objects returned for namedtuple + inference have parents. + + * Don't crash when inferring nodes from `with` clauses + with multiple context managers. Closes #18. + + * Add a new YieldFrom node. + 2013-10-18 -- 1.0.1 * fix py3k/windows installation issue (issue #4) diff --git a/as_string.py b/as_string.py index fcff19e..ace1c4e 100644 --- a/as_string.py +++ b/as_string.py @@ -144,7 +144,17 @@ class AsStringVisitor(object): """return an astroid.Class node as string""" decorate = node.decorators and node.decorators.accept(self) or '' bases = ', '.join([n.accept(self) for n in node.bases]) - bases = bases and '(%s)' % bases or '' + if sys.version_info[0] == 2: + bases = bases and '(%s)' % bases or '' + else: + metaclass = node.metaclass() + if metaclass: + if bases: + bases = '(%s, metaclass=%s)' % (bases, metaclass.name) + else: + bases = '(metaclass=%s)' % metaclass.name + else: + bases = bases and '(%s)' % bases or '' docs = node.doc and '\n%s"""%s"""' % (INDENT, node.doc) or '' return '\n\n%sclass %s%s:%s\n%s\n' % (decorate, node.name, bases, docs, self._stmt_list( node.body)) @@ -389,6 +399,8 @@ class AsStringVisitor(object): def visit_tuple(self, node): """return an astroid.Tuple node as string""" + if len(node.elts) == 1: + return '(%s, )' % node.elts[0].accept(self) return '(%s)' % ', '.join([child.accept(self) for child in node.elts]) def visit_unaryop(self, node): @@ -455,6 +467,15 @@ class AsStringVisitor3k(AsStringVisitor): """return Starred node as string""" return "*" + node.value.accept(self) + def visit_yieldfrom(self, node): + """ Return an astroid.YieldFrom node as string. """ + yi_val = node.value and (" " + node.value.accept(self)) or "" + expr = 'yield from' + yi_val + if node.parent.is_statement: + return expr + else: + return "(%s)" % (expr,) + def _import_string(names): """return a list of (name, asname) formatted as a string""" diff --git a/brain/py2stdlib.py b/brain/py2stdlib.py index f43bd35..27507ec 100644 --- a/brain/py2stdlib.py +++ b/brain/py2stdlib.py @@ -5,7 +5,8 @@ Currently help understanding of : * hashlib.md5 and hashlib.sha1 """ -from astroid import MANAGER, AsStringRegexpPredicate, UseInferenceDefault, inference_tip +from astroid import MANAGER, AsStringRegexpPredicate, UseInferenceDefault, inference_tip, YES +from astroid import exceptions from astroid import nodes from astroid.builder import AstroidBuilder @@ -183,6 +184,16 @@ MODULE_TRANSFORMS['subprocess'] = subprocess_transform def infer_named_tuple(node, context=None): """Specific inference function for namedtuple CallFunc node""" + def infer_first(node): + try: + value = node.infer().next() + if value is YES: + raise UseInferenceDefault() + else: + return value + except StopIteration: + raise InferenceError() + # node is a CallFunc node, class name as first argument and generated class # attributes as second argument if len(node.args) != 2: @@ -191,15 +202,17 @@ def infer_named_tuple(node, context=None): # namedtuple list of attributes can be a list of strings or a # whitespace-separate string try: - name = node.args[0].value + name = infer_first(node.args[0]).value + names = infer_first(node.args[1]) try: - attributes = node.args[1].value.split() + attributes = names.value.split() except AttributeError: - attributes = [const.value for const in node.args[1].elts] - except AttributeError: + attributes = [infer_first(const).value for const in names.elts] + except (AttributeError, exceptions.InferenceError): raise UseInferenceDefault() # we want to return a Class node instance with proper attributes set class_node = nodes.Class(name, 'docstring') + class_node.parent = node.parent # set base class=tuple class_node.bases.append(nodes.Tuple._proxied) # XXX add __init__(*attributes) method @@ -212,4 +225,3 @@ def infer_named_tuple(node, context=None): MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_named_tuple), AsStringRegexpPredicate('namedtuple', 'func')) - @@ -44,6 +44,7 @@ if sys.version_info >= (3, 0): def open_source_file(filename): byte_stream = open(filename, 'bU') encoding = detect_encoding(byte_stream.readline)[0] + byte_stream.close() stream = open(filename, 'U', encoding=encoding) try: data = stream.read() @@ -101,6 +102,12 @@ class AstroidBuilder(InspectBuilder): # this is a built-in module # get a partial representation by introspection node = self.inspect_build(module, modname=modname, path=path) + # we have to handle transformation by ourselves since the rebuilder + # isn't called for builtin nodes + # + # XXX it's then only called for Module nodes, not for underlying + # nodes + node = self._manager.transform(node) return node def file_build(self, path, modname=None): @@ -273,6 +273,33 @@ class AstroidManager(OptionsProviderMixIn): """ self.transforms.setdefault(node_class, []).append( (transform, predicate) ) + def unregister_transform(self, node_class, transform, predicate=None): + """Unregister the given transform.""" + self.transforms[node_class].remove( (transform, predicate) ) + + def transform(self, node): + """Call matching transforms for the given node if any and return the + transformed node. + """ + try: + transforms = self.transforms[type(node)] + except KeyError: + return node # no transform registered for this class of node + orig_node = node # copy the reference + for transform_func, predicate in transforms: + if predicate is None or predicate(node): + ret = transform_func(node) + # if the transformation function returns something, it's + # expected to be a replacement for the node + if ret is not None: + if node is not orig_node: + # node has already be modified by some previous + # transformation, warn about it + warn('node %s substitued multiple times' % node) + node = ret + return node + + class Project(object): """a project handle a set of modules / packages""" diff --git a/node_classes.py b/node_classes.py index 97bd907..01dc8d9 100644 --- a/node_classes.py +++ b/node_classes.py @@ -888,6 +888,9 @@ class Yield(NodeNG): _astroid_fields = ('value',) value = None +class YieldFrom(Yield): + """ Class representing a YieldFrom node. """ + # constants ############################################################## CONST_CLS = { @@ -43,7 +43,7 @@ from astroid.node_classes import Arguments, AssAttr, Assert, Assign, \ Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \ From, Getattr, Global, If, IfExp, Import, Index, Keyword, \ List, Name, Nonlocal, Pass, Print, Raise, Return, Set, Slice, Starred, Subscript, \ - TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \ + TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, YieldFrom, \ const_factory from astroid.scoped_nodes import Module, GenExpr, Lambda, DictComp, \ ListComp, SetComp, Function, Class @@ -68,6 +68,6 @@ ALL_NODE_CLASSES = ( TryExcept, TryFinally, Tuple, UnaryOp, While, With, - Yield, + Yield, YieldFrom ) diff --git a/protocols.py b/protocols.py index a26986c..e66b802 100644 --- a/protocols.py +++ b/protocols.py @@ -311,6 +311,8 @@ nodes.ExceptHandler.assigned_stmts = raise_if_nothing_infered(excepthandler_assi def with_assigned_stmts(self, node, context=None, asspath=None): if asspath is None: for _, vars in self.items: + if vars is None: + continue for lst in vars.infer(context): if isinstance(lst, (nodes.Tuple, nodes.List)): for item in lst.nodes: diff --git a/raw_building.py b/raw_building.py index 756bec1..720cdce 100644 --- a/raw_building.py +++ b/raw_building.py @@ -24,7 +24,7 @@ __docformat__ = "restructuredtext en" import sys from os.path import abspath from inspect import (getargspec, isdatadescriptor, isfunction, ismethod, - ismethoddescriptor, isclass, isbuiltin) + ismethoddescriptor, isclass, isbuiltin, ismodule) from astroid.node_classes import CONST_CLS from astroid.nodes import (Module, Class, Const, const_factory, From, @@ -35,6 +35,14 @@ MANAGER = AstroidManager() _CONSTANTS = tuple(CONST_CLS) # the keys of CONST_CLS eg python builtin types +def _io_discrepancy(member): + # _io module names itself `io`: http://bugs.python.org/issue18602 + member_self = getattr(member, '__self__', None) + return (member_self and + ismodule(member_self) and + member_self.__name__ == '_io' and + member.__module__ == 'io') + def _attach_local_node(parent, node, name): node.name = name # needed by add_local_node parent.add_local_node(node) @@ -249,8 +257,9 @@ class InspectBuilder(object): attach_dummy_node(node, name, member) else: object_build_function(node, member, name) - elif isbuiltin(member): - if self.imported_member(node, member, name): + elif isbuiltin(member): + if (not _io_discrepancy(member) and + self.imported_member(node, member, name)): #if obj is object: # print 'skippp', obj, name, member continue diff --git a/rebuilder.py b/rebuilder.py index 7f4c7a7..ef8e763 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -21,7 +21,7 @@ order to get a single Astroid representation import sys from warnings import warn -from _ast import (Expr as Discard, Str, +from _ast import (Expr as Discard, Str, Name, Attribute, # binary operators Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, LShift, RShift, @@ -87,6 +87,8 @@ REDIRECT = {'arguments': 'Arguments', 'keyword': 'Keyword', 'Repr': 'Backquote', } +PY3K = sys.version_info >= (3, 0) +PY34 = sys.version_info >= (3, 4) def _init_set_doc(node, newnode): newnode.doc = None @@ -114,7 +116,19 @@ def _set_infos(oldnode, newnode, parent): newnode.col_offset = oldnode.col_offset newnode.set_line_info(newnode.last_child()) # set_line_info accepts None +def _infer_metaclass(node): + if isinstance(node, Name): + return node.id + elif isinstance(node, Attribute): + return node.attr +def _create_yield_node(node, parent, rebuilder, factory): + newnode = factory() + _lineno_parent(node, newnode, parent) + if node.value is not None: + newnode.value = rebuilder.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode class TreeRebuilder(object): @@ -128,25 +142,7 @@ class TreeRebuilder(object): self._from_nodes = [] self._delayed_assattr = [] self._visit_meths = {} - - def _transform(self, node): - try: - transforms = self._manager.transforms[type(node)] - except KeyError: - return node # no transform registered for this class of node - orig_node = node # copy the reference - for transform_func, predicate in transforms: - if predicate is None or predicate(node): - ret = transform_func(node) - # if the transformation function returns something, it's - # expected to be a replacement for the node - if ret is not None: - if node is not orig_node: - # node has already be modified by some previous - # transformation, warn about it - warn('node %s substitued multiple times' % node) - node = ret - return node + self._transform = manager.transform def visit_module(self, node, modname, package): """visit a Module node by returning a fresh instance of it""" @@ -187,13 +183,20 @@ class TreeRebuilder(object): newnode.defaults = [self.visit(child, newnode) for child in node.defaults] newnode.kwonlyargs = [] newnode.kw_defaults = [] - newnode.vararg = node.vararg - newnode.kwarg = node.kwarg + vararg, kwarg = node.vararg, node.kwarg + # change added in 82732 (7c5c678e4164), vararg and kwarg + # are instances of `_ast.arg`, not strings + if vararg and PY34: + vararg = vararg.arg + if kwarg and PY34: + kwarg = kwarg.arg + newnode.vararg = vararg + newnode.kwarg = kwarg # save argument names in locals: - if node.vararg: - newnode.parent.set_local(newnode.vararg, newnode) - if node.kwarg: - newnode.parent.set_local(newnode.kwarg, newnode) + if vararg: + newnode.parent.set_local(vararg, newnode) + if kwarg: + newnode.parent.set_local(kwarg, newnode) newnode.set_line_info(newnode.last_child()) return newnode @@ -245,7 +248,7 @@ class TreeRebuilder(object): continue elif getattr(newnode.targets[0], 'name', None) == '__metaclass__': # XXX check more... - self._metaclass[-1] = 'type' # XXX get the actual metaclass + self._metaclass[-1] = _infer_metaclass(node.value) newnode.set_line_info(newnode.last_child()) return newnode @@ -328,10 +331,13 @@ class TreeRebuilder(object): newnode.decorators = self.visit_decorators(node, newnode) newnode.set_line_info(newnode.last_child()) metaclass = self._metaclass.pop() - if not newnode.bases: - # no base classes, detect new / style old style according to - # current scope - newnode._newstyle = metaclass == 'type' + if PY3K: + newnode._newstyle = True + else: + if not newnode.bases: + # no base classes, detect new / style old style according to + # current scope + newnode._newstyle = metaclass in ('type', 'ABCMeta') newnode.parent.frame().set_local(newnode.name, newnode) return newnode @@ -821,13 +827,7 @@ class TreeRebuilder(object): def visit_yield(self, node, parent): """visit a Yield node by returning a fresh instance of it""" - newnode = new.Yield() - _lineno_parent(node, newnode, parent) - if node.value is not None: - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - + return _create_yield_node(node, parent, self, new.Yield) class TreeRebuilder3k(TreeRebuilder): """extend and overwrite TreeRebuilder for python3k""" @@ -838,6 +838,12 @@ class TreeRebuilder3k(TreeRebuilder): # XXX or we should instead introduce a Arg node in astroid ? return self.visit_assname(node, parent, node.arg) + def visit_nameconstant(self, node, parent): + # in Python 3.4 we have NameConstant for True / False / None + newnode = new.Const(node.value) + _set_infos(node, newnode, parent) + return newnode + def visit_arguments(self, node, parent): newnode = super(TreeRebuilder3k, self).visit_arguments(node, parent) self.asscontext = "Ass" @@ -932,7 +938,15 @@ class TreeRebuilder3k(TreeRebuilder): return newnode def visit_yieldfrom(self, node, parent): - return self.visit_yield(node, parent) + return _create_yield_node(node, parent, self, new.YieldFrom) + + def visit_class(self, node, parent): + newnode = super(TreeRebuilder3k, self).visit_class(node, parent) + for keyword in node.keywords: + if keyword.arg == 'metaclass': + newnode._metaclass = self.visit(keyword, newnode).value + break + return newnode if sys.version_info >= (3, 0): TreeRebuilder = TreeRebuilder3k diff --git a/scoped_nodes.py b/scoped_nodes.py index 4a4d633..7141ef7 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -36,7 +36,7 @@ from logilab.common.decorators import cached from astroid.exceptions import NotFoundError, \ AstroidBuildingException, InferenceError from astroid.node_classes import Const, DelName, DelAttr, \ - Dict, From, List, Pass, Raise, Return, Tuple, Yield, \ + Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ LookupMixIn, const_factory as cf, unpack_infer from astroid.bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ @@ -628,7 +628,8 @@ class Function(Statement, Lambda): """return true if this is a generator function""" # XXX should be flagged, not computed try: - return self.nodes_of_class(Yield, skip_klass=(Function, Lambda)).next() + return self.nodes_of_class((Yield, YieldFrom), + skip_klass=(Function, Lambda)).next() except StopIteration: return False @@ -998,3 +999,25 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): yield iface if missing: raise InferenceError() + + _metaclass = None + def metaclass(self): + """ Return the metaclass of this class """ + if self._metaclass: + # Expects this from Py3k TreeRebuilder + try: + return next(self._metaclass.infer()) + except InferenceError: + return + + try: + meta = self.getattr('__metaclass__')[0] + except NotFoundError: + return + try: + infered = meta.infer().next() + except InferenceError: + return + if infered is YES: # don't expose this + return None + return infered diff --git a/test/unittest_brain.py b/test/unittest_brain.py index 5f1da65..7b750cf 100644 --- a/test/unittest_brain.py +++ b/test/unittest_brain.py @@ -16,9 +16,12 @@ # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. """Tests for basic functionality in astroid.brain.""" -from astroid import MANAGER from logilab.common.testlib import TestCase, unittest_main +from astroid import MANAGER +from astroid import bases +from astroid import test_utils +import astroid class HashlibTC(TestCase): def test_hashlib(self): @@ -35,5 +38,44 @@ class HashlibTC(TestCase): self.assertEqual(len(class_obj['digest'].args.args), 1) self.assertEqual(len(class_obj['hexdigest'].args.args), 1) + +class NamedTupleTest(TestCase): + def test_namedtuple_base(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + class X(namedtuple("X", ["a", "b", "c"])): + pass + """) + self.assertEqual( + [anc.name for anc in klass.ancestors()], + ['X', 'tuple', 'object']) + for anc in klass.ancestors(): + self.assertFalse(anc.parent is None) + + def test_namedtuple_inference(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + name = "X" + fields = ["a", "b", "c"] + class X(namedtuple(name, fields)): + pass + """) + for base in klass.ancestors(): + if base.name == 'X': + break + self.assertItemsEqual(["a", "b", "c"], base.instance_attrs.keys()) + + def test_namedtuple_inference_failure(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + def foo(fields): + return __(namedtuple("foo", fields)) + """) + self.assertIs(bases.YES, klass.infer().next()) + + if __name__ == '__main__': unittest_main() diff --git a/test/unittest_builder.py b/test/unittest_builder.py index a514dfe..5411539 100644 --- a/test/unittest_builder.py +++ b/test/unittest_builder.py @@ -30,7 +30,7 @@ from astroid.bases import YES, BUILTINS from astroid.manager import AstroidManager MANAGER = AstroidManager() - +PY3K = sys.version_info >= (3, 0) from unittest_inference import get_name_node @@ -268,9 +268,9 @@ class BuilderTC(TestCase): def test_inspect_build0(self): """test astroid tree build from a living object""" - builtin_astroid = MANAGER.ast_from_module_name(BUILTINS) + builtin_ast = MANAGER.ast_from_module_name(BUILTINS) if sys.version_info < (3, 0): - fclass = builtin_astroid['file'] + fclass = builtin_ast['file'] self.assertIn('name', fclass) self.assertIn('mode', fclass) self.assertIn('read', fclass) @@ -278,33 +278,33 @@ class BuilderTC(TestCase): self.assertTrue(fclass.pytype(), '%s.type' % BUILTINS) self.assertIsInstance(fclass['read'], nodes.Function) # check builtin function has args.args == None - dclass = builtin_astroid['dict'] + dclass = builtin_ast['dict'] self.assertIsNone(dclass['has_key'].args.args) # just check type and object are there - builtin_astroid.getattr('type') - objectastroid = builtin_astroid.getattr('object')[0] + builtin_ast.getattr('type') + objectastroid = builtin_ast.getattr('object')[0] self.assertIsInstance(objectastroid.getattr('__new__')[0], nodes.Function) # check open file alias - builtin_astroid.getattr('open') + builtin_ast.getattr('open') # check 'help' is there (defined dynamically by site.py) - builtin_astroid.getattr('help') + builtin_ast.getattr('help') # check property has __init__ - pclass = builtin_astroid['property'] + pclass = builtin_ast['property'] self.assertIn('__init__', pclass) - self.assertIsInstance(builtin_astroid['None'], nodes.Const) - self.assertIsInstance(builtin_astroid['True'], nodes.Const) - self.assertIsInstance(builtin_astroid['False'], nodes.Const) + self.assertIsInstance(builtin_ast['None'], nodes.Const) + self.assertIsInstance(builtin_ast['True'], nodes.Const) + self.assertIsInstance(builtin_ast['False'], nodes.Const) if sys.version_info < (3, 0): - self.assertIsInstance(builtin_astroid['Exception'], nodes.From) - self.assertIsInstance(builtin_astroid['NotImplementedError'], nodes.From) + self.assertIsInstance(builtin_ast['Exception'], nodes.From) + self.assertIsInstance(builtin_ast['NotImplementedError'], nodes.From) else: - self.assertIsInstance(builtin_astroid['Exception'], nodes.Class) - self.assertIsInstance(builtin_astroid['NotImplementedError'], nodes.Class) + self.assertIsInstance(builtin_ast['Exception'], nodes.Class) + self.assertIsInstance(builtin_ast['NotImplementedError'], nodes.Class) def test_inspect_build1(self): - time_astroid = MANAGER.ast_from_module_name('time') - self.assertTrue(time_astroid) - self.assertEqual(time_astroid['time'].args.defaults, []) + time_ast = MANAGER.ast_from_module_name('time') + self.assertTrue(time_ast) + self.assertEqual(time_ast['time'].args.defaults, []) def test_inspect_build2(self): """test astroid tree build from a living object""" @@ -313,10 +313,10 @@ class BuilderTC(TestCase): except ImportError: self.skipTest('test skipped: mxDateTime is not available') else: - dt_astroid = self.builder.inspect_build(DateTime) - dt_astroid.getattr('DateTime') + dt_ast = self.builder.inspect_build(DateTime) + dt_ast.getattr('DateTime') # this one is failing since DateTimeType.__module__ = 'builtins' ! - #dt_astroid.getattr('DateTimeType') + #dt_ast.getattr('DateTimeType') def test_inspect_build3(self): self.builder.inspect_build(unittest) @@ -326,8 +326,8 @@ class BuilderTC(TestCase): if sys.version_info >= (3, 0): self.skipTest('The module "exceptions" is gone in py3.x') import exceptions - builtin_astroid = self.builder.inspect_build(exceptions) - fclass = builtin_astroid['OSError'] + builtin_ast = self.builder.inspect_build(exceptions) + fclass = builtin_ast['OSError'] # things like OSError.strerror are now (2.5) data descriptors on the # class instead of entries in the __dict__ of an instance container = fclass @@ -336,20 +336,33 @@ class BuilderTC(TestCase): self.assertIn('filename', container) def test_inspect_build_type_object(self): - builtin_astroid = MANAGER.ast_from_module_name(BUILTINS) + builtin_ast = MANAGER.ast_from_module_name(BUILTINS) - infered = list(builtin_astroid.igetattr('object')) + infered = list(builtin_ast.igetattr('object')) self.assertEqual(len(infered), 1) infered = infered[0] self.assertEqual(infered.name, 'object') infered.as_string() # no crash test - infered = list(builtin_astroid.igetattr('type')) + infered = list(builtin_ast.igetattr('type')) self.assertEqual(len(infered), 1) infered = infered[0] self.assertEqual(infered.name, 'type') infered.as_string() # no crash test + def test_inspect_transform_module(self): + # ensure no cached version of the time module + MANAGER._mod_file_cache.pop(('time', None), None) + def transform_time(node): + if node.name == 'time': + node.transformed = True + MANAGER.register_transform(nodes.Module, transform_time) + try: + time_ast = MANAGER.ast_from_module_name('time') + self.assertTrue(getattr(time_ast, 'transformed', False)) + finally: + MANAGER.unregister_transform(nodes.Module, transform_time) + def test_package_name(self): """test base properties and method of a astroid module""" datap = self.builder.file_build(join(DATA, '__init__.py'), 'data') @@ -376,8 +389,8 @@ def yiell(): self.assertIsInstance(func.body[1].body[0].value, nodes.Yield) def test_object(self): - obj_astroid = self.builder.inspect_build(object) - self.assertIn('__setattr__', obj_astroid) + obj_ast = self.builder.inspect_build(object) + self.assertIn('__setattr__', obj_ast) def test_newstyle_detection(self): data = ''' @@ -401,13 +414,18 @@ class E(A): class F: "new style" ''' - mod_astroid = self.builder.string_build(data, __name__, __file__) - self.assertFalse(mod_astroid['A'].newstyle) - self.assertFalse(mod_astroid['B'].newstyle) - self.assertTrue(mod_astroid['C'].newstyle) - self.assertTrue(mod_astroid['D'].newstyle) - self.assertFalse(mod_astroid['E'].newstyle) - self.assertTrue(mod_astroid['F'].newstyle) + mod_ast = self.builder.string_build(data, __name__, __file__) + if PY3K: + self.assertTrue(mod_ast['A'].newstyle) + self.assertTrue(mod_ast['B'].newstyle) + self.assertTrue(mod_ast['E'].newstyle) + else: + self.assertFalse(mod_ast['A'].newstyle) + self.assertFalse(mod_ast['B'].newstyle) + self.assertFalse(mod_ast['E'].newstyle) + self.assertTrue(mod_ast['C'].newstyle) + self.assertTrue(mod_ast['D'].newstyle) + self.assertTrue(mod_ast['F'].newstyle) def test_globals(self): data = ''' @@ -520,7 +538,10 @@ class FileBuildTC(TestCase): self.assertEqual(klass.parent.frame(), module) self.assertEqual(klass.root(), module) self.assertEqual(klass.basenames, []) - self.assertEqual(klass.newstyle, False) + if PY3K: + self.assertTrue(klass.newstyle) + else: + self.assertFalse(klass.newstyle) def test_class_locals(self): """test the 'locals' dictionary of a astroid class""" diff --git a/test/unittest_inference.py b/test/unittest_inference.py index b5890e6..ce02f99 100644 --- a/test/unittest_inference.py +++ b/test/unittest_inference.py @@ -52,6 +52,11 @@ if sys.version_info < (3, 0): else: EXC_MODULE = BUILTINS +if sys.version_info < (3, 4): + SITE = 'site' +else: + SITE = '_sitebuiltins' + class InferenceTC(TestCase): CODE = ''' @@ -700,7 +705,7 @@ help() self.assertEqual(len(infered), 1, infered) self.assertIsInstance(infered[0], Instance) self.assertEqual(str(infered[0]), - 'Instance of site._Helper') + 'Instance of %s._Helper' % SITE) def test_builtin_open(self): code = ''' diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py index 14813bb..e04218e 100644 --- a/test/unittest_nodes.py +++ b/test/unittest_nodes.py @@ -35,6 +35,15 @@ abuilder = builder.AstroidBuilder() class AsString(testlib.TestCase): + def test_tuple_as_string(self): + def build(string): + return abuilder.string_build(string).body[0].value + + self.assertEqual(build('1,').as_string(), '(1, )') + self.assertEqual(build('1, 2, 3').as_string(), '(1, 2, 3)') + self.assertEqual(build('(1, )').as_string(), '(1, )') + self.assertEqual(build('1, 2, 3').as_string(), '(1, 2, 3)') + def test_varargs_kwargs_as_string(self): ast = abuilder.string_build( 'raise_string(*args, **kwargs)').body[0] self.assertEqual(ast.as_string(), 'raise_string(*args, **kwargs)') @@ -233,7 +242,10 @@ class ImportNodeTC(testlib.TestCase): self.assertTrue(isinstance(spawn, nodes.Class), spawn) self.assertEqual(spawn.root().name, 'logilab.common.shellutils') self.assertEqual(spawn.qname(), 'logilab.common.shellutils.Execute') - self.assertEqual(spawn.pytype(), '%s.classobj' % BUILTINS) + if spawn.newstyle: + self.assertEqual(spawn.pytype(), '%s.type' % BUILTINS) + else: + self.assertEqual(spawn.pytype(), '%s.classobj' % BUILTINS) abspath = MODULE2.igetattr('abspath').next() self.assertTrue(isinstance(abspath, nodes.Function), abspath) self.assertEqual(abspath.root().name, 'os.path') diff --git a/test/unittest_python3.py b/test/unittest_python3.py index 84650fc..96de632 100644 --- a/test/unittest_python3.py +++ b/test/unittest_python3.py @@ -16,12 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License along # with astroid. If not, see <http://www.gnu.org/licenses/>. import sys +from textwrap import dedent from logilab.common.testlib import TestCase, unittest_main, require_version -from astroid.node_classes import Assign +from astroid.node_classes import Assign, Discard, YieldFrom from astroid.manager import AstroidManager from astroid.builder import AstroidBuilder +from astroid.scoped_nodes import Class, Function class Python3TC(TestCase): @@ -39,5 +41,94 @@ class Python3TC(TestCase): self.assertTrue(isinstance(node.ass_type(), Assign)) + @require_version('3.3') + def test_yield_from(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertIsInstance(func, Function) + yieldfrom_stmt = func.body[0] + + self.assertIsInstance(yieldfrom_stmt, Discard) + self.assertIsInstance(yieldfrom_stmt.value, YieldFrom) + self.assertEqual(yieldfrom_stmt.as_string(), + 'yield from iter([1, 2])') + + @require_version('3.3') + def test_yield_from_is_generator(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertIsInstance(func, Function) + self.assertTrue(func.is_generator()) + + @require_version('3.3') + def test_yield_from_as_string(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + value = yield from other() + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertEqual(func.as_string().strip(), body.strip()) + + # metaclass tests + + @require_version('3.0') + def test_simple_metaclass(self): + astroid = self.builder.string_build("class Test(metaclass=type): pass") + klass = astroid.body[0] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, Class) + self.assertEqual(metaclass.name, 'type') + + @require_version('3.0') + def test_metaclass_error(self): + astroid = self.builder.string_build("class Test(metaclass=typ): pass") + klass = astroid.body[0] + self.assertFalse(klass.metaclass()) + + @require_version('3.0') + def test_metaclass_imported(self): + astroid = self.builder.string_build(dedent(""" + from abc import ABCMeta + class Test(metaclass=ABCMeta): pass""")) + klass = astroid.body[1] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, Class) + self.assertEqual(metaclass.name, 'ABCMeta') + + @require_version('3.0') + def test_as_string(self): + body = dedent(""" + from abc import ABCMeta + class Test(metaclass=ABCMeta): pass""") + astroid = self.builder.string_build(body) + klass = astroid.body[1] + + self.assertEqual(klass.as_string(), + '\n\nclass Test(metaclass=ABCMeta):\n pass\n') + + @require_version('3.0') + def test_old_syntax_works(self): + astroid = self.builder.string_build(dedent(""" + class Test: + __metaclass__ = type + class SubTest(Test): pass + """)) + klass = astroid['SubTest'] + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, Class) + self.assertEqual(metaclass.name, 'type') + if __name__ == '__main__': unittest_main() diff --git a/test/unittest_regrtest.py b/test/unittest_regrtest.py index 5521210..265fc14 100644 --- a/test/unittest_regrtest.py +++ b/test/unittest_regrtest.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with astroid. If not, see <http://www.gnu.org/licenses/>. -from logilab.common.testlib import unittest_main, TestCase +from logilab.common.testlib import unittest_main, TestCase, require_version from astroid import ResolveError, MANAGER, Instance, nodes, YES, InferenceError from astroid.builder import AstroidBuilder @@ -135,6 +135,25 @@ multiply(1, 2, 3) self.assertEqual(len(infered), 1) self.assertIsInstance(infered[0], Instance) + @require_version('3.0') + def test_nameconstant(self): + # used to fail for Python 3.4 + builder = AstroidBuilder() + astroid = builder.string_build("def test(x=True): pass") + default = astroid.body[0].args.args[0] + self.assertEqual(default.name, 'x') + self.assertEqual(next(default.infer()).value, True) + + def test_with_infer_assnames(self): + builder = AstroidBuilder() + data = """ +with open('a.txt') as stream, open('b.txt'): + stream.read() +""" + astroid = builder.string_build(data, __name__, __file__) + # Used to crash due to the fact that the second + # context manager didn't use an assignment name. + list(astroid.nodes_of_class(nodes.CallFunc))[-1].infered() class Whatever(object): a = property(lambda x: x, lambda x: x) diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py index 003a46e..a349ca0 100644 --- a/test/unittest_scoped_nodes.py +++ b/test/unittest_scoped_nodes.py @@ -23,8 +23,9 @@ from __future__ import with_statement import sys from os.path import join, abspath, dirname +from textwrap import dedent -from logilab.common.testlib import TestCase, unittest_main +from logilab.common.testlib import TestCase, unittest_main, require_version from astroid import builder, nodes, scoped_nodes, \ InferenceError, NotFoundError, NoDefault @@ -39,6 +40,7 @@ MODULE2 = abuilder.file_build(join(DATA, 'module2.py'), 'data.module2') NONREGR = abuilder.file_build(join(DATA, 'nonregr.py'), 'data.nonregr') PACK = abuilder.file_build(join(DATA, '__init__.py'), 'data') +PY3K = sys.version_info >= (3, 0) def _test_dict_interface(self, node, test_attr): self.assertIs(node[test_attr], node[test_attr]) @@ -397,7 +399,8 @@ class ClassNodeTC(TestCase): self.assertIsInstance(cls.getattr('__module__')[0], nodes.Const) self.assertEqual(cls.getattr('__module__')[0].value, 'data.module') self.assertEqual(len(cls.getattr('__dict__')), 1) - self.assertRaises(NotFoundError, cls.getattr, '__mro__') + if not cls.newstyle: + self.assertRaises(NotFoundError, cls.getattr, '__mro__') for cls in (nodes.List._proxied, nodes.Const(1)._proxied): self.assertEqual(len(cls.getattr('__bases__')), 1) self.assertEqual(len(cls.getattr('__name__')), 1) @@ -676,6 +679,73 @@ def g2(): self.assertEqual(astroid['g2'].fromlineno, 9) self.assertEqual(astroid['g2'].tolineno, 10) + def test_simple_metaclass(self): + astroid = abuilder.string_build(dedent(""" + class Test(object): + __metaclass__ = type + """)) + klass = astroid['Test'] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.Class) + self.assertEqual(metaclass.name, 'type') + + def test_metaclass_error(self): + astroid = abuilder.string_build(dedent(""" + class Test(object): + __metaclass__ = typ + """)) + klass = astroid['Test'] + self.assertFalse(klass.metaclass()) + + @require_version('2.7') + def test_metaclass_imported(self): + astroid = abuilder.string_build(dedent(""" + from abc import ABCMeta + class Test(object): + __metaclass__ = ABCMeta + """)) + klass = astroid['Test'] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.Class) + self.assertEqual(metaclass.name, 'ABCMeta') + + @require_version('2.7') + def test_newstyle_and_metaclass_good(self): + astroid = abuilder.string_build(dedent(""" + from abc import ABCMeta + class Test: + __metaclass__ = ABCMeta + """)) + klass = astroid['Test'] + self.assertTrue(klass.newstyle) + + def test_newstyle_and_metaclass_bad(self): + astroid = abuilder.string_build(dedent(""" + class Test: + __metaclass__ = int + """)) + klass = astroid['Test'] + if PY3K: + self.assertTrue(klass.newstyle) + else: + self.assertFalse(klass.newstyle) + + @require_version('2.7') + def test_parent_metaclass(self): + astroid = abuilder.string_build(dedent(""" + from abc import ABCMeta + class Test: + __metaclass__ = ABCMeta + class SubTest(Test): pass + """)) + klass = astroid['SubTest'] + self.assertTrue(klass.newstyle) + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.Class) + self.assertEqual(metaclass.name, 'ABCMeta') + __all__ = ('ModuleNodeTC', 'ImportNodeTC', 'FunctionNodeTC', 'ClassNodeTC') diff --git a/test_utils.py b/test_utils.py index 8e54eb9..144d19f 100644 --- a/test_utils.py +++ b/test_utils.py @@ -3,7 +3,6 @@ import textwrap from astroid import nodes from astroid import builder - # The name of the transient function that is used to # wrap expressions to be extracted when calling # extract_node. @@ -37,7 +36,7 @@ def _extract_expressions(node): # be lists or tuples, in which case the elements need to be checked. # When we find it, replace it by real_expr, so that the AST looks # like no call to _TRANSIENT_FUNCTION ever took place. - for name in node.parent._astng_fields: + for name in node.parent._astroid_fields: child = getattr(node.parent, name) if isinstance(child, (list, tuple)): for idx, compound_child in enumerate(child): @@ -145,7 +144,7 @@ def extract_node(code, module_name=''): return node.value else: return node - + requested_lines = [] for idx, line in enumerate(code.splitlines()): if line.strip().endswith(_STATEMENT_SELECTOR): |