diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 31793f3da..ec4329fd9 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -7,6 +7,8 @@ import sys import warnings +from parso.python.tree import search_ancestor + from jedi import settings from jedi import debug from jedi.inference.utils import unite @@ -368,12 +370,28 @@ def parent(self): if not self._name.is_value_name: return None - context = self._name.parent_context + if self.type in ('function', 'class', 'param') and self._name.tree_name is not None: + # Since the parent_context doesn't really match what the user + # thinks of that the parent is here, we do these cases separately. + # The reason for this is the following: + # - class: Nested classes parent_context is always the + # parent_context of the most outer one. + # - function: Functions in classes have the module as + # parent_context. + # - param: The parent_context of a param is not its function but + # e.g. the outer class or module. + cls_or_func_node = self._name.tree_name.get_definition() + parent = search_ancestor(cls_or_func_node, 'funcdef', 'classdef', 'file_input') + context = self._get_module_context().create_value(parent).as_context() + else: + context = self._name.parent_context + if context is None: return None while context.name is None: # Happens for comprehension contexts context = context.parent_context + return Definition(self._inference_state, context.name) def __repr__(self): diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index 9e4ca5aad..deb7f952d 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -276,7 +276,7 @@ def test_parent_on_function(Script): assert parent.type == 'module' -def test_parent_on_completion(Script): +def test_parent_on_completion_and_else(Script): script = Script(dedent('''\ class Foo(): def bar(name): name @@ -287,14 +287,19 @@ def bar(name): name assert parent.name == 'Foo' assert parent.type == 'class' - param, = script.goto(line=2) - parent = param.parent() + param, name, = [d for d in script.names(all_scopes=True, references=True) + if d.name == 'name'] + parent = name.parent() + assert parent.name == 'bar' + assert parent.type == 'function' + parent = name.parent().parent() assert parent.name == 'Foo' assert parent.type == 'class' - name, = [d for d in script.names(all_scopes=True, references=True) - if d.name == 'name' and d.type == 'statement'] parent = param.parent() + assert parent.name == 'bar' + assert parent.type == 'function' + parent = param.parent().parent() assert parent.name == 'Foo' assert parent.type == 'class' @@ -303,6 +308,28 @@ def bar(name): name assert parent.type == 'class' +def test_parent_on_closure(Script): + script = Script(dedent('''\ + class Foo(): + def bar(name): + def inner(): foo + return inner''')) + + names = script.names(all_scopes=True, references=True) + inner_func, inner_reference = filter(lambda d: d.name == 'inner', names) + foo, = filter(lambda d: d.name == 'foo', names) + + assert foo.parent().name == 'inner' + assert foo.parent().parent().name == 'bar' + assert foo.parent().parent().parent().name == 'Foo' + assert foo.parent().parent().parent().parent().name == '' + + assert inner_func.parent().name == 'bar' + assert inner_func.parent().parent().name == 'Foo' + assert inner_reference.parent().name == 'bar' + assert inner_reference.parent().parent().name == 'Foo' + + def test_parent_on_comprehension(Script): ns = Script('''\ def spam():