summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <thenault@gmail.com>2014-03-26 14:20:42 +0100
committerSylvain Th?nault <thenault@gmail.com>2014-03-26 14:20:42 +0100
commit64c7cd458ed099b24a5e323eef679a9419439c08 (patch)
tree4c077a4082b35a3b152e31cef57b5b8a9c3c6593
parent1175e1a698768aeaddb37dafc916a90f15f86b7a (diff)
parent0b64b5075802793dc68da98f9591ec927e14ffe4 (diff)
downloadastroid-64c7cd458ed099b24a5e323eef679a9419439c08.tar.gz
Merged in flyingsheep/astroid (pull request #15)
AstroidBuilder.string_build was incompatible with file_stream
-rw-r--r--ChangeLog16
-rw-r--r--as_string.py23
-rw-r--r--brain/py2stdlib.py24
-rw-r--r--builder.py7
-rw-r--r--manager.py27
-rw-r--r--node_classes.py3
-rw-r--r--nodes.py4
-rw-r--r--protocols.py2
-rw-r--r--raw_building.py15
-rw-r--r--rebuilder.py92
-rw-r--r--scoped_nodes.py27
-rw-r--r--test/unittest_brain.py44
-rw-r--r--test/unittest_builder.py95
-rw-r--r--test/unittest_inference.py7
-rw-r--r--test/unittest_nodes.py14
-rw-r--r--test/unittest_python3.py93
-rw-r--r--test/unittest_regrtest.py21
-rw-r--r--test/unittest_scoped_nodes.py74
-rw-r--r--test_utils.py5
19 files changed, 493 insertions, 100 deletions
diff --git a/ChangeLog b/ChangeLog
index 9a1cb4b..6881c7e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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'))
-
diff --git a/builder.py b/builder.py
index d4a9a8e..0271f71 100644
--- a/builder.py
+++ b/builder.py
@@ -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):
diff --git a/manager.py b/manager.py
index 53b1a9c..f5e8132 100644
--- a/manager.py
+++ b/manager.py
@@ -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 = {
diff --git a/nodes.py b/nodes.py
index 9fdc67e..263ab47 100644
--- a/nodes.py
+++ b/nodes.py
@@ -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):