diff options
author | cpopa <devnull@localhost> | 2014-08-11 12:59:24 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-08-11 12:59:24 +0300 |
commit | 4165f185f92bd60b436ae1138f13d5747bbb2497 (patch) | |
tree | 68329839f49b82efe9928b4bad902518ae5cc58b | |
parent | 653b7d3f52d5cdfc86feb848af699844fc9dca9d (diff) | |
download | astroid-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-- | ChangeLog | 4 | ||||
-rw-r--r-- | inference.py | 28 | ||||
-rw-r--r-- | test/unittest_inference.py | 17 |
3 files changed, 48 insertions, 1 deletions
@@ -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() |