mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 22:44:27 +08:00
Use even more stubs to get more complex completions for e.g. strings working
This commit is contained in:
@@ -118,7 +118,7 @@ def add_attribute_error(name_context, lookup_context, name):
|
|||||||
# Check for __getattr__/__getattribute__ existance and issue a warning
|
# Check for __getattr__/__getattribute__ existance and issue a warning
|
||||||
# instead of an error, if that happens.
|
# instead of an error, if that happens.
|
||||||
typ = Error
|
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__') + \
|
slot_names = lookup_context.get_function_slot_names(u'__getattr__') + \
|
||||||
lookup_context.get_function_slot_names(u'__getattribute__')
|
lookup_context.get_function_slot_names(u'__getattribute__')
|
||||||
for n in slot_names:
|
for n in slot_names:
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ from jedi.evaluate.utils import safe_property
|
|||||||
from jedi.evaluate.cache import evaluator_as_method_param_cache
|
from jedi.evaluate.cache import evaluator_as_method_param_cache
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
|
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
class HelperContextMixin(object):
|
class HelperContextMixin(object):
|
||||||
def get_root_context(self):
|
def get_root_context(self):
|
||||||
@@ -185,6 +187,11 @@ class Context(HelperContextMixin, BaseContext):
|
|||||||
return clean_scope_docstring(self.tree_node)
|
return clean_scope_docstring(self.tree_node)
|
||||||
return None
|
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):
|
def py__call__(self, arguments):
|
||||||
debug.warning("no execution possible %s", self)
|
debug.warning("no execution possible %s", self)
|
||||||
return NO_CONTEXTS
|
return NO_CONTEXTS
|
||||||
|
|||||||
@@ -282,6 +282,9 @@ class DirectObjectAccess(object):
|
|||||||
def is_module(self):
|
def is_module(self):
|
||||||
return inspect.ismodule(self._obj)
|
return inspect.ismodule(self._obj)
|
||||||
|
|
||||||
|
def is_instance(self):
|
||||||
|
return _is_class_instance(self._obj)
|
||||||
|
|
||||||
def ismethoddescriptor(self):
|
def ismethoddescriptor(self):
|
||||||
return inspect.ismethoddescriptor(self._obj)
|
return inspect.ismethoddescriptor(self._obj)
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ class CompiledObject(Context):
|
|||||||
def is_stub(self):
|
def is_stub(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_instance(self):
|
||||||
|
return self.access_handle.is_instance()
|
||||||
|
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
return self.access_handle.py__doc__()
|
return self.access_handle.py__doc__()
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ from jedi.file_io import FileIO
|
|||||||
from jedi.evaluate.base_context import ContextSet, ContextWrapper
|
from jedi.evaluate.base_context import ContextSet, ContextWrapper
|
||||||
from jedi.evaluate.context import ModuleContext
|
from jedi.evaluate.context import ModuleContext
|
||||||
from jedi.evaluate.cache import evaluator_function_cache
|
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.getattr_static import getattr_static
|
||||||
from jedi.evaluate.compiled.access import compiled_objects_cache
|
from jedi.evaluate.compiled.access import compiled_objects_cache
|
||||||
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
||||||
from jedi.evaluate.gradual.conversion import to_stub
|
from jedi.evaluate.gradual.conversion import to_stub
|
||||||
|
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
class MixedObject(ContextWrapper):
|
class MixedObject(ContextWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -49,6 +50,12 @@ class MixedObject(ContextWrapper):
|
|||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(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):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (
|
return '<%s: %s>' % (
|
||||||
type(self).__name__,
|
type(self).__name__,
|
||||||
@@ -210,9 +217,14 @@ def _create(evaluator, access_handle, parent_context, *args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result = _find_syntax_node_name(evaluator, access_handle)
|
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:
|
if result is None:
|
||||||
|
# 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})
|
return ContextSet({compiled_object})
|
||||||
else:
|
else:
|
||||||
module_node, tree_node, file_io, code_lines = result
|
module_node, tree_node, file_io, code_lines = result
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class AbstractInstanceContext(Context):
|
|||||||
|
|
||||||
def iterate():
|
def iterate():
|
||||||
for generator in self.execute_function_slots(iter_slot_names):
|
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.
|
# `__next__` logic.
|
||||||
if self.evaluator.environment.version_info.major == 2:
|
if self.evaluator.environment.version_info.major == 2:
|
||||||
name = u'next'
|
name = u'next'
|
||||||
|
|||||||
@@ -189,13 +189,13 @@ class NameFinder(object):
|
|||||||
contexts = ContextSet.from_sets(name.infer() for name in names)
|
contexts = ContextSet.from_sets(name.infer() for name in names)
|
||||||
|
|
||||||
debug.dbg('finder._names_to_types: %s -> %s', names, contexts)
|
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__
|
# handling __getattr__ / __getattribute__
|
||||||
return self._check_getattr(self._context)
|
return self._check_getattr(self._context)
|
||||||
|
|
||||||
# Add isinstance and other if/assert knowledge.
|
# Add isinstance and other if/assert knowledge.
|
||||||
if not contexts and isinstance(self._name, tree.Name) and \
|
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
|
flow_scope = self._name
|
||||||
base_nodes = [self._name_context.tree_node]
|
base_nodes = [self._name_context.tree_node]
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ def is_literal(context):
|
|||||||
|
|
||||||
|
|
||||||
def _get_safe_value_or_none(context, accept):
|
def _get_safe_value_or_none(context, accept):
|
||||||
if context.is_compiled():
|
|
||||||
value = context.get_safe_value(default=None)
|
value = context.get_safe_value(default=None)
|
||||||
if isinstance(value, accept):
|
if isinstance(value, accept):
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -212,11 +212,12 @@ def builtins_getattr(objects, names, defaults=None):
|
|||||||
# follow the first param
|
# follow the first param
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
for name in names:
|
for name in names:
|
||||||
if is_string(name):
|
string = get_str_or_none(name)
|
||||||
return obj.py__getattribute__(force_unicode(name.get_safe_value()))
|
if string is None:
|
||||||
else:
|
|
||||||
debug.warning('getattr called without str')
|
debug.warning('getattr called without str')
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
return obj.py__getattribute__(force_unicode(string))
|
||||||
return NO_CONTEXTS
|
return NO_CONTEXTS
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,14 @@ def test_list():
|
|||||||
['upper'])
|
['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():
|
def test_slice():
|
||||||
class Foo1:
|
class Foo1:
|
||||||
bar = []
|
bar = []
|
||||||
|
|||||||
Reference in New Issue
Block a user