From 1cccc832b6e4e8d9b4b574c415b83c61db6966ed Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 19 Jul 2019 01:27:37 +0200 Subject: [PATCH] Dataclass progress --- conftest.py | 8 +++++++ jedi/evaluate/context/klass.py | 15 ++++++++----- jedi/plugins/stdlib.py | 33 +++++++++++++++++++++++++++- test/test_evaluate/test_signature.py | 21 +++++++++++++++++- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/conftest.py b/conftest.py index e0914dcc..3c1b1d79 100644 --- a/conftest.py +++ b/conftest.py @@ -140,3 +140,11 @@ def skip_pre_python38(environment): # This if is just needed to avoid that tests ever skip way more than # they should for all Python versions. pytest.skip() + + +@pytest.fixture() +def skip_pre_python37(environment): + if environment.version_info < (3, 7): + # This if is just needed to avoid that tests ever skip way more than + # they should for all Python versions. + pytest.skip() diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index bd8a2bcc..b96e59bf 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -198,12 +198,7 @@ class ClassMixin(object): yield f if search_global: - yield ParserTreeFilter( - self.evaluator, - context=self, - until_position=until_position, - origin_scope=origin_scope - ) + yield self.get_global_filter(until_position, origin_scope) else: for cls in self.py__mro__(): if isinstance(cls, compiled.CompiledObject): @@ -227,6 +222,14 @@ class ClassMixin(object): next(instance_filters) yield next(instance_filters) + def get_global_filter(self, until_position=None, origin_scope=None): + return ParserTreeFilter( + self.evaluator, + context=self, + until_position=until_position, + origin_scope=origin_scope + ) + class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)): """ diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 0a6e36a6..06cd7f46 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -520,6 +520,37 @@ def _random_choice(sequences): ) +def _dataclass(obj, arguments): + for c in _follow_param(obj.evaluator, arguments, 0): + if c.is_class(): + return ContextSet([DataclassWrapper(c)]) + else: + return ContextSet([obj]) + return NO_CONTEXTS + + +class DataclassWrapper(ContextWrapper): + def get_signatures(self): + params = [] + for cls in reversed(self._wrapped_context.py__mro__()): + if isinstance(cls, ClassContext) and not cls.is_stub(): + filter_ = cls.get_global_filter() + print(filter_) + for name in filter_.values(): + d = name.tree_name.get_definition() + if d.type == 'expr_stmt' and d.children[1].type == 'annassign': + params.append() + return [DataclassSignature(cls, params)] + + +class DataclassSignature(AbstractSignature): + def __init__(self, context, params): + super(DataclassSignature, self).__init__(context) + self._params = params + + def get_param_names(self): + + class ItemGetterCallable(ContextWrapper): def __init__(self, instance, args_context_set): super(ItemGetterCallable, self).__init__(instance) @@ -602,7 +633,7 @@ _implemented = { }, 'dataclasses': { # For now this works at least better than Jedi trying to understand it. - 'dataclass': lambda obj, arguments: NO_CONTEXTS, + 'dataclass': _dataclass }, } diff --git a/test/test_evaluate/test_signature.py b/test/test_evaluate/test_signature.py index bddd9983..4a97e2e9 100644 --- a/test/test_evaluate/test_signature.py +++ b/test/test_evaluate/test_signature.py @@ -1,6 +1,8 @@ -import pytest +from textwrap import dedent from operator import ge, lt +import pytest + from jedi.evaluate.gradual.conversion import _stub_to_python_context_set @@ -60,3 +62,20 @@ def test_pow_signature(Script): 'pow(x: float, y: float, /) -> float', 'pow(x: int, y: int, z: int, /) -> Any', 'pow(x: int, y: int, /) -> Any'} + + +@pytest.mark.parametrize('decorator', ['@dataclass', '@dataclass(eq=True)']) +def test_dataclass_signature(Script, skip_pre_python37, decorator): + code = dedent(''' + class InventoryItem: + name: str + foo = 3 + unit_price: float + quantity_on_hand: int = 0 + + InventoryItem(''') + + code = 'from dataclasses import dataclass\n' + decorator + code + + sig, = Script(code).call_signatures() + assert [p.name for p in sig.params] == ['name', 'unit_price', 'quantity_on_hand']