diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f8ae757..dc9c6079 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,8 @@ Changelog - ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters with the actual alternatives. - Better support for enums/dataclasses +- When using Interpreter, properties are now executed, since a lot of people + have complained about this. Discussion in #1299. New APIs: diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 9fd884b6..8b00258e 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -436,6 +436,7 @@ class Interpreter(Script): >>> print(script.completions()[0].name) upper """ + _allow_descriptor_getattr_default = True def __init__(self, source, namespaces, **kwds): """ @@ -466,6 +467,7 @@ class Interpreter(Script): super(Interpreter, self).__init__(source, environment=environment, _project=Project(os.getcwd()), **kwds) self.namespaces = namespaces + self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default def _get_module(self): return interpreter.MixedModuleContext( diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 3b0a45a7..a4f7e902 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -106,6 +106,7 @@ class Evaluator(object): self.is_analysis = False self.project = project self.access_cache = {} + self.allow_descriptor_getattr = False self.reset_recursion_limitations() self.allow_different_encoding = True diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index 8775aea3..bd9d68cf 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -340,12 +340,16 @@ class DirectObjectAccess(object): def getattr_paths(self, name, default=_sentinel): try: return_obj = getattr(self._obj, name) - except AttributeError: - # Happens e.g. in properties of - # PyQt4.QtGui.QStyleOptionComboBox.currentText - # -> just set it to None + except Exception as e: if default is _sentinel: - raise + if isinstance(e, AttributeError): + # Happens e.g. in properties of + # PyQt4.QtGui.QStyleOptionComboBox.currentText + # -> just set it to None + raise + # Just in case anything happens, return an AttributeError. It + # should not crash. + raise AttributeError return_obj = default access = self._create_access(return_obj) if inspect.ismodule(return_obj): diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index c5ced978..731fce0b 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter): # Always use unicode objects in Python 2 from here. name = force_unicode(name) - if is_descriptor or not has_attribute: + if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute: return [self._get_cached_name(name, is_empty=True)] if self.is_instance and name not in dir_callback(): diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 1ed63d13..e97f4985 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -7,7 +7,7 @@ import pytest import jedi from jedi._compatibility import is_py3, py_version -from jedi.evaluate.compiled import mixed +from jedi.evaluate.compiled import mixed, context from importlib import import_module if py_version > 30: @@ -197,7 +197,13 @@ def test_getitem_side_effects(): _assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper']) -def test_property_error_oldstyle(): +@pytest.fixture(params=[False, True]) +def allow_descriptor_access_or_not(request, monkeypatch): + monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param) + return request.param + + +def test_property_error_oldstyle(allow_descriptor_access_or_not): lst = [] class Foo3: @property @@ -209,11 +215,14 @@ def test_property_error_oldstyle(): _assert_interpreter_complete('foo.bar', locals(), ['bar']) _assert_interpreter_complete('foo.bar.baz', locals(), []) - # There should not be side effects - assert lst == [] + if allow_descriptor_access_or_not: + assert lst == [1, 1] + else: + # There should not be side effects + assert lst == [] -def test_property_error_newstyle(): +def test_property_error_newstyle(allow_descriptor_access_or_not): lst = [] class Foo3(object): @property @@ -225,8 +234,22 @@ def test_property_error_newstyle(): _assert_interpreter_complete('foo.bar', locals(), ['bar']) _assert_interpreter_complete('foo.bar.baz', locals(), []) - # There should not be side effects - assert lst == [] + if allow_descriptor_access_or_not: + assert lst == [1, 1] + else: + # There should not be side effects + assert lst == [] + + +def test_property_content(): + class Foo3(object): + @property + def bar(self): + return 1 + + foo = Foo3() + def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions() + assert def_.name == 'int' @pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")