Skip to content

Commit

Permalink
Fix some issues with Definition.parent()
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhalter committed Dec 22, 2019
1 parent 0202d4e commit 22c3bef
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 6 deletions.
20 changes: 19 additions & 1 deletion jedi/api/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
37 changes: 32 additions & 5 deletions test/test_api/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'

Expand All @@ -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():
Expand Down

0 comments on commit 22c3bef

Please sign in to comment.