You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			310 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			310 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Used only for REPL Completion.
 | 
						|
"""
 | 
						|
 | 
						|
import inspect
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
from jedi.parser_utils import get_cached_code_lines
 | 
						|
 | 
						|
from jedi import settings
 | 
						|
from jedi.cache import memoize_method
 | 
						|
from jedi.inference import compiled
 | 
						|
from jedi.file_io import FileIO
 | 
						|
from jedi.inference.names import NameWrapper
 | 
						|
from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES
 | 
						|
from jedi.inference.value import ModuleValue
 | 
						|
from jedi.inference.cache import inference_state_function_cache, \
 | 
						|
    inference_state_method_cache
 | 
						|
from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type
 | 
						|
from jedi.inference.gradual.conversion import to_stub
 | 
						|
from jedi.inference.context import CompiledContext, CompiledModuleContext, \
 | 
						|
    TreeContextMixin
 | 
						|
 | 
						|
_sentinel = object()
 | 
						|
 | 
						|
 | 
						|
class MixedObject(ValueWrapper):
 | 
						|
    """
 | 
						|
    A ``MixedObject`` is used in two ways:
 | 
						|
 | 
						|
    1. It uses the default logic of ``parser.python.tree`` objects,
 | 
						|
    2. except for getattr calls and signatures. The names dicts are generated
 | 
						|
       in a fashion like ``CompiledValue``.
 | 
						|
 | 
						|
    This combined logic makes it possible to provide more powerful REPL
 | 
						|
    completion. It allows side effects that are not noticable with the default
 | 
						|
    parser structure to still be completable.
 | 
						|
 | 
						|
    The biggest difference from CompiledValue to MixedObject is that we are
 | 
						|
    generally dealing with Python code and not with C code. This will generate
 | 
						|
    fewer special cases, because we in Python you don't have the same freedoms
 | 
						|
    to modify the runtime.
 | 
						|
    """
 | 
						|
    def __init__(self, compiled_value, tree_value):
 | 
						|
        super().__init__(tree_value)
 | 
						|
        self.compiled_value = compiled_value
 | 
						|
        self.access_handle = compiled_value.access_handle
 | 
						|
 | 
						|
    def get_filters(self, *args, **kwargs):
 | 
						|
        yield MixedObjectFilter(
 | 
						|
            self.inference_state, self.compiled_value, self._wrapped_value)
 | 
						|
 | 
						|
    def get_signatures(self):
 | 
						|
        # Prefer `inspect.signature` over somehow analyzing Python code. It
 | 
						|
        # should be very precise, especially for stuff like `partial`.
 | 
						|
        return self.compiled_value.get_signatures()
 | 
						|
 | 
						|
    @inference_state_method_cache(default=NO_VALUES)
 | 
						|
    def py__call__(self, arguments):
 | 
						|
        # Fallback to the wrapped value if to stub returns no values.
 | 
						|
        values = to_stub(self._wrapped_value)
 | 
						|
        if not values:
 | 
						|
            values = self._wrapped_value
 | 
						|
        return values.py__call__(arguments)
 | 
						|
 | 
						|
    def get_safe_value(self, default=_sentinel):
 | 
						|
        if default is _sentinel:
 | 
						|
            return self.compiled_value.get_safe_value()
 | 
						|
        else:
 | 
						|
            return self.compiled_value.get_safe_value(default)
 | 
						|
 | 
						|
    @property
 | 
						|
    def array_type(self):
 | 
						|
        return self.compiled_value.array_type
 | 
						|
 | 
						|
    def get_key_values(self):
 | 
						|
        return self.compiled_value.get_key_values()
 | 
						|
 | 
						|
    def py__simple_getitem__(self, index):
 | 
						|
        python_object = self.compiled_value.access_handle.access._obj
 | 
						|
        if type(python_object) in ALLOWED_GETITEM_TYPES:
 | 
						|
            return self.compiled_value.py__simple_getitem__(index)
 | 
						|
        return self._wrapped_value.py__simple_getitem__(index)
 | 
						|
 | 
						|
    def negate(self):
 | 
						|
        return self.compiled_value.negate()
 | 
						|
 | 
						|
    def _as_context(self):
 | 
						|
        if self.parent_context is None:
 | 
						|
            return MixedModuleContext(self)
 | 
						|
        return MixedContext(self)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return '<%s: %s; %s>' % (
 | 
						|
            type(self).__name__,
 | 
						|
            self.access_handle.get_repr(),
 | 
						|
            self._wrapped_value,
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MixedContext(CompiledContext, TreeContextMixin):
 | 
						|
    @property
 | 
						|
    def compiled_value(self):
 | 
						|
        return self._value.compiled_value
 | 
						|
 | 
						|
 | 
						|
class MixedModuleContext(CompiledModuleContext, MixedContext):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class MixedName(NameWrapper):
 | 
						|
    """
 | 
						|
    The ``CompiledName._compiled_value`` is our MixedObject.
 | 
						|
    """
 | 
						|
    def __init__(self, wrapped_name, parent_tree_value):
 | 
						|
        super().__init__(wrapped_name)
 | 
						|
        self._parent_tree_value = parent_tree_value
 | 
						|
 | 
						|
    @property
 | 
						|
    def start_pos(self):
 | 
						|
        values = list(self.infer())
 | 
						|
        if not values:
 | 
						|
            # This means a start_pos that doesn't exist (compiled objects).
 | 
						|
            return 0, 0
 | 
						|
        return values[0].name.start_pos
 | 
						|
 | 
						|
    @memoize_method
 | 
						|
    def infer(self):
 | 
						|
        compiled_value = self._wrapped_name.infer_compiled_value()
 | 
						|
        tree_value = self._parent_tree_value
 | 
						|
        if tree_value.is_instance() or tree_value.is_class():
 | 
						|
            tree_values = tree_value.py__getattribute__(self.string_name)
 | 
						|
            if compiled_value.is_function():
 | 
						|
                return ValueSet({MixedObject(compiled_value, v) for v in tree_values})
 | 
						|
 | 
						|
        module_context = tree_value.get_root_context()
 | 
						|
        return _create(self._inference_state, compiled_value, module_context)
 | 
						|
 | 
						|
 | 
						|
class MixedObjectFilter(compiled.CompiledValueFilter):
 | 
						|
    def __init__(self, inference_state, compiled_value, tree_value):
 | 
						|
        super().__init__(inference_state, compiled_value)
 | 
						|
        self._tree_value = tree_value
 | 
						|
 | 
						|
    def _create_name(self, *args, **kwargs):
 | 
						|
        return MixedName(
 | 
						|
            super()._create_name(*args, **kwargs),
 | 
						|
            self._tree_value,
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
@inference_state_function_cache()
 | 
						|
def _load_module(inference_state, path):
 | 
						|
    return inference_state.parse(
 | 
						|
        path=path,
 | 
						|
        cache=True,
 | 
						|
        diff_cache=settings.fast_parser,
 | 
						|
        cache_path=settings.cache_directory
 | 
						|
    ).get_root_node()
 | 
						|
 | 
						|
 | 
						|
def _get_object_to_check(python_object):
 | 
						|
    """Check if inspect.getfile has a chance to find the source."""
 | 
						|
    try:
 | 
						|
        python_object = inspect.unwrap(python_object)
 | 
						|
    except ValueError:
 | 
						|
        # Can return a ValueError when it wraps around
 | 
						|
        pass
 | 
						|
 | 
						|
    if (inspect.ismodule(python_object)
 | 
						|
            or inspect.isclass(python_object)
 | 
						|
            or inspect.ismethod(python_object)
 | 
						|
            or inspect.isfunction(python_object)
 | 
						|
            or inspect.istraceback(python_object)
 | 
						|
            or inspect.isframe(python_object)
 | 
						|
            or inspect.iscode(python_object)):
 | 
						|
        return python_object
 | 
						|
 | 
						|
    try:
 | 
						|
        return python_object.__class__
 | 
						|
    except AttributeError:
 | 
						|
        raise TypeError  # Prevents computation of `repr` within inspect.
 | 
						|
 | 
						|
 | 
						|
def _find_syntax_node_name(inference_state, python_object):
 | 
						|
    original_object = python_object
 | 
						|
    try:
 | 
						|
        python_object = _get_object_to_check(python_object)
 | 
						|
        path = inspect.getsourcefile(python_object)
 | 
						|
    except (OSError, TypeError):
 | 
						|
        # The type might not be known (e.g. class_with_dict.__weakref__)
 | 
						|
        return None
 | 
						|
    path = None if path is None else Path(path)
 | 
						|
    try:
 | 
						|
        if path is None or not path.exists():
 | 
						|
            # The path might not exist or be e.g. <stdin>.
 | 
						|
            return None
 | 
						|
    except OSError:
 | 
						|
        # Might raise an OSError on Windows:
 | 
						|
        #
 | 
						|
        #     [WinError 123] The filename, directory name, or volume label
 | 
						|
        #     syntax is incorrect: '<string>'
 | 
						|
        return None
 | 
						|
 | 
						|
    file_io = FileIO(path)
 | 
						|
    module_node = _load_module(inference_state, path)
 | 
						|
 | 
						|
    if inspect.ismodule(python_object):
 | 
						|
        # We don't need to check names for modules, because there's not really
 | 
						|
        # a way to write a module in a module in Python (and also __name__ can
 | 
						|
        # be something like ``email.utils``).
 | 
						|
        code_lines = get_cached_code_lines(inference_state.grammar, path)
 | 
						|
        return module_node, module_node, file_io, code_lines
 | 
						|
 | 
						|
    try:
 | 
						|
        name_str = python_object.__name__
 | 
						|
    except AttributeError:
 | 
						|
        # Stuff like python_function.__code__.
 | 
						|
        return None
 | 
						|
 | 
						|
    if name_str == '<lambda>':
 | 
						|
        return None  # It's too hard to find lambdas.
 | 
						|
 | 
						|
    # Doesn't always work (e.g. os.stat_result)
 | 
						|
    names = module_node.get_used_names().get(name_str, [])
 | 
						|
    # Only functions and classes are relevant. If a name e.g. points to an
 | 
						|
    # import, it's probably a builtin (like collections.deque) and needs to be
 | 
						|
    # ignored.
 | 
						|
    names = [
 | 
						|
        n for n in names
 | 
						|
        if n.parent.type in ('funcdef', 'classdef') and n.parent.name == n
 | 
						|
    ]
 | 
						|
    if not names:
 | 
						|
        return None
 | 
						|
 | 
						|
    try:
 | 
						|
        code = python_object.__code__
 | 
						|
        # By using the line number of a code object we make the lookup in a
 | 
						|
        # file pretty easy. There's still a possibility of people defining
 | 
						|
        # stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
 | 
						|
        # do so we just don't care.
 | 
						|
        line_nr = code.co_firstlineno
 | 
						|
    except AttributeError:
 | 
						|
        pass
 | 
						|
    else:
 | 
						|
        line_names = [name for name in names if name.start_pos[0] == line_nr]
 | 
						|
        # There's a chance that the object is not available anymore, because
 | 
						|
        # the code has changed in the background.
 | 
						|
        if line_names:
 | 
						|
            names = line_names
 | 
						|
 | 
						|
    code_lines = get_cached_code_lines(inference_state.grammar, path)
 | 
						|
    # It's really hard to actually get the right definition, here as a last
 | 
						|
    # resort we just return the last one. This chance might lead to odd
 | 
						|
    # completions at some points but will lead to mostly correct type
 | 
						|
    # inference, because people tend to define a public name in a module only
 | 
						|
    # once.
 | 
						|
    tree_node = names[-1].parent
 | 
						|
    if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
 | 
						|
        # If an instance is given and we're landing on a function (e.g.
 | 
						|
        # partial in 3.5), something is completely wrong and we should not
 | 
						|
        # return that.
 | 
						|
        return None
 | 
						|
    return module_node, tree_node, file_io, code_lines
 | 
						|
 | 
						|
 | 
						|
@inference_state_function_cache()
 | 
						|
def _create(inference_state, compiled_value, module_context):
 | 
						|
    # TODO accessing this is bad, but it probably doesn't matter that much,
 | 
						|
    # because we're working with interpreters only here.
 | 
						|
    python_object = compiled_value.access_handle.access._obj
 | 
						|
    result = _find_syntax_node_name(inference_state, python_object)
 | 
						|
    if result is None:
 | 
						|
        # TODO Care about generics from stuff like `[1]` and don't return like this.
 | 
						|
        if type(python_object) in (dict, list, tuple):
 | 
						|
            return ValueSet({compiled_value})
 | 
						|
 | 
						|
        tree_values = to_stub(compiled_value)
 | 
						|
        if not tree_values:
 | 
						|
            return ValueSet({compiled_value})
 | 
						|
    else:
 | 
						|
        module_node, tree_node, file_io, code_lines = result
 | 
						|
 | 
						|
        if module_context is None or module_context.tree_node != module_node:
 | 
						|
            root_compiled_value = compiled_value.get_root_context().get_value()
 | 
						|
            # TODO this __name__ might be wrong.
 | 
						|
            name = root_compiled_value.py__name__()
 | 
						|
            string_names = tuple(name.split('.'))
 | 
						|
            module_value = ModuleValue(
 | 
						|
                inference_state, module_node,
 | 
						|
                file_io=file_io,
 | 
						|
                string_names=string_names,
 | 
						|
                code_lines=code_lines,
 | 
						|
                is_package=root_compiled_value.is_package(),
 | 
						|
            )
 | 
						|
            if name is not None:
 | 
						|
                inference_state.module_cache.add(string_names, ValueSet([module_value]))
 | 
						|
            module_context = module_value.as_context()
 | 
						|
 | 
						|
        tree_values = ValueSet({module_context.create_value(tree_node)})
 | 
						|
        if tree_node.type == 'classdef':
 | 
						|
            if not compiled_value.is_class():
 | 
						|
                # Is an instance, not a class.
 | 
						|
                tree_values = tree_values.execute_with_values()
 | 
						|
 | 
						|
    return ValueSet(
 | 
						|
        MixedObject(compiled_value, tree_value=tree_value)
 | 
						|
        for tree_value in tree_values
 | 
						|
    )
 |