forked from VimPlug/jedi
Setting correct parents for CompiledObject filters
This commit is contained in:
@@ -291,17 +291,6 @@ Usage::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Python3Method(object):
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
|
|
||||||
def __get__(self, obj, objtype):
|
|
||||||
if obj is None:
|
|
||||||
return lambda *args, **kwargs: self.func(*args, **kwargs)
|
|
||||||
else:
|
|
||||||
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def use_metaclass(meta, *bases):
|
def use_metaclass(meta, *bases):
|
||||||
""" Create a class with a metaclass. """
|
""" Create a class with a metaclass. """
|
||||||
if not bases:
|
if not bases:
|
||||||
|
|||||||
+7
-20
@@ -6,7 +6,7 @@ from jedi.evaluate.context import ModuleContext
|
|||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
from jedi.evaluate.compiled import mixed
|
from jedi.evaluate.compiled import mixed
|
||||||
from jedi.evaluate.compiled.access import create_access_path
|
from jedi.evaluate.compiled.access import create_access_path
|
||||||
from jedi.evaluate.base_context import Context
|
from jedi.evaluate.base_context import ContextWrapper
|
||||||
|
|
||||||
|
|
||||||
def _create(evaluator, obj):
|
def _create(evaluator, obj):
|
||||||
@@ -20,41 +20,28 @@ class NamespaceObject(object):
|
|||||||
self.__dict__ = dct
|
self.__dict__ = dct
|
||||||
|
|
||||||
|
|
||||||
class MixedModuleContext(Context):
|
class MixedModuleContext(ContextWrapper):
|
||||||
# TODO use ContextWrapper!
|
|
||||||
type = 'mixed_module'
|
type = 'mixed_module'
|
||||||
|
|
||||||
def __init__(self, evaluator, tree_module, namespaces, file_io, code_lines):
|
def __init__(self, evaluator, tree_module, namespaces, file_io, code_lines):
|
||||||
self.evaluator = evaluator
|
module_context = ModuleContext(
|
||||||
self._namespaces = namespaces
|
|
||||||
|
|
||||||
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
|
||||||
self._module_context = ModuleContext(
|
|
||||||
evaluator, tree_module,
|
evaluator, tree_module,
|
||||||
file_io=file_io,
|
file_io=file_io,
|
||||||
string_names=('__main__',),
|
string_names=('__main__',),
|
||||||
code_lines=code_lines
|
code_lines=code_lines
|
||||||
)
|
)
|
||||||
self.tree_node = tree_module
|
super(MixedModuleContext, self).__init__(module_context)
|
||||||
|
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
||||||
|
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
for filter in self._module_context.get_filters(*args, **kwargs):
|
for filter in self._wrapped_context.get_filters(*args, **kwargs):
|
||||||
yield filter
|
yield filter
|
||||||
|
|
||||||
for namespace_obj in self._namespace_objects:
|
for namespace_obj in self._namespace_objects:
|
||||||
compiled_object = _create(self.evaluator, namespace_obj)
|
compiled_object = _create(self.evaluator, namespace_obj)
|
||||||
mixed_object = mixed.MixedObject(
|
mixed_object = mixed.MixedObject(
|
||||||
self.evaluator,
|
|
||||||
parent_context=self,
|
|
||||||
compiled_object=compiled_object,
|
compiled_object=compiled_object,
|
||||||
tree_context=self._module_context
|
tree_context=self._wrapped_context
|
||||||
)
|
)
|
||||||
for filter in mixed_object.get_filters(*args, **kwargs):
|
for filter in mixed_object.get_filters(*args, **kwargs):
|
||||||
yield filter
|
yield filter
|
||||||
|
|
||||||
@property
|
|
||||||
def code_lines(self):
|
|
||||||
return self._module_context.code_lines
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._module_context, name)
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from operator import add
|
|||||||
from parso.python.tree import ExprStmt, CompFor
|
from parso.python.tree import ExprStmt, CompFor
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi._compatibility import Python3Method, zip_longest, unicode
|
from jedi._compatibility import zip_longest, unicode
|
||||||
from jedi.parser_utils import clean_scope_docstring
|
from jedi.parser_utils import clean_scope_docstring
|
||||||
from jedi.common import BaseContextSet, BaseContext
|
from jedi.common import BaseContextSet, BaseContext
|
||||||
from jedi.evaluate.helpers import SimpleGetItemNotFound, execute_evaluated
|
from jedi.evaluate.helpers import SimpleGetItemNotFound, execute_evaluated
|
||||||
@@ -51,7 +51,6 @@ class HelperContextMixin(object):
|
|||||||
for lazy_context in self.iterate(contextualized_node, is_async)
|
for lazy_context in self.iterate(contextualized_node, is_async)
|
||||||
)
|
)
|
||||||
|
|
||||||
@Python3Method
|
|
||||||
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||||
search_global=False, is_goto=False,
|
search_global=False, is_goto=False,
|
||||||
analysis_errors=True):
|
analysis_errors=True):
|
||||||
|
|||||||
@@ -305,16 +305,26 @@ class DirectObjectAccess(object):
|
|||||||
return True, True
|
return True, True
|
||||||
return True, False
|
return True, False
|
||||||
|
|
||||||
def getattr(self, name, default=_sentinel):
|
def getattr_paths(self, name, default=_sentinel):
|
||||||
try:
|
try:
|
||||||
return self._create_access(getattr(self._obj, name))
|
return_obj = getattr(self._obj, name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Happens e.g. in properties of
|
# Happens e.g. in properties of
|
||||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||||
# -> just set it to None
|
# -> just set it to None
|
||||||
if default is _sentinel:
|
if default is _sentinel:
|
||||||
raise
|
raise
|
||||||
return self._create_access(default)
|
return_obj = default
|
||||||
|
access = self._create_access(return_obj)
|
||||||
|
if inspect.ismodule(self._obj):
|
||||||
|
return [access]
|
||||||
|
|
||||||
|
module = inspect.getmodule(return_obj)
|
||||||
|
if module is None:
|
||||||
|
module = inspect.getmodule(type(return_obj))
|
||||||
|
if module is None:
|
||||||
|
module = builtins
|
||||||
|
return [self._create_access(module), access]
|
||||||
|
|
||||||
def get_safe_value(self):
|
def get_safe_value(self):
|
||||||
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice):
|
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice):
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class CheckAttribute(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
# This might raise an AttributeError. That's wanted.
|
# This might raise an AttributeError. That's wanted.
|
||||||
instance.access_handle.getattr(self.check_name)
|
instance.access_handle.getattr_paths(self.check_name)
|
||||||
return partial(self.func, instance)
|
return partial(self.func, instance)
|
||||||
|
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ class CompiledObject(Context):
|
|||||||
try:
|
try:
|
||||||
# TODO wtf is this? this is exactly the same as the thing
|
# TODO wtf is this? this is exactly the same as the thing
|
||||||
# below. It uses getattr as well.
|
# below. It uses getattr as well.
|
||||||
self.evaluator.builtins_module.access_handle.getattr(name)
|
self.evaluator.builtins_module.access_handle.getattr_paths(name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -486,13 +486,17 @@ def _parse_function_doc(doc):
|
|||||||
|
|
||||||
|
|
||||||
def _create_from_name(evaluator, compiled_object, name):
|
def _create_from_name(evaluator, compiled_object, name):
|
||||||
access = compiled_object.access_handle.getattr(name, default=None)
|
access_paths = compiled_object.access_handle.getattr_paths(name, default=None)
|
||||||
parent_context = compiled_object
|
parent_context = compiled_object
|
||||||
if parent_context.is_class():
|
if parent_context.is_class():
|
||||||
parent_context = parent_context.parent_context
|
parent_context = parent_context.parent_context
|
||||||
return create_cached_compiled_object(
|
|
||||||
evaluator, access, parent_context=parent_context
|
context = None
|
||||||
|
for access_path in access_paths:
|
||||||
|
context = create_cached_compiled_object(
|
||||||
|
evaluator, access_path, parent_context=context
|
||||||
)
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
def _normalize_create_args(func):
|
def _normalize_create_args(func):
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from jedi import settings
|
|||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
from jedi.cache import underscore_memoization
|
from jedi.cache import underscore_memoization
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
from jedi.evaluate.base_context import Context, ContextSet
|
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.helpers import execute_evaluated
|
||||||
@@ -21,7 +21,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
class MixedObject(object):
|
class MixedObject(ContextWrapper):
|
||||||
"""
|
"""
|
||||||
A ``MixedObject`` is used in two ways:
|
A ``MixedObject`` is used in two ways:
|
||||||
|
|
||||||
@@ -38,27 +38,19 @@ class MixedObject(object):
|
|||||||
fewer special cases, because we in Python you don't have the same freedoms
|
fewer special cases, because we in Python you don't have the same freedoms
|
||||||
to modify the runtime.
|
to modify the runtime.
|
||||||
"""
|
"""
|
||||||
def __init__(self, evaluator, parent_context, compiled_object, tree_context):
|
def __init__(self, compiled_object, tree_context):
|
||||||
self.evaluator = evaluator
|
super(MixedObject, self).__init__(tree_context)
|
||||||
self.parent_context = parent_context
|
|
||||||
self.compiled_object = compiled_object
|
self.compiled_object = compiled_object
|
||||||
self._context = tree_context
|
|
||||||
self.access_handle = compiled_object.access_handle
|
self.access_handle = compiled_object.access_handle
|
||||||
|
|
||||||
# We have to overwrite everything that has to do with trailers, name
|
|
||||||
# lookups and filters to make it possible to route name lookups towards
|
|
||||||
# compiled objects and the rest towards tree node contexts.
|
|
||||||
def py__getattribute__(*args, **kwargs):
|
|
||||||
return Context.py__getattribute__(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
yield MixedObjectFilter(self.evaluator, self)
|
yield MixedObjectFilter(self.evaluator, self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (type(self).__name__, self.access_handle.get_repr())
|
return '<%s: %s>' % (
|
||||||
|
type(self).__name__,
|
||||||
def __getattr__(self, name):
|
self.access_handle.get_repr()
|
||||||
return getattr(self._context, name)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MixedName(compiled.CompiledName):
|
class MixedName(compiled.CompiledName):
|
||||||
@@ -80,10 +72,15 @@ class MixedName(compiled.CompiledName):
|
|||||||
|
|
||||||
@underscore_memoization
|
@underscore_memoization
|
||||||
def infer(self):
|
def infer(self):
|
||||||
access_handle = self.parent_context.access_handle
|
|
||||||
# TODO use logic from compiled.CompiledObjectFilter
|
# TODO use logic from compiled.CompiledObjectFilter
|
||||||
access_handle = access_handle.getattr(self.string_name, default=None)
|
access_paths = self.parent_context.access_handle.getattr_paths(
|
||||||
return _create(self._evaluator, access_handle, parent_context=self.parent_context)
|
self.string_name,
|
||||||
|
default=None
|
||||||
|
)
|
||||||
|
context = None
|
||||||
|
for access in access_paths:
|
||||||
|
return _create(self._evaluator, access, parent_context=context)
|
||||||
|
return context
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_type(self):
|
def api_type(self):
|
||||||
@@ -227,10 +224,6 @@ def _create(evaluator, access_handle, parent_context, *args):
|
|||||||
tree_context, = execute_evaluated(tree_context)
|
tree_context, = execute_evaluated(tree_context)
|
||||||
|
|
||||||
return ContextSet({
|
return ContextSet({
|
||||||
MixedObject(
|
MixedObject(compiled_object, tree_context=c)
|
||||||
evaluator,
|
for c in to_stub(tree_context) or [tree_context]
|
||||||
parent_context,
|
|
||||||
compiled_object,
|
|
||||||
tree_context=c,
|
|
||||||
) for c in to_stub(tree_context) or [tree_context]
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from . import run
|
|||||||
from . import refactor
|
from . import refactor
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
|
from jedi.api.environment import InterpreterEnvironment
|
||||||
from jedi.evaluate.analysis import Warning
|
from jedi.evaluate.analysis import Warning
|
||||||
|
|
||||||
|
|
||||||
@@ -163,3 +164,8 @@ def cwd_tmpdir(monkeypatch, tmpdir):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def evaluator(Script):
|
def evaluator(Script):
|
||||||
return Script('')._evaluator
|
return Script('')._evaluator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def same_process_evaluator(Script):
|
||||||
|
return Script('', environment=InterpreterEnvironment())._evaluator
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from textwrap import dedent
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
|
from jedi.evaluate.compiled.access import DirectObjectAccess
|
||||||
from jedi.evaluate.helpers import execute_evaluated
|
from jedi.evaluate.helpers import execute_evaluated
|
||||||
from jedi.evaluate.gradual.conversion import stub_to_actual_context_set
|
from jedi.evaluate.gradual.conversion import stub_to_actual_context_set
|
||||||
|
|
||||||
@@ -101,3 +102,39 @@ def test_getitem_on_none(Script):
|
|||||||
assert not script.goto_definitions()
|
assert not script.goto_definitions()
|
||||||
issue, = script._evaluator.analysis
|
issue, = script._evaluator.analysis
|
||||||
assert issue.name == 'type-error-not-subscriptable'
|
assert issue.name == 'type-error-not-subscriptable'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'attribute, expected_name, expected_parent', [
|
||||||
|
('x', 'int', 'builtins'),
|
||||||
|
('y', 'int', 'builtins'),
|
||||||
|
('z', 'bool', 'builtins'),
|
||||||
|
('cos', 'cos', 'math'),
|
||||||
|
('dec', 'Decimal', 'decimal'),
|
||||||
|
('dt', 'datetime', 'datetime'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_parent_context(same_process_evaluator, attribute, expected_name, expected_parent):
|
||||||
|
import math
|
||||||
|
import decimal
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class C:
|
||||||
|
x = 1
|
||||||
|
y = int
|
||||||
|
z = True
|
||||||
|
cos = math.cos
|
||||||
|
dec = decimal.Decimal(1)
|
||||||
|
dt = datetime.datetime(2000, 1, 1)
|
||||||
|
|
||||||
|
o = compiled.CompiledObject(
|
||||||
|
same_process_evaluator,
|
||||||
|
DirectObjectAccess(same_process_evaluator, C)
|
||||||
|
)
|
||||||
|
x, = o.py__getattribute__(attribute)
|
||||||
|
assert x.py__name__() == expected_name
|
||||||
|
module_name = x.parent_context.py__name__()
|
||||||
|
if module_name == '__builtin__':
|
||||||
|
module_name = 'builtins' # Python 2
|
||||||
|
assert module_name == expected_parent
|
||||||
|
assert x.parent_context.parent_context is None
|
||||||
|
|||||||
Reference in New Issue
Block a user