diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 2b2ffc52..e58825bb 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -94,7 +94,7 @@ class Evaluator(object): # To memorize modules -> equals `sys.modules`. self.modules = {} # like `sys.modules`. 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.dynamic_params_depth = 0 self.is_analysis = False diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 4a76e3a2..dbce13c3 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -14,7 +14,7 @@ from jedi.cache import underscore_memoization, memoize_method from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ ContextNameMixin from jedi.evaluate.context import Context, LazyKnownContext -from jedi.evaluate.compiled.getattr_static import getattr_static +from jedi.evaluate.compiled.getattr_static import getattr_static from . import fake diff --git a/jedi/evaluate/compiled/mixed.py b/jedi/evaluate/compiled/mixed.py index 1919268a..122dbf9c 100644 --- a/jedi/evaluate/compiled/mixed.py +++ b/jedi/evaluate/compiled/mixed.py @@ -11,6 +11,7 @@ from jedi.cache import underscore_memoization from jedi.evaluate import imports from jedi.evaluate.context import Context from jedi.evaluate.cache import evaluator_function_cache +from jedi.evaluate.compiled.getattr_static import getattr_static class MixedObject(object): @@ -77,13 +78,14 @@ class MixedName(compiled.CompiledName): def infer(self): obj = self.parent_context.obj try: + # TODO use logic from compiled.CompiledObjectFilter obj = getattr(obj, self.string_name) except AttributeError: # Happens e.g. in properties of # PyQt4.QtGui.QStyleOptionComboBox.currentText # -> just set it to None obj = None - return [create(self._evaluator, obj, parent_context=self.parent_context)] + return [_create(self._evaluator, obj, parent_context=self.parent_context)] @property def api_type(self): @@ -116,21 +118,26 @@ def _load_module(evaluator, path, python_object): 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.""" - return (inspect.ismodule(python_object) or + 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)) + 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): try: - if not source_findable(python_object): - raise TypeError # Prevents computation of `repr` within inspect. + python_object = _get_object_to_check(python_object) path = inspect.getsourcefile(python_object) except TypeError: # 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') -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) compiled_object = compiled.create( @@ -210,6 +217,10 @@ def create(evaluator, obj, parent_context=None, *args): node_is_context=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( evaluator, diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index f2ef4b0d..1a132de1 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -171,11 +171,13 @@ def test_slice(): def test_getitem_side_effects(): class Foo2(): 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 foo = Foo2() - _assert_interpreter_complete('foo[0].', locals(), []) + _assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper']) def test_property_error_oldstyle(): @@ -252,3 +254,39 @@ def test_completion_param_annotations(): assert a._goto_definitions() == [] assert [d.name for d in b._goto_definitions()] == ['str'] 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'