1
0
forked from VimPlug/jedi

Setting correct parents for CompiledObject filters

This commit is contained in:
Dave Halter
2019-06-04 23:31:42 +02:00
parent 586354b571
commit 0a56211df8
8 changed files with 92 additions and 67 deletions
-11
View File
@@ -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
View File
@@ -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)
+1 -2
View File
@@ -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):
+13 -3
View File
@@ -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):
+10 -6
View File
@@ -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):
+18 -25
View File
@@ -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]
}) })
+6
View File
@@ -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
+37
View File
@@ -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