1
0
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:
Dave Halter
2017-09-15 01:55:00 +02:00
parent 63edbdcc5b
commit 9dd2027299
4 changed files with 60 additions and 11 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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'