Use even more stubs to get more complex completions for e.g. strings working

This commit is contained in:
Dave Halter
2019-06-07 02:37:51 +02:00
parent 97f342fc4c
commit 94dfe7bf69
10 changed files with 48 additions and 15 deletions

View File

@@ -118,7 +118,7 @@ def add_attribute_error(name_context, lookup_context, name):
# Check for __getattr__/__getattribute__ existance and issue a warning
# instead of an error, if that happens.
typ = Error
if lookup_context.is_instance():
if lookup_context.is_instance() and not lookup_context.is_compiled():
slot_names = lookup_context.get_function_slot_names(u'__getattr__') + \
lookup_context.get_function_slot_names(u'__getattribute__')
for n in slot_names:

View File

@@ -19,6 +19,8 @@ from jedi.evaluate.utils import safe_property
from jedi.evaluate.cache import evaluator_as_method_param_cache
from jedi.cache import memoize_method
_sentinel = object()
class HelperContextMixin(object):
def get_root_context(self):
@@ -185,6 +187,11 @@ class Context(HelperContextMixin, BaseContext):
return clean_scope_docstring(self.tree_node)
return None
def get_safe_value(self, default=_sentinel):
if default is _sentinel:
raise ValueError("There exists no safe value for context %s" % self)
return default
def py__call__(self, arguments):
debug.warning("no execution possible %s", self)
return NO_CONTEXTS

View File

@@ -282,6 +282,9 @@ class DirectObjectAccess(object):
def is_module(self):
return inspect.ismodule(self._obj)
def is_instance(self):
return _is_class_instance(self._obj)
def ismethoddescriptor(self):
return inspect.ismethoddescriptor(self._obj)

View File

@@ -110,6 +110,9 @@ class CompiledObject(Context):
def is_stub(self):
return False
def is_instance(self):
return self.access_handle.is_instance()
def py__doc__(self):
return self.access_handle.py__doc__()

View File

@@ -14,12 +14,13 @@ from jedi.file_io import FileIO
from jedi.evaluate.base_context import ContextSet, ContextWrapper
from jedi.evaluate.context import ModuleContext
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.helpers import execute_evaluated
from jedi.evaluate.compiled.getattr_static import getattr_static
from jedi.evaluate.compiled.access import compiled_objects_cache
from jedi.evaluate.compiled.context import create_cached_compiled_object
from jedi.evaluate.gradual.conversion import to_stub
_sentinel = object()
class MixedObject(ContextWrapper):
"""
@@ -49,6 +50,12 @@ class MixedObject(ContextWrapper):
def py__call__(self, arguments):
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
def get_safe_value(self, default=_sentinel):
if default is _sentinel:
return self.compiled_object.get_safe_value()
else:
return self.compiled_object.get_safe_value(default)
def __repr__(self):
return '<%s: %s>' % (
type(self).__name__,
@@ -210,10 +217,15 @@ def _create(evaluator, access_handle, parent_context, *args):
)
result = _find_syntax_node_name(evaluator, access_handle)
# 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 ContextSet({compiled_object})
# TODO Care about generics from stuff like `[1]` and don't return like this.
python_object = access_handle.access._obj
if type(python_object) in (dict, list, tuple):
return ContextSet({compiled_object})
tree_contexts = to_stub(compiled_object)
if not tree_contexts:
return ContextSet({compiled_object})
else:
module_node, tree_node, file_io, code_lines = result

View File

@@ -157,7 +157,7 @@ class AbstractInstanceContext(Context):
def iterate():
for generator in self.execute_function_slots(iter_slot_names):
if generator.is_instance():
if generator.is_instance() and not generator.is_compiled():
# `__next__` logic.
if self.evaluator.environment.version_info.major == 2:
name = u'next'

View File

@@ -189,13 +189,13 @@ class NameFinder(object):
contexts = ContextSet.from_sets(name.infer() for name in names)
debug.dbg('finder._names_to_types: %s -> %s', names, contexts)
if not names and self._context.is_instance():
if not names and self._context.is_instance() and not self._context.is_compiled():
# handling __getattr__ / __getattribute__
return self._check_getattr(self._context)
# Add isinstance and other if/assert knowledge.
if not contexts and isinstance(self._name, tree.Name) and \
not self._name_context.is_instance():
not self._name_context.is_instance() and not self._context.is_compiled():
flow_scope = self._name
base_nodes = [self._name_context.tree_node]

View File

@@ -207,10 +207,9 @@ def is_literal(context):
def _get_safe_value_or_none(context, accept):
if context.is_compiled():
value = context.get_safe_value(default=None)
if isinstance(value, accept):
return value
value = context.get_safe_value(default=None)
if isinstance(value, accept):
return value
def get_int_or_none(context):

View File

@@ -212,11 +212,12 @@ def builtins_getattr(objects, names, defaults=None):
# follow the first param
for obj in objects:
for name in names:
if is_string(name):
return obj.py__getattribute__(force_unicode(name.get_safe_value()))
else:
string = get_str_or_none(name)
if string is None:
debug.warning('getattr called without str')
continue
else:
return obj.py__getattribute__(force_unicode(string))
return NO_CONTEXTS

View File

@@ -167,6 +167,14 @@ def test_list():
['upper'])
def test_getattr():
class Foo1:
bar = []
baz = 'bar'
_assert_interpreter_complete('getattr(Foo1, baz).app', locals(), ['append'])
@pytest.mark.xfail(reason='For now slicing on strings is not supported for mixed objects')
def test_slice():
class Foo1:
bar = []