Refactor the descriptor logic.

This commit is contained in:
Dave Halter
2016-12-02 22:13:45 +01:00
parent 565989cf07
commit 9d4786ddcb
4 changed files with 40 additions and 44 deletions
+7 -11
View File
@@ -508,6 +508,7 @@ def _parse_function_doc(doc):
def _create_from_name(evaluator, module, compiled_object, name): def _create_from_name(evaluator, module, compiled_object, name):
obj = compiled_object.obj obj = compiled_object.obj
faked = None
try: try:
faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name) faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name)
if faked.type == 'funcdef': if faked.type == 'funcdef':
@@ -523,7 +524,7 @@ def _create_from_name(evaluator, module, compiled_object, name):
# PyQt4.QtGui.QStyleOptionComboBox.currentText # PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None # -> just set it to None
obj = 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): 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 Caching the id has the advantage that an object doesn't need to be
hashable. 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) cache = getattr(evaluator, attribute_name)
# Do a very cheap form of caching here. # Do a very cheap form of caching here.
key = id(obj), id(parent_context) key = id(obj), id(parent_context)
try: try:
return cache[key][0] return cache[key][0]
except KeyError: except KeyError:
# TODO this whole decorator looks way too ugly and this if # TODO this whole decorator is way too ugly
# doesn't make it better. Find a more generic solution. result = func(evaluator, obj, parent_context, module, faked)
if parent_context or module:
result = func(evaluator, obj, parent_context, module)
else:
result = func(evaluator, obj)
# Need to cache all of them, otherwise the id could be overwritten. # 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 result
return wrapper return wrapper
@@ -580,12 +577,11 @@ def compiled_objects_cache(attribute_name):
@compiled_objects_cache('compiled_cache') @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 A very weird interface class to this module. The more options provided the
more acurate loading compiled objects is. more acurate loading compiled objects is.
""" """
faked = None
if inspect.ismodule(obj): if inspect.ismodule(obj):
if parent_context is not None: if parent_context is not None:
# Modules don't have parents, be careful with caching: recurse. # Modules don't have parents, be careful with caching: recurse.
+2 -23
View File
@@ -266,6 +266,7 @@ class NameFinder(object):
for filter in filters: for filter in filters:
names = filter.get(self._name) names = filter.get(self._name)
if names: if names:
self._last_used_filter = filter
break break
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name, debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
self._context, names, self._position) self._context, names, self._position)
@@ -310,13 +311,7 @@ class NameFinder(object):
def _names_to_types(self, names, attribute_lookup): def _names_to_types(self, names, attribute_lookup):
types = set() types = set()
for name in names: types = unite(name.infer() 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)
debug.dbg('finder._names_to_types: %s -> %s', names, types) debug.dbg('finder._names_to_types: %s -> %s', names, types)
if not names and isinstance(self._context, AbstractInstanceContext): if not names and isinstance(self._context, AbstractInstanceContext):
@@ -338,22 +333,6 @@ class NameFinder(object):
break break
return types 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): def _name_to_types(evaluator, context, name):
types = [] types = []
+8 -8
View File
@@ -62,8 +62,7 @@ class AbstractInstanceContext(Context):
for name in names for name in names
) )
def get_descriptor_returns(self, obj): def py__get__(self, obj):
""" Throws a KeyError if there's no method. """
# Arguments in __get__ descriptors are obj, class. # Arguments in __get__ descriptors are obj, class.
# `method` is the new parent of the array, don't know if that's good. # `method` is the new parent of the array, don't know if that's good.
names = self.get_function_slot_names('__get__') names = self.get_function_slot_names('__get__')
@@ -277,21 +276,22 @@ class LazyInstanceName(filters.TreeNameDefinition):
class LazyInstanceClassName(LazyInstanceName): class LazyInstanceClassName(LazyInstanceName):
def infer(self): def infer(self):
for v in super(LazyInstanceClassName, self).infer(): for result_context in super(LazyInstanceClassName, self).infer():
if isinstance(v, er.FunctionContext): if isinstance(result_context, er.FunctionContext):
# Classes are never used to resolve anything within the # Classes are never used to resolve anything within the
# functions. Only other functions and modules will resolve # functions. Only other functions and modules will resolve
# those things. # those things.
parent_context = v.parent_context parent_context = result_context.parent_context
while isinstance(parent_context, er.ClassContext): while isinstance(parent_context, er.ClassContext):
parent_context = parent_context.parent_context parent_context = parent_context.parent_context
yield BoundMethod( yield BoundMethod(
v.evaluator, self._instance, self.class_context, result_context.evaluator, self._instance, self.class_context,
parent_context, v.funcdef parent_context, result_context.funcdef
) )
else: else:
yield v for c in er.apply_py__get__(result_context, self._instance):
yield c
class InstanceClassFilter(filters.ParserTreeFilter): class InstanceClassFilter(filters.ParserTreeFilter):
+23 -2
View File
@@ -56,7 +56,7 @@ from jedi.evaluate import imports
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \ from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \ GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \
ParamName, AnonymousInstanceParamName ParamName, AnonymousInstanceParamName, TreeNameDefinition
from jedi.evaluate.dynamic import search_params from jedi.evaluate.dynamic import search_params
from jedi.evaluate import context from jedi.evaluate import context
@@ -74,6 +74,27 @@ class Executed(context.TreeContext):
return True 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)): class ClassContext(use_metaclass(CachedMetaClass, context.TreeContext)):
""" """
This class is not only important to extend `tree.Class`, it is also a 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): for filter in scope.get_filters(is_instance=is_instance):
yield filter yield filter
else: 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): def is_class(self):
return True return True