diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 49cab814..b5aed86c 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -264,6 +264,9 @@ class CompiledObject(Context): def negate(self): return create_from_access_path(self.evaluator, self.access_handle.negate()) + def get_metaclasses(self): + return NO_CONTEXTS + class CompiledName(AbstractNameDefinition): def __init__(self, evaluator, parent_context, name): diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index 2e4245f4..aac65e6a 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -49,6 +49,7 @@ from jedi.evaluate.arguments import unpack_arglist, ValuesArguments from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \ NO_CONTEXTS from jedi.evaluate.context.function import FunctionAndClassBase +from jedi.plugins import plugin_manager def apply_py__get__(context, instance, class_context): @@ -191,6 +192,11 @@ class ClassMixin(object): def get_filters(self, search_global=False, until_position=None, origin_scope=None, is_instance=False): + metaclasses = self.get_metaclasses() + if metaclasses: + for f in self.get_metaclass_filters(metaclasses): + yield f + if search_global: yield ParserTreeFilter( self.evaluator, @@ -247,12 +253,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa found.append(type_var) return found - @evaluator_method_cache(default=()) - def py__bases__(self): + def _get_bases_arguments(self): arglist = self.tree_node.get_super_arglist() if arglist: from jedi.evaluate import arguments - args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist) + return arguments.TreeArguments(self.evaluator, self.parent_context, arglist) + return None + + @evaluator_method_cache(default=()) + def py__bases__(self): + args = self._get_bases_arguments() + if args is not None: lst = [value for key, value in args.unpack() if key is None] if lst: return lst @@ -303,3 +314,24 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa def get_signatures(self): init_funcs = self.py__getattribute__('__init__') return [sig.bind(self) for sig in init_funcs.get_signatures()] + + @plugin_manager.decorate() + def get_metaclass_filters(self, metaclass): + debug.dbg('Unprocessed metaclass %s', metaclass) + return [] + + def get_metaclasses(self): + args = self._get_bases_arguments() + if args is not None: + m = [value for key, value in args.unpack() if key == 'metaclass'] + metaclasses = ContextSet.from_sets(lazy_context.infer() for lazy_context in m) + metaclasses = ContextSet(m for m in metaclasses if m.is_class()) + if metaclasses: + return metaclasses + + for lazy_base in self.py__bases__(): + for context in lazy_base.infer(): + contexts = context.get_metaclasses() + if contexts: + return contexts + return NO_CONTEXTS diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 464c4c1b..088fb698 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -13,6 +13,7 @@ import parso from jedi._compatibility import force_unicode from jedi import debug +from jedi.evaluate.utils import safe_property from jedi.evaluate.helpers import get_str_or_none from jedi.evaluate.arguments import ValuesArguments, \ repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper @@ -26,8 +27,10 @@ from jedi.evaluate.context import ClassContext, ModuleContext, \ from jedi.evaluate.context import iterable from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \ LazyKnownContexts +from jedi.evaluate.names import ContextName from jedi.evaluate.syntax_tree import is_string -from jedi.evaluate.filters import AttributeOverwrite, publish_method +from jedi.evaluate.filters import AttributeOverwrite, publish_method, \ + ParserTreeFilter, DictFilter # Copied from Python 3.6's stdlib. @@ -602,3 +605,41 @@ _implemented = { 'dataclass': lambda obj, arguments: NO_CONTEXTS, }, } + + +def get_metaclass_filters(func): + def wrapper(cls, metaclasses): + for metaclass in metaclasses: + if metaclass.py__name__() == 'EnumMeta' \ + and metaclass.get_root_context().py__name__() == 'enum': + print('cont', cls) + filter_ = ParserTreeFilter(cls.evaluator, context=cls) + return [DictFilter({ + name.string_name: EnumInstance(cls, name).name for name in filter_.values() + })] + return func(cls, metaclasses) + return wrapper + + +class EnumInstance(LazyContextWrapper): + def __init__(self, cls, name): + self.evaluator = cls.evaluator + self._cls = cls # Corresponds to super().__self__ + self._name = name + self.tree_node = self._name.tree_name + + @safe_property + def name(self): + return ContextName(self, self._name.tree_name) + + def _get_wrapped_context(self): + obj, = self._cls.execute_evaluated() + return obj + + def get_filters(self, search_global=False, position=None, origin_scope=None): + yield DictFilter(dict( + name=self._name.string_name, + value=self._name, + )) + for f in self._get_wrapped_context().get_filters(): + yield f diff --git a/test/completion/stdlib.py b/test/completion/stdlib.py index 7127efea..843b0acf 100644 --- a/test/completion/stdlib.py +++ b/test/completion/stdlib.py @@ -290,3 +290,31 @@ class Test(metaclass=Meta): result = super(Test, self).test_function() #? [] result. + +# ----------------- +# Enum +# ----------------- + +# python >= 3.4 +import enum + +class X(enum.Enum): + attr_x = 3 + attr_y = 2.0 + +#? ['mro'] +X.mro +#? ['attr_x', 'attr_y'] +X.attr_ +#? str() +X.attr_x.name +#? int() +X.attr_x.value +#? str() +X.attr_y.name +#? float() +X.attr_y.value +#? str() +X().name +#? float() +X().attr_x.attr_y.value diff --git a/test/test_evaluate/test_imports.py b/test/test_evaluate/test_imports.py index 6ee101ed..0edc3a8e 100644 --- a/test/test_evaluate/test_imports.py +++ b/test/test_evaluate/test_imports.py @@ -472,6 +472,6 @@ def test_relative_import_star(Script): from . import * furl.c """ - script = jedi.Script(source,3,len("furl.c"), 'export.py') + script = jedi.Script(source, 3, len("furl.c"), 'export.py') assert script.completions()