From 9ac301d0c3bc1a8df16f55e045ce6b17ae9f2a35 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 16 Dec 2016 17:17:03 +0100 Subject: [PATCH] Refactor the mixed objects a bit to make at least some interpreter tests pass. --- jedi/api/interpreter.py | 20 +++--- jedi/evaluate/__init__.py | 8 ++- jedi/evaluate/compiled/__init__.py | 65 ++++-------------- jedi/evaluate/compiled/mixed.py | 104 +++++++++++++++++------------ jedi/evaluate/context.py | 5 +- jedi/evaluate/instance.py | 2 + test/test_api/test_interpreter.py | 3 +- 7 files changed, 93 insertions(+), 114 deletions(-) diff --git a/jedi/api/interpreter.py b/jedi/api/interpreter.py index c73913b0..4dbe480c 100644 --- a/jedi/api/interpreter.py +++ b/jedi/api/interpreter.py @@ -6,6 +6,7 @@ import copy from jedi.cache import underscore_memoization from jedi.evaluate import helpers from jedi.evaluate.representation import ModuleContext +from jedi.evaluate import compiled from jedi.evaluate.compiled import mixed from jedi.evaluate.context import Context @@ -25,22 +26,19 @@ class MixedModuleContext(Context): def get_node(self): return self.tree_node - def names_dicts(self, search_global): - for names_dict in self._module_context.names_dicts(search_global): - yield names_dict - - for namespace_obj in self._namespace_objects: - m = mixed.MixedObject(self.evaluator, namespace_obj, self.tree_node.name) - for names_dict in m.names_dicts(False): - yield names_dict - def get_filters(self, *args, **kwargs): for filter in self._module_context.get_filters(*args, **kwargs): yield filter for namespace_obj in self._namespace_objects: - m = mixed.MixedObject(self.evaluator, namespace_obj, self.tree_node.name) - for filter in m.get_filters(*args, **kwargs): + compiled_object = compiled.create(self.evaluator, namespace_obj) + mixed_object = mixed.MixedObject( + self.evaluator, + parent_context=self, + compiled_object=compiled_object, + tree_name=self.tree_node.name + ) + for filter in mixed_object.get_filters(*args, **kwargs): yield filter def __getattr__(self, name): diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 3e251319..ea4efacd 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -565,7 +565,7 @@ class Evaluator(object): if node.children[1].type == 'comp_for': return node - def from_scope_node(scope_node, child_is_funcdef=None): + def from_scope_node(scope_node, child_is_funcdef=None, is_nested=True): if scope_node == base_node: return base_context @@ -585,7 +585,9 @@ class Evaluator(object): parent_context, scope_node ) - return func.get_function_execution() + if is_nested: + return func.get_function_execution() + return func elif scope_node.type == 'classdef': class_context = er.ClassContext(self, scope_node, parent_context) if child_is_funcdef: @@ -607,4 +609,4 @@ class Evaluator(object): # object itself and not its contents. node = node.parent scope_node = parent_scope(node) - return from_scope_node(scope_node) + return from_scope_node(scope_node, is_nested=not node_is_context) diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index ab79bfc4..ecdb9821 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -231,6 +231,12 @@ class CompiledObject(Context): for part in self.obj: yield LazyKnownContext(create(self.evaluator, part)) + def py__name__(self): + try: + return self._get_class().__name__ + except AttributeError: + return None + @property def name(self): try: @@ -308,67 +314,22 @@ class CompiledContextName(ContextNameMixin, AbstractNameDefinition): self.parent_context = context.parent_context -class LazyNamesDict(object): - """ - A names_dict instance for compiled objects, resembles the parser.tree. - """ - name_class = CompiledName - - def __init__(self, evaluator, compiled_obj, is_instance=False): - self._evaluator = evaluator - self._compiled_obj = compiled_obj - self._is_instance = is_instance - - def __iter__(self): - return (v[0].value for v in self.values()) - - @memoize_method - def __getitem__(self, name): - try: - getattr(self._compiled_obj.obj, name) - except AttributeError: - raise KeyError('%s in %s not found.' % (name, self._compiled_obj)) - except Exception: - # This is a bit ugly. We're basically returning this to make - # lookups possible without having the actual attribute. However - # this makes proper completion possible. - return [FakeName(name, create(self._evaluator, None), is_definition=True)] - return [self.name_class(self._evaluator, self._compiled_obj, name)] - - def values(self): - obj = self._compiled_obj.obj - - values = [] - for name in dir(obj): - try: - values.append(self[name]) - except KeyError: - # The dir function can be wrong. - pass - - is_instance = self._is_instance or fake.is_class_instance(obj) - # ``dir`` doesn't include the type names. - if not inspect.ismodule(obj) and obj != type and not is_instance: - values += create(self._evaluator, type).names_dict.values() - return values - - class CompiledObjectFilter(AbstractFilter): """ A names_dict instance for compiled objects, resembles the parser.tree. """ name_class = CompiledName - def __init__(self, evaluator, compiled_obj, is_instance=False): + def __init__(self, evaluator, compiled_object, is_instance=False): super(CompiledObjectFilter, self).__init__(None) self._evaluator = evaluator - self._compiled_obj = compiled_obj + self._compiled_object = compiled_object self._is_instance = is_instance @memoize_method def get(self, name): name = str(name) - obj = self._compiled_obj.obj + obj = self._compiled_object.obj try: getattr(obj, name) if self._is_instance and name not in dir(obj): @@ -380,10 +341,10 @@ class CompiledObjectFilter(AbstractFilter): # lookups possible without having the actual attribute. However # this makes proper completion possible. return [FakeName(name, create(self._evaluator, None), is_definition=True)] - return [self._create(name)] + return [self._create_name(name)] def values(self): - obj = self._compiled_obj.obj + obj = self._compiled_object.obj names = [] for name in dir(obj): @@ -396,8 +357,8 @@ class CompiledObjectFilter(AbstractFilter): names += filter.values() return names - def _create(self, name): - return self.name_class(self._evaluator, self._compiled_obj, name) + def _create_name(self, name): + return self.name_class(self._evaluator, self._compiled_object, name) def dotted_from_fs_path(fs_path, sys_path): diff --git a/jedi/evaluate/compiled/mixed.py b/jedi/evaluate/compiled/mixed.py index 43a4c0b7..628e08d6 100644 --- a/jedi/evaluate/compiled/mixed.py +++ b/jedi/evaluate/compiled/mixed.py @@ -9,6 +9,7 @@ from jedi import common from jedi.parser.fast import FastParser from jedi.evaluate import compiled from jedi.cache import underscore_memoization +from jedi.evaluate import imports class MixedObject(object): @@ -28,67 +29,80 @@ class MixedObject(object): fewer special cases, because we in Python you don't have the same freedoms to modify the runtime. """ - def __init__(self, evaluator, obj, node_name): - self._evaluator = evaluator - self.obj = obj - self.node_name = node_name - self.definition = node_name.get_definition() + def __init__(self, evaluator, parent_context, compiled_object, tree_name): + self.evaluator = evaluator + self.compiled_object = compiled_object + self.obj = compiled_object.obj + self._tree_name = tree_name + name_module = tree_name.get_root_node() + if parent_context.get_node().get_root_node() != name_module: + from jedi.evaluate.representation import ModuleContext + module_context = ModuleContext(evaluator, name_module) + name = compiled_object.get_root_context().py__name__() + imports.add_module(evaluator, name, module_context) + else: + module_context = parent_context.get_root_context() - @property - def names_dict(self): - return LazyMixedNamesDict(self._evaluator, self) + self._context = module_context.create_context( + tree_name.parent, + node_is_context=True + ) - def names_dicts(self, search_global): - # TODO is this needed? - assert search_global is False - return [self.names_dict] - - def api_type(self): - mappings = { - 'expr_stmt': 'statement', - 'classdef': 'class', - 'funcdef': 'function', - 'file_input': 'module', - } - return mappings[self.definition.type] + def get_filters(self, *args, **kwargs): + yield MixedObjectFilter(self.evaluator, self) def __repr__(self): return '<%s: %s>' % (type(self).__name__, repr(self.obj)) def __getattr__(self, name): - return getattr(self.definition, name) + return getattr(self._context, name) class MixedName(compiled.CompiledName): """ The ``CompiledName._compiled_object`` is our MixedObject. """ - @property - @underscore_memoization - def parent(self): - return create(self._evaluator, getattr(self._compiled_obj.obj, self.name)) - - @parent.setter - def parent(self, value): - pass # Just ignore this, Name tries to overwrite the parent attribute. - @property def start_pos(self): - if isinstance(self.parent, MixedObject): - return self.parent.node_name.start_pos - - # This means a start_pos that doesn't exist (compiled objects). - return (0, 0) + contexts = list(self.infer()) + if not contexts: + # This means a start_pos that doesn't exist (compiled objects). + return (0, 0) + return contexts[0].name.start_pos @start_pos.setter def start_pos(self, value): # Ignore the __init__'s start_pos setter call. pass + @underscore_memoization + def infer(self): + obj = self.parent_context.obj + try: + obj = getattr(obj, self.string_name) + except AttributeError: + # Happens e.g. in properties of + # PyQt4.QtGui.QStyleOptionComboBox.currentText + # -> just set it to None + obj = None + return [create(self._evaluator, obj, parent_context=self.parent_context)] -class LazyMixedNamesDict(compiled.LazyNamesDict): + @property + def api_type(self): + return next(iter(self.infer())).api_type + + +class MixedObjectFilter(compiled.CompiledObjectFilter): name_class = MixedName + def __init__(self, evaluator, mixed_object, is_instance=False): + super(MixedObjectFilter, self).__init__( + evaluator, mixed_object, is_instance) + self._mixed_object = mixed_object + + #def _create(self, name): + #return MixedName(self._evaluator, self._compiled_object, name) + def parse(grammar, path): with open(path) as f: @@ -121,7 +135,7 @@ def find_syntax_node_name(evaluator, python_object): # We don't need to check names for modules, because there's not really # a way to write a module in a module in Python (and also __name__ can # be something like ``email.utils``). - return module + return module.name name_str = python_object.__name__ if name_str == '': @@ -159,9 +173,11 @@ def find_syntax_node_name(evaluator, python_object): @compiled.compiled_objects_cache('mixed_cache') -def create(evaluator, obj): - name = find_syntax_node_name(evaluator, obj) - if name is None: - return compiled.create(evaluator, obj) - else: - return MixedObject(evaluator, obj, name) +def create(evaluator, obj, parent_context=None, *args): + tree_name = find_syntax_node_name(evaluator, obj) + + compiled_object = compiled.create( + evaluator, obj, parent_context=parent_context.compiled_object) + if tree_name is None: + return compiled_object + return MixedObject(evaluator, parent_context, compiled_object, tree_name) diff --git a/jedi/evaluate/context.py b/jedi/evaluate/context.py index b3578a94..4ea56e03 100644 --- a/jedi/evaluate/context.py +++ b/jedi/evaluate/context.py @@ -2,10 +2,9 @@ from jedi.common import unite class Context(object): - api_type = 'instance' + api_type = None """ - Most contexts are just instances of something, therefore make this the - default to make subclassing a lot easier. + To be defined by subclasses. """ predefined_names = {} diff --git a/jedi/evaluate/instance.py b/jedi/evaluate/instance.py index d03a66e4..d5e85016 100644 --- a/jedi/evaluate/instance.py +++ b/jedi/evaluate/instance.py @@ -17,6 +17,8 @@ class AbstractInstanceContext(Context): """ This class is used to evaluate instances. """ + api_type = 'instance' + def __init__(self, evaluator, parent_context, class_context, var_args): super(AbstractInstanceContext, self).__init__(evaluator, parent_context) # Generated instances are classes that are just generated by self diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 0976241f..232a008d 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -60,7 +60,8 @@ def test_side_effect_completion(): side_effect = get_completion('SideEffectContainer', _GlobalNameSpace.__dict__) # It's a class that contains MixedObject. - assert isinstance(side_effect._definition.base, mixed.MixedObject) + context, = side_effect._name.infer() + assert isinstance(context, mixed.MixedObject) foo = get_completion('SideEffectContainer.foo', _GlobalNameSpace.__dict__) assert foo.name == 'foo'