From a9ff58683e85b48bdead5f40c422bcd83ae0d9a4 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 26 Jun 2019 22:56:30 +0200 Subject: [PATCH] Fix ClassVar filter for instances --- jedi/evaluate/context/instance.py | 5 ++-- jedi/evaluate/context/klass.py | 20 ++++++++++++--- jedi/evaluate/filters.py | 10 +++++--- jedi/evaluate/gradual/typing.py | 13 ++++++---- test/completion/pep0484_typing.py | 41 ++++++++++++++++++++++++++++++- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index d9851522..809ff568 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -462,12 +462,13 @@ class InstanceClassFilter(AbstractFilter): origin_scope=origin_scope, is_instance=True, )) + assert isinstance(self._class_filter, ClassFilter), self._class_filter def get(self, name): - return self._convert(self._class_filter.get(name)) + return self._convert(self._class_filter.get(name, from_instance=True)) def values(self): - return self._convert(self._class_filter.values()) + return self._convert(self._class_filter.values(from_instance=True)) def _convert(self, names): return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names] diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index f07ee125..e9b884c0 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -109,13 +109,27 @@ class ClassFilter(ParserTreeFilter): node = get_cached_parent_scope(self._used_names, node) return False - def _access_possible(self, name): + def _access_possible(self, name, from_instance): + # Filter for ClassVar variables + # TODO this is not properly done, yet. It just checks for the string + # ClassVar in the annotation, which can be quite imprecise. If we + # wanted to do this correct, we would have to resolve the ClassVar. + if not from_instance: + expr_stmt = name.get_definition() + if expr_stmt is not None and expr_stmt.type == 'expr_stmt': + annassign = expr_stmt.children[1] + if annassign.type == 'annassign': + # TODO this is not proper matching + if 'ClassVar' not in annassign.children[1].get_code(): + return False + + # Filter for name mangling of private variables like __foo return not name.value.startswith('__') or name.value.endswith('__') \ or self._equals_origin_scope() - def _filter(self, names): + def _filter(self, names, from_instance=False): names = super(ClassFilter, self)._filter(names) - return [name for name in names if self._access_possible(name)] + return [name for name in names if self._access_possible(name, from_instance)] class ClassMixin(object): diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index a65b3c49..e3c4f41d 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -74,20 +74,22 @@ class AbstractUsedNamesFilter(AbstractFilter): self._used_names = self._module_node.get_used_names() self.context = context - def get(self, name): + def get(self, name, **filter_kwargs): return self._convert_names(self._filter( - _get_definition_names(self._used_names, name) + _get_definition_names(self._used_names, name), + **filter_kwargs, )) def _convert_names(self, names): return [self.name_class(self.context, name) for name in names] - def values(self): + def values(self, **filter_kwargs): return self._convert_names( name for name_key in self._used_names for name in self._filter( - _get_definition_names(self._used_names, name_key) + _get_definition_names(self._used_names, name_key), + **filter_kwargs, ) ) diff --git a/jedi/evaluate/gradual/typing.py b/jedi/evaluate/gradual/typing.py index 1e46374d..df81821f 100644 --- a/jedi/evaluate/gradual/typing.py +++ b/jedi/evaluate/gradual/typing.py @@ -19,7 +19,7 @@ from jedi.evaluate.filters import FilterWrapper from jedi.evaluate.names import NameWrapper, AbstractTreeName, \ AbstractNameDefinition, ContextName from jedi.evaluate.helpers import is_string -from jedi.evaluate.context.klass import ClassMixin +from jedi.evaluate.context.klass import ClassMixin, ClassFilter _PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split() _TYPE_ALIAS_TYPES = { @@ -54,12 +54,15 @@ class _BaseTypingContext(Context): return self._tree_name def get_filters(self, *args, **kwargs): - # TODO this is obviously wrong. - class EmptyFilter(): - def get(self, name): + # TODO this is obviously wrong. Is it though? + class EmptyFilter(ClassFilter): + def __init__(self): + pass + + def get(self, name, **kwargs): return [] - def values(self): + def values(self, **kwargs): return [] yield EmptyFilter() diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index cfdb9853..fbade3af 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -295,7 +295,6 @@ import typing as t def union2(x: t.Union[int, str]): #? int() str() x - from typing import Union def union3(x: Union[int, str]): #? int() str() @@ -398,3 +397,43 @@ def cast_tests(): #? str() cast_tests() + +# ------------------------- +# instance/class vars +# ------------------------- + +# python >= 3.6 + +class VarClass: + var_instance1: int = 1 + var_instance2: float + var_class1: typing.ClassVar[str] = 1 + var_class2: typing.ClassVar[bytes] + + +#? ['var_class1', 'var_class2'] +VarClass.var_ +#? +VarClass.var_instance1 +#? +VarClass.var_instance2 +#? str() +VarClass.var_class1 +#? bytes() +VarClass.var_class2 +#? [] +VarClass.int + +d = VarClass() +#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2'] +d.var_ +#? int() +d.var_instance1 +#? float() +d.var_instance2 +#? str() +d.var_class1 +#? bytes() +d.var_class2 +#? [] +d.int