From 9d4786ddcb938274e9187275e3ab5e18de9aee8f Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 2 Dec 2016 22:13:45 +0100 Subject: [PATCH] Refactor the descriptor logic. --- jedi/evaluate/compiled/__init__.py | 18 +++++++----------- jedi/evaluate/finder.py | 25 ++----------------------- jedi/evaluate/instance.py | 16 ++++++++-------- jedi/evaluate/representation.py | 25 +++++++++++++++++++++++-- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index d30e01e5..95214fa4 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -508,6 +508,7 @@ def _parse_function_doc(doc): def _create_from_name(evaluator, module, compiled_object, name): obj = compiled_object.obj + faked = None try: faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name) if faked.type == 'funcdef': @@ -523,7 +524,7 @@ def _create_from_name(evaluator, module, compiled_object, name): # PyQt4.QtGui.QStyleOptionComboBox.currentText # -> just set it to None obj = None - return create(evaluator, obj, parent_context=compiled_object) + return create(evaluator, obj, parent_context=compiled_object, faked=faked) def builtin_from_name(evaluator, string): @@ -558,21 +559,17 @@ def compiled_objects_cache(attribute_name): Caching the id has the advantage that an object doesn't need to be hashable. """ - def wrapper(evaluator, obj, parent_context=None, module=None): + def wrapper(evaluator, obj, parent_context=None, module=None, faked=None): cache = getattr(evaluator, attribute_name) # Do a very cheap form of caching here. key = id(obj), id(parent_context) try: return cache[key][0] except KeyError: - # TODO this whole decorator looks way too ugly and this if - # doesn't make it better. Find a more generic solution. - if parent_context or module: - result = func(evaluator, obj, parent_context, module) - else: - result = func(evaluator, obj) + # TODO this whole decorator is way too ugly + result = func(evaluator, obj, parent_context, module, faked) # Need to cache all of them, otherwise the id could be overwritten. - cache[key] = result, obj, parent_context, module + cache[key] = result, obj, parent_context, module, faked return result return wrapper @@ -580,12 +577,11 @@ def compiled_objects_cache(attribute_name): @compiled_objects_cache('compiled_cache') -def create(evaluator, obj, parent_context=None, module=None): +def create(evaluator, obj, parent_context=None, module=None, faked=None): """ A very weird interface class to this module. The more options provided the more acurate loading compiled objects is. """ - faked = None if inspect.ismodule(obj): if parent_context is not None: # Modules don't have parents, be careful with caching: recurse. diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 9cc6e382..2fbe7f48 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -266,6 +266,7 @@ class NameFinder(object): for filter in filters: names = filter.get(self._name) if names: + self._last_used_filter = filter break debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name, self._context, names, self._position) @@ -310,13 +311,7 @@ class NameFinder(object): def _names_to_types(self, names, attribute_lookup): types = set() - for name in names: - new_types = name.infer() - if isinstance(self._context, (er.ClassContext, AbstractInstanceContext)) \ - and attribute_lookup: - types |= set(self._resolve_descriptors(name, new_types)) - else: - types |= set(new_types) + types = unite(name.infer() for name in names) debug.dbg('finder._names_to_types: %s -> %s', names, types) if not names and isinstance(self._context, AbstractInstanceContext): @@ -338,22 +333,6 @@ class NameFinder(object): break return types - def _resolve_descriptors(self, name, types): - if not isinstance(name, ContextName): - # Compiled names and other stuff should just be ignored when it - # comes to descriptors. - return types - - result = set() - for r in types: - try: - desc_return = r.get_descriptor_returns - except AttributeError: - result.add(r) - else: - result |= desc_return(self._context) - return result - def _name_to_types(evaluator, context, name): types = [] diff --git a/jedi/evaluate/instance.py b/jedi/evaluate/instance.py index c0258c5d..5190c63f 100644 --- a/jedi/evaluate/instance.py +++ b/jedi/evaluate/instance.py @@ -62,8 +62,7 @@ class AbstractInstanceContext(Context): for name in names ) - def get_descriptor_returns(self, obj): - """ Throws a KeyError if there's no method. """ + def py__get__(self, obj): # Arguments in __get__ descriptors are obj, class. # `method` is the new parent of the array, don't know if that's good. names = self.get_function_slot_names('__get__') @@ -277,21 +276,22 @@ class LazyInstanceName(filters.TreeNameDefinition): class LazyInstanceClassName(LazyInstanceName): def infer(self): - for v in super(LazyInstanceClassName, self).infer(): - if isinstance(v, er.FunctionContext): + for result_context in super(LazyInstanceClassName, self).infer(): + if isinstance(result_context, er.FunctionContext): # Classes are never used to resolve anything within the # functions. Only other functions and modules will resolve # those things. - parent_context = v.parent_context + parent_context = result_context.parent_context while isinstance(parent_context, er.ClassContext): parent_context = parent_context.parent_context yield BoundMethod( - v.evaluator, self._instance, self.class_context, - parent_context, v.funcdef + result_context.evaluator, self._instance, self.class_context, + parent_context, result_context.funcdef ) else: - yield v + for c in er.apply_py__get__(result_context, self._instance): + yield c class InstanceClassFilter(filters.ParserTreeFilter): diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index a8923548..00a0316d 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -56,7 +56,7 @@ from jedi.evaluate import imports from jedi.evaluate import helpers from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \ GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \ - ParamName, AnonymousInstanceParamName + ParamName, AnonymousInstanceParamName, TreeNameDefinition from jedi.evaluate.dynamic import search_params from jedi.evaluate import context @@ -74,6 +74,27 @@ class Executed(context.TreeContext): return True +def apply_py__get__(context, base_context): + try: + method = context.py__get__ + except AttributeError: + yield context + else: + for descriptor_context in method(base_context): + yield descriptor_context + + +class ClassName(TreeNameDefinition): + def infer(self): + for result_context in super(ClassName, self).infer(): + for c in apply_py__get__(result_context, self.parent_context): + yield c + + +class ClassFilter(ParserTreeFilter): + name_class = ClassName + + class ClassContext(use_metaclass(CachedMetaClass, context.TreeContext)): """ This class is not only important to extend `tree.Class`, it is also a @@ -164,7 +185,7 @@ class ClassContext(use_metaclass(CachedMetaClass, context.TreeContext)): for filter in scope.get_filters(is_instance=is_instance): yield filter else: - yield ParserTreeFilter(self.evaluator, self, scope.classdef, origin_scope=origin_scope) + yield ClassFilter(self.evaluator, self, scope.classdef, origin_scope=origin_scope) def is_class(self): return True