diff --git a/jedi/api/interpreter.py b/jedi/api/interpreter.py index 0359675d..8b9b4fc6 100644 --- a/jedi/api/interpreter.py +++ b/jedi/api/interpreter.py @@ -102,7 +102,7 @@ class LazyName(helpers.FakeName): 'should be part of sys.modules.') if parser_path: - assert len(parser_path) == 1 + #assert len(parser_path) == 1 found = list(self._evaluator.find_types(mod, parser_path[0], search_global=True)) else: diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 29a9233d..54554f6e 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -234,10 +234,40 @@ class CompiledObject(Base): return [] # Builtins don't have imports +class CompiledName(FakeName): + def __init__(self, evaluator, compiled_obj, name): + super(CompiledName, self).__init__(name) + self._evaluator = evaluator + self._compiled_obj = compiled_obj + self.name = name + + def __repr__(self): + try: + name = self._compiled_obj.name # __name__ is not defined all the time + except AttributeError: + name = None + return '<%s: (%s).%s>' % (type(self).__name__, name, self.name) + + def is_definition(self): + return True + + @property + @underscore_memoization + def parent(self): + module = self._compiled_obj.get_parent_until() + return _create_from_name(self._evaluator, module, self._compiled_obj, self.name) + + @parent.setter + def parent(self, value): + pass # Just ignore this, FakeName tries to overwrite the parent attribute. + + 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): self._evaluator = evaluator self._compiled_obj = compiled_obj @@ -252,7 +282,7 @@ class LazyNamesDict(object): getattr(self._compiled_obj.obj, name) except AttributeError: raise KeyError('%s in %s not found.' % (name, self._compiled_obj)) - return [CompiledName(self._evaluator, self._compiled_obj, name)] + return [self.name_class(self._evaluator, self._compiled_obj, name)] def values(self): obj = self._compiled_obj.obj @@ -265,40 +295,12 @@ class LazyNamesDict(object): # The dir function can be wrong. pass - # dir doesn't include the type names. + # ``dir`` doesn't include the type names. if not inspect.ismodule(obj) and obj != type and not self._is_instance: values += create(self._evaluator, type).names_dict.values() return values -class CompiledName(FakeName): - def __init__(self, evaluator, obj, name): - super(CompiledName, self).__init__(name) - self._evaluator = evaluator - self._obj = obj - self.name = name - - def __repr__(self): - try: - name = self._obj.name # __name__ is not defined all the time - except AttributeError: - name = None - return '<%s: (%s).%s>' % (type(self).__name__, name, self.name) - - def is_definition(self): - return True - - @property - @underscore_memoization - def parent(self): - module = self._obj.get_parent_until() - return _create_from_name(self._evaluator, module, self._obj, self.name) - - @parent.setter - def parent(self, value): - pass # Just ignore this, FakeName tries to overwrite the parent attribute. - - def dotted_from_fs_path(fs_path, sys_path): """ Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e. diff --git a/jedi/evaluate/compiled/mixed.py b/jedi/evaluate/compiled/mixed.py index 90aa6faa..6cbb1506 100644 --- a/jedi/evaluate/compiled/mixed.py +++ b/jedi/evaluate/compiled/mixed.py @@ -4,9 +4,9 @@ Used only for REPL Completion. import inspect -from jedi.parser import load_grammar from jedi.parser.fast import FastParser from jedi.evaluate import compiled +from jedi.cache import underscore_memoization, memoize_method class MixedObject(object): @@ -20,10 +20,41 @@ class MixedObject(object): This combined logic makes it possible to provide more powerful REPL completion. It allows side effects that are not noticable with the default parser structure to still be completeable. + + The biggest difference from CompiledObject to MixedObject is that we are + generally dealing with Python code and not with C code. This will generate + fewer special cases, because we in Python you don't have the same freedoms + to modify the runtime. """ - def __init__(self, evaluator, obj): + 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 names_dicts(self): + return [LazyMixedNamesDict(self._evaluator, self, is_instance=False)] + + def __getattr__(self, name): + return getattr(self._definition, 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, self.name)) + + @parent.setter + def parent(self, value): + pass # Just ignore this, FakeName tries to overwrite the parent attribute. + + +class LazyMixedNamesDict(compiled.LazyNamesDict): + name_class = MixedName def _load_module(evaluator, path, python_object): @@ -34,30 +65,54 @@ def _load_module(evaluator, path, python_object): return module -def find_syntax_node(evaluator, python_object): +def find_syntax_node_name(evaluator, python_object): path = inspect.getsourcefile(python_object) if path is None: return None module = _load_module(evaluator, path, python_object) + if inspect.ismodule(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 + try: + names = module.used_names[python_object.__name__] + except NameError: + return None + + names = [n for n in names if n.is_definition()] + try: code = python_object.__code__ - except AttributeError: # By using the line number of a code object we make the lookup in a # file pretty easy. There's still a possibility of people defining # stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people # do so we just don't care. line_nr = code.co_firstlineno - return None + except AttributeError: + pass + else: + line_names = [name for name in names if name.start_pos[0] == line_nr] + # There's a chance that the object is not available anymore, because + # the code has changed in the background. + if line_names: + return line_names[-1] + + # It's really hard to actually get the right definition, here as a last + # resort we just return the last one. This chance might lead to odd + # completions at some points but will lead to mostly correct type + # inference, because people tend to define a public name in a module only + # once. + return names[-1] -@compiled_objects_cache +@compiled.compiled_objects_cache def create(evaluator, obj): - node = find_syntax_node(obj) - if node is None: + name = find_syntax_node_name(obj) + if name is None: return compiled.create(evaluator, obj) else: - return MixedObject(evaluator, obj) + return MixedObject(evaluator, obj, name)