Fix qualified names for CompiledObject

This commit is contained in:
Dave Halter
2019-06-07 01:33:37 +02:00
parent a43a6cbc06
commit 97f342fc4c
7 changed files with 99 additions and 42 deletions

View File

@@ -279,9 +279,25 @@ class DirectObjectAccess(object):
def is_class(self):
return inspect.isclass(self._obj)
def is_module(self):
return inspect.ismodule(self._obj)
def ismethoddescriptor(self):
return inspect.ismethoddescriptor(self._obj)
def get_qualified_names(self):
def try_to_get_name(obj):
return getattr(obj, '__qualname__', getattr(obj, '__name__', None))
if self.is_module():
return ()
name = try_to_get_name(self._obj)
if name is None:
name = try_to_get_name(type(self._obj))
if name is None:
return ()
return name.split('.')
def dir(self):
return list(map(force_unicode, dir(self._obj)))

View File

@@ -86,11 +86,11 @@ class CompiledObject(Context):
# For modules
name = self.py__name__()
if name is None:
return []
return ()
return tuple(name.split('.'))
def get_qualified_names(self):
return self.string_names
return self.access_handle.get_qualified_names()
def py__bool__(self):
return self.access_handle.py__bool__()
@@ -101,6 +101,9 @@ class CompiledObject(Context):
def is_class(self):
return self.access_handle.is_class()
def is_module(self):
return self.access_handle.is_module()
def is_compiled(self):
return True

View File

@@ -81,13 +81,15 @@ class MixedName(compiled.CompiledName):
default=None
)
assert len(access_paths)
context = None
contexts = [None]
for access in access_paths:
if context is None or isinstance(context, MixedObject):
context = _create(self._evaluator, access, parent_context=context)
else:
context = create_cached_compiled_object(context.evaluator, access, context)
return ContextSet([context])
contexts = ContextSet.from_sets(
_create(self._evaluator, access, parent_context=c)
if c is None or isinstance(c, MixedObject)
else ContextSet({create_cached_compiled_object(c.evaluator, access, c)})
for c in contexts
)
return contexts
@property
def api_type(self):
@@ -211,35 +213,40 @@ def _create(evaluator, access_handle, parent_context, *args):
# TODO use stub contexts here. If we do that we probably have to care about
# generics from stuff like `[1]`.
if result is None:
return compiled_object
module_node, tree_node, file_io, code_lines = result
if parent_context is None:
# TODO this __name__ is probably wrong.
name = compiled_object.get_root_context().py__name__()
string_names = tuple(name.split('.'))
module_context = ModuleContext(
evaluator, module_node,
file_io=file_io,
string_names=string_names,
code_lines=code_lines,
is_package=hasattr(compiled_object, 'py__path__'),
)
if name is not None:
evaluator.module_cache.add(string_names, ContextSet([module_context]))
return ContextSet({compiled_object})
else:
assert parent_context.tree_node.get_root_node() == module_node
module_context = parent_context.get_root_context()
module_node, tree_node, file_io, code_lines = result
tree_context = module_context.create_context(
tree_node,
node_is_context=True,
node_is_object=True
if parent_context is None:
# TODO this __name__ is probably wrong.
name = compiled_object.get_root_context().py__name__()
string_names = tuple(name.split('.'))
module_context = ModuleContext(
evaluator, module_node,
file_io=file_io,
string_names=string_names,
code_lines=code_lines,
is_package=hasattr(compiled_object, 'py__path__'),
)
if name is not None:
evaluator.module_cache.add(string_names, ContextSet([module_context]))
else:
assert parent_context.tree_node.get_root_node() == module_node
module_context = parent_context.get_root_context()
tree_contexts = ContextSet({
module_context.create_context(
tree_node,
node_is_context=True,
node_is_object=True
)
})
if tree_node.type == 'classdef':
if not access_handle.is_class():
# Is an instance, not a class.
tree_contexts = tree_contexts.execute_evaluated()
return ContextSet(
MixedObject(compiled_object, tree_context=tree_context)
for tree_context in tree_contexts
)
if tree_node.type == 'classdef':
if not access_handle.is_class():
# Is an instance, not a class.
tree_context, = execute_evaluated(tree_context)
return MixedObject(compiled_object, tree_context=tree_context)

View File

@@ -38,7 +38,7 @@ def usages(module_context, tree_name):
search_name = tree_name.value
found_names = _find_names(module_context, tree_name)
modules = set(d.get_root_context() for d in found_names.values())
modules = set(m for m in modules if m.is_module())
modules = set(m for m in modules if m.is_module() and not m.is_compiled())
non_matching_usage_maps = {}
for m in imports.get_modules_containing_name(module_context.evaluator, modules, search_name):

View File

@@ -110,7 +110,7 @@ class StdlibPlugin(BasePlugin):
if context.parent_context == self._evaluator.builtins_module:
module_name = 'builtins'
elif context.parent_context is not None and context.parent_context.is_module():
module_name = context.parent_context.name.string_name
module_name = context.parent_context.py__name__()
else:
return callback(context, arguments=arguments)

View File

@@ -397,6 +397,9 @@ def test_sys_path_docstring(): # Was an issue in #1298
('counter.setdefa', ['setdefault']),
('counter.pop().imag', []), # TODO stubs could make this better
('counter.keys())[0].uppe', []),
('string.upper().uppe', ['upper']),
('"".upper().uppe', ['upper']),
]
)
def test_simple_completions(code, completions):
@@ -406,6 +409,7 @@ def test_simple_completions(code, completions):
import collections
deq = collections.deque([1])
counter = collections.Counter(['asdf'])
string = ''
defs = jedi.Interpreter(code, [locals()]).completions()
assert [d.name for d in defs] == completions

View File

@@ -1,4 +1,8 @@
from textwrap import dedent
import math
import sys
from collections import Counter
from datetime import datetime
import pytest
@@ -107,6 +111,7 @@ def test_getitem_on_none(Script):
def _return_int():
return 1
@pytest.mark.parametrize(
'attribute, expected_name, expected_parent', [
('x', 'int', 'builtins'),
@@ -119,9 +124,7 @@ def _return_int():
]
)
def test_parent_context(same_process_evaluator, attribute, expected_name, expected_parent):
import math
import decimal
import datetime
class C:
x = 1
@@ -129,7 +132,7 @@ def test_parent_context(same_process_evaluator, attribute, expected_name, expect
z = True
cos = math.cos
dec = decimal.Decimal(1)
dt = datetime.datetime(2000, 1, 1)
dt = datetime(2000, 1, 1)
ret_int = _return_int
o = compiled.CompiledObject(
@@ -143,3 +146,27 @@ def test_parent_context(same_process_evaluator, attribute, expected_name, expect
module_name = 'builtins' # Python 2
assert module_name == expected_parent
assert x.parent_context.parent_context is None
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
@pytest.mark.parametrize(
'obj, expected_names', [
('', ['str']),
(str, ['str']),
(''.upper, ['str', 'upper']),
(str.upper, ['str', 'upper']),
(math.cos, ['cos']),
(Counter, ['Counter']),
(Counter(""), ['Counter']),
(Counter.most_common, ['Counter', 'most_common']),
(Counter("").most_common, ['Counter', 'most_common']),
]
)
def test_qualified_names(same_process_evaluator, obj, expected_names):
o = compiled.CompiledObject(
same_process_evaluator,
DirectObjectAccess(same_process_evaluator, obj)
)
assert o.get_qualified_names() == expected_names