From 358472b21f343aaf75537160781ece245d8ebd75 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 10 May 2014 14:12:36 +0200 Subject: [PATCH 1/2] improved star import support preparation --- jedi/evaluate/finder.py | 10 +++++++--- jedi/evaluate/imports.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index b4e2dc25..5f9c13c7 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -43,9 +43,11 @@ class NameFinder(object): if search_global: return get_names_of_scope(self._evaluator, self.scope, self.position) else: - if isinstance(self.scope, er.Instance): - return self.scope.scope_generator() - else: + try: + # Use scope generators if parts of it (e.g. sub classes or star + # imports). + gen = self.scope.scope_generator + except AttributeError: if isinstance(self.scope, er.Class): # classes are only available directly via chaining? # strange stuff... @@ -53,6 +55,8 @@ class NameFinder(object): else: names = _get_defined_names_for_position(self.scope, self.position) return iter([(self.scope, names)]) + else: + return gen() def filter_name(self, scope_generator): """ diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 43388e20..890e3bb7 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -239,6 +239,23 @@ class NestedImportModule(pr.Module): self._module) +class StarImportModule(pr.Module): + """ + Used if a module contains star imports. + """ + def __init__(self, module, star_import_modules): + self._module = module + self._star_import_modules = star_import_modules + + def scope_generator(self): + yield self.module + for s in self._star_import_modules: + yield s + + def __getattr__(self, name): + return getattr(self._module, name) + + def get_importer(evaluator, import_path, module, level=0): """ Checks the evaluator caches first, which resembles the ``sys.modules`` From 60971245d6b395f1d3a6e81e24dfc20e76d83105 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 10 May 2014 16:53:27 +0200 Subject: [PATCH 2/2] star imports now have their own class, which will be important for AttributeError detection --- jedi/api/__init__.py | 8 ++++---- jedi/evaluate/finder.py | 10 +++++----- jedi/evaluate/imports.py | 28 ++++++++++++++++++++-------- jedi/evaluate/representation.py | 2 +- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 48e8a56d..e3629917 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -204,11 +204,11 @@ class Script(object): scopes = list(self._prepare_goto(path, True)) except NotFoundError: scopes = [] - scope_generator = get_names_of_scope(self._evaluator, - self._parser.user_scope(), - self._pos) + scope_names_generator = get_names_of_scope(self._evaluator, + self._parser.user_scope(), + self._pos) completions = [] - for scope, name_list in scope_generator: + for scope, name_list in scope_names_generator: for c in name_list: completions.append((c, scope)) else: diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 5f9c13c7..710106f0 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -46,7 +46,7 @@ class NameFinder(object): try: # Use scope generators if parts of it (e.g. sub classes or star # imports). - gen = self.scope.scope_generator + gen = self.scope.scope_names_generator except AttributeError: if isinstance(self.scope, er.Class): # classes are only available directly via chaining? @@ -58,13 +58,13 @@ class NameFinder(object): else: return gen() - def filter_name(self, scope_generator): + def filter_name(self, scope_names_generator): """ Filters all variables of a scope (which are defined in the - `scope_generator`), until the name fits. + `scope_names_generator`), until the name fits. """ result = [] - for nscope, name_list in scope_generator: + for nscope, name_list in scope_names_generator: break_scopes = [] if not isinstance(nscope, compiled.CompiledObject): # Here is the position stuff happening (sorting of variables). @@ -443,7 +443,7 @@ def get_names_of_scope(evaluator, scope, position=None, star_search=True, includ and scope.type() == 'class' and in_func_scope != scope): try: if isinstance(scope, er.Instance): - for g in scope.scope_generator(): + for g in scope.scope_names_generator(): yield g else: yield scope, _get_defined_names_for_position(scope, position, in_func_scope) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 890e3bb7..59e73a7b 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -178,7 +178,9 @@ class ImportWrapper(pr.Base): else: scopes = [scope] - scopes += remove_star_imports(self._evaluator, scope) + star_imports = remove_star_imports(self._evaluator, scope) + if star_imports: + scopes = [StarImportModule(scopes[0], star_imports)] # follow the rest of the import (not FS -> classes, functions) if len(rest) > 1 or rest and self.is_like_search: @@ -235,8 +237,7 @@ class NestedImportModule(pr.Module): return getattr(self._module, name) def __repr__(self): - return "<%s: %s>" % (self.__class__.__name__, - self._module) + return "<%s: %s>" % (self.__class__.__name__, self._module) class StarImportModule(pr.Module): @@ -245,16 +246,25 @@ class StarImportModule(pr.Module): """ def __init__(self, module, star_import_modules): self._module = module - self._star_import_modules = star_import_modules + self.star_import_modules = star_import_modules - def scope_generator(self): - yield self.module - for s in self._star_import_modules: - yield s + def scope_names_generator(self): + yield self._module, self._module.get_defined_names() + for s in self.star_import_modules: + yield s, s.get_defined_names() + + def get_defined_names(self): + result = self._module.get_defined_names() + for m in self.star_import_modules: + result += m.get_defined_names() + return result def __getattr__(self, name): return getattr(self._module, name) + def __repr__(self): + return "<%s: %s>" % (self.__class__.__name__, self._module) + def get_importer(evaluator, import_path, module, level=0): """ @@ -473,6 +483,8 @@ def remove_star_imports(evaluator, scope, ignored_modules=()): and follow these modules. """ + if isinstance(scope, StarImportModule): + return scope.star_import_modules modules = strip_imports(evaluator, (i for i in scope.get_imports() if i.star)) new = [] for m in modules: diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 070e502f..e367c10a 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -149,7 +149,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): names.append(InstanceElement(self._evaluator, self, var, True)) return names - def scope_generator(self): + def scope_names_generator(self): """ An Instance has two scopes: The scope with self names and the class scope. Instance variables have priority over the class scope.