forked from VimPlug/jedi
Way better support for instantiated classes in REPL
Fixes several issues: - It was not possible to correctly trace where instances were coming from in a REPL. This led to them being pretty much ignored. - Instances were then just treated as classes and not as actual instances in MixedObjects. (However since they were ignored in the first place this wasn't really an issue). - Avoiding the repr bug https://github.com/python/cpython/pull/2132/files in Jedi is working a bit differently. We're just never accessing Objects directly. This should work around 99.99% of the cases were people are using this stuff. Fixes #872
This commit is contained in:
@@ -94,7 +94,7 @@ class Evaluator(object):
|
|||||||
# To memorize modules -> equals `sys.modules`.
|
# To memorize modules -> equals `sys.modules`.
|
||||||
self.modules = {} # like `sys.modules`.
|
self.modules = {} # like `sys.modules`.
|
||||||
self.compiled_cache = {} # see `evaluate.compiled.create()`
|
self.compiled_cache = {} # see `evaluate.compiled.create()`
|
||||||
self.mixed_cache = {} # see `evaluate.compiled.mixed.create()`
|
self.mixed_cache = {} # see `evaluate.compiled.mixed._create()`
|
||||||
self.analysis = []
|
self.analysis = []
|
||||||
self.dynamic_params_depth = 0
|
self.dynamic_params_depth = 0
|
||||||
self.is_analysis = False
|
self.is_analysis = False
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from jedi.cache import underscore_memoization
|
|||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.evaluate.context import Context
|
from jedi.evaluate.context import Context
|
||||||
from jedi.evaluate.cache import evaluator_function_cache
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
|
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||||
|
|
||||||
|
|
||||||
class MixedObject(object):
|
class MixedObject(object):
|
||||||
@@ -77,13 +78,14 @@ class MixedName(compiled.CompiledName):
|
|||||||
def infer(self):
|
def infer(self):
|
||||||
obj = self.parent_context.obj
|
obj = self.parent_context.obj
|
||||||
try:
|
try:
|
||||||
|
# TODO use logic from compiled.CompiledObjectFilter
|
||||||
obj = getattr(obj, self.string_name)
|
obj = getattr(obj, self.string_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
|
||||||
obj = None
|
obj = None
|
||||||
return [create(self._evaluator, obj, parent_context=self.parent_context)]
|
return [_create(self._evaluator, obj, parent_context=self.parent_context)]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_type(self):
|
def api_type(self):
|
||||||
@@ -116,21 +118,26 @@ def _load_module(evaluator, path, python_object):
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def source_findable(python_object):
|
def _get_object_to_check(python_object):
|
||||||
"""Check if inspect.getfile has a chance to find the source."""
|
"""Check if inspect.getfile has a chance to find the source."""
|
||||||
return (inspect.ismodule(python_object) or
|
if (inspect.ismodule(python_object) or
|
||||||
inspect.isclass(python_object) or
|
inspect.isclass(python_object) or
|
||||||
inspect.ismethod(python_object) or
|
inspect.ismethod(python_object) or
|
||||||
inspect.isfunction(python_object) or
|
inspect.isfunction(python_object) or
|
||||||
inspect.istraceback(python_object) or
|
inspect.istraceback(python_object) or
|
||||||
inspect.isframe(python_object) or
|
inspect.isframe(python_object) or
|
||||||
inspect.iscode(python_object))
|
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(evaluator, python_object):
|
def find_syntax_node_name(evaluator, python_object):
|
||||||
try:
|
try:
|
||||||
if not source_findable(python_object):
|
python_object = _get_object_to_check(python_object)
|
||||||
raise TypeError # Prevents computation of `repr` within inspect.
|
|
||||||
path = inspect.getsourcefile(python_object)
|
path = inspect.getsourcefile(python_object)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||||
@@ -188,7 +195,7 @@ def find_syntax_node_name(evaluator, python_object):
|
|||||||
|
|
||||||
|
|
||||||
@compiled.compiled_objects_cache('mixed_cache')
|
@compiled.compiled_objects_cache('mixed_cache')
|
||||||
def create(evaluator, obj, parent_context=None, *args):
|
def _create(evaluator, obj, parent_context=None, *args):
|
||||||
tree_node, path = find_syntax_node_name(evaluator, obj)
|
tree_node, path = find_syntax_node_name(evaluator, obj)
|
||||||
|
|
||||||
compiled_object = compiled.create(
|
compiled_object = compiled.create(
|
||||||
@@ -210,6 +217,10 @@ def create(evaluator, obj, parent_context=None, *args):
|
|||||||
node_is_context=True,
|
node_is_context=True,
|
||||||
node_is_object=True
|
node_is_object=True
|
||||||
)
|
)
|
||||||
|
if tree_node.type == 'classdef':
|
||||||
|
if not isinstance(obj, type):
|
||||||
|
# Is an instance, not a class.
|
||||||
|
tree_context, = tree_context.execute_evaluated()
|
||||||
|
|
||||||
return MixedObject(
|
return MixedObject(
|
||||||
evaluator,
|
evaluator,
|
||||||
|
|||||||
@@ -171,11 +171,13 @@ def test_slice():
|
|||||||
def test_getitem_side_effects():
|
def test_getitem_side_effects():
|
||||||
class Foo2():
|
class Foo2():
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
# possible side effects here, should therefore not call this.
|
# Possible side effects here, should therefore not call this.
|
||||||
|
if True:
|
||||||
|
raise NotImplementedError()
|
||||||
return index
|
return index
|
||||||
|
|
||||||
foo = Foo2()
|
foo = Foo2()
|
||||||
_assert_interpreter_complete('foo[0].', locals(), [])
|
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
|
||||||
|
|
||||||
|
|
||||||
def test_property_error_oldstyle():
|
def test_property_error_oldstyle():
|
||||||
@@ -252,3 +254,39 @@ def test_completion_param_annotations():
|
|||||||
assert a._goto_definitions() == []
|
assert a._goto_definitions() == []
|
||||||
assert [d.name for d in b._goto_definitions()] == ['str']
|
assert [d.name for d in b._goto_definitions()] == ['str']
|
||||||
assert set([d.name for d in c._goto_definitions()]) == set(['int', 'float'])
|
assert set([d.name for d in c._goto_definitions()]) == set(['int', 'float'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_more_complex_instances():
|
||||||
|
class Something:
|
||||||
|
def foo(self, other):
|
||||||
|
return self
|
||||||
|
|
||||||
|
class Base():
|
||||||
|
def wow(self):
|
||||||
|
return Something()
|
||||||
|
|
||||||
|
#script = jedi.Interpreter('Base().wow().foo', [locals()])
|
||||||
|
#c, = script.completions()
|
||||||
|
#assert c.name == 'foo'
|
||||||
|
|
||||||
|
x = Base()
|
||||||
|
script = jedi.Interpreter('x.wow().foo', [locals()])
|
||||||
|
c, = script.completions()
|
||||||
|
assert c.name == 'foo'
|
||||||
|
|
||||||
|
|
||||||
|
def test_repr_execution_issue():
|
||||||
|
"""
|
||||||
|
Anticipate inspect.getfile executing a __repr__ of all kinds of objects.
|
||||||
|
See also #919.
|
||||||
|
"""
|
||||||
|
class ErrorRepr:
|
||||||
|
def __repr__(self):
|
||||||
|
raise Exception('xyz')
|
||||||
|
|
||||||
|
er = ErrorRepr()
|
||||||
|
|
||||||
|
script = jedi.Interpreter('er', [locals()])
|
||||||
|
d, = script.goto_definitions()
|
||||||
|
assert d.name == 'ErrorRepr'
|
||||||
|
assert d.type == 'instance'
|
||||||
|
|||||||
Reference in New Issue
Block a user