summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-08-11 12:59:24 +0300
committercpopa <devnull@localhost>2014-08-11 12:59:24 +0300
commit4165f185f92bd60b436ae1138f13d5747bbb2497 (patch)
tree68329839f49b82efe9928b4bad902518ae5cc58b
parent653b7d3f52d5cdfc86feb848af699844fc9dca9d (diff)
downloadastroid-4165f185f92bd60b436ae1138f13d5747bbb2497.tar.gz
Name inference will lookup in the parent function of the current scope, in case searching in the current scope fails.
-rw-r--r--ChangeLog4
-rw-r--r--inference.py28
-rw-r--r--test/unittest_inference.py17
3 files changed, 48 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index e2780e8..a388957 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,10 @@ Change log for the astroid package (used to be astng)
* enum members knows about the methods from the enum class.
+ * Name inference will lookup in the parent function
+ of the current scope, in case searching in the current scope
+ fails.
+
2014-07-25 -- 1.2.0
* Function nodes can detect decorator call chain and see if they are
diff --git a/inference.py b/inference.py
index 5bdb0fc..c36f695 100644
--- a/inference.py
+++ b/inference.py
@@ -142,11 +142,37 @@ nodes.Tuple._infer = infer_end
nodes.Dict._infer = infer_end
nodes.Set._infer = infer_end
+def _higher_function_scope(node):
+ """ Search for the first function which encloses the given
+ scope. This can be used for looking up in that function's
+ scope, in case looking up in a lower scope for a particular
+ name fails.
+
+ :param node: A scope node.
+ :returns:
+ ``None``, if no parent function scope was found,
+ otherwise an instance of :class:`astroid.scoped_nodes.Function`,
+ which encloses the given node.
+ """
+ current = node
+ while current.parent and not isinstance(current.parent, nodes.Function):
+ current = current.parent
+ if current and current.parent:
+ return current.parent
+
def infer_name(self, context=None):
"""infer a Name: use name lookup rules"""
frame, stmts = self.lookup(self.name)
if not stmts:
- raise UnresolvableName(self.name)
+ # Try to see if the name is enclosed in a nested function
+ # and use the higher (first function) scope for searching.
+ # TODO: should this be promoted to other nodes as well?
+ parent_function = _higher_function_scope(self.scope())
+ if parent_function:
+ _, stmts = parent_function.lookup(self.name)
+
+ if not stmts:
+ raise UnresolvableName(self.name)
context = context.clone()
context.lookupname = self.name
return _infer_stmts(stmts, context, frame)
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
index 7e70ed3..06f5abb 100644
--- a/test/unittest_inference.py
+++ b/test/unittest_inference.py
@@ -20,6 +20,7 @@
from os.path import join, dirname, abspath
import sys
from StringIO import StringIO
+from textwrap import dedent
from logilab.common.testlib import TestCase, unittest_main, require_version
@@ -1239,6 +1240,22 @@ def test(*args, **kwargs):
self.assertIsInstance(vararg_infered, nodes.Tuple)
self.assertIs(vararg_infered.parent, func.args)
+ def test_infer_nested(self):
+ code = dedent("""
+ def nested():
+ from threading import Thread
+
+ class NestedThread(Thread):
+ def __init__(self):
+ Thread.__init__(self)
+ """)
+ # Test that inferring Thread.__init__ looks up in
+ # the nested scope.
+ astroid = builder.string_build(code, __name__, __file__)
+ callfunc = next(astroid.nodes_of_class(nodes.CallFunc))
+ func = callfunc.func
+ infered = func.infered()[0]
+ self.assertIsInstance(infered, UnboundMethod)
if __name__ == '__main__':
unittest_main()