diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 2eb876bb..49faa29a 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -151,6 +151,9 @@ class CompiledObject(Base): def names_dict(self): return LazyNamesDict(self._cls()) + def names_dicts(self): + yield self.names_dict + def scope_names_generator(self, position=None, add_class_vars=True): yield self, self.get_defined_names() diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 80510050..040acf9a 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -35,7 +35,8 @@ from jedi.evaluate.cache import memoize_default class NameFinder(object): def __init__(self, evaluator, scope, name_str, position=None): self._evaluator = evaluator - self.scope = scope + # Make sure that it's not just a syntax tree node. + self.scope = er.wrap(evaluator, scope) self.name_str = name_str self.position = position @@ -62,21 +63,24 @@ class NameFinder(object): def scopes(self, search_global=False): if search_global: - return get_names_of_scope(self._evaluator, self.scope, self.position) + return global_names_dict_generator(self._evaluator, self.scope, self.position) else: - return self.scope.scope_names_generator(self.position) + return ((n, None) for n in self.scope.names_dicts()) - def names_dict_lookup(self, scope, position): + def names_dict_lookup(self, names_dict, position): def get_param(el): if isinstance(el.parent, pr.Param) or isinstance(el.parent.parent, pr.Param): return scope.param_by_name(str(el)) return el try: - names = scope.names_dict[str(self.name_str)] + names = names_dict[str(self.name_str)] except KeyError: return [] + #print(names[0].parent, names[0].get_definition().get_parent_scope()) + # Just calculate the scope from the first + scope = er.wrap(self._evaluator, names[0].get_definition().get_parent_scope()) if isinstance(scope, (pr.CompFor, pr.Lambda)): return names @@ -110,10 +114,10 @@ class NameFinder(object): return [get_param(n) for n in last_names] return last_names - def filter_name(self, scope_names_generator, search_global=False): + def filter_name(self, names_dicts, search_global=False): """ - Filters all variables of a scope (which are defined in the - `scope_names_generator`), until the name fits. + Searches names that are defined in a scope (the different + `names_dicts`), until a name fits. """ # TODO Now this import is really ugly. Try to remove it. # It's possibly the only api dependency. @@ -124,6 +128,16 @@ class NameFinder(object): return [n for n in self.scope.get_magic_function_names() if str(n) == str(self.name_str)] + scope_names_generator = [] + name_list_scope = None # TODO delete + for names_dict, position in names_dicts: + #scope = parent + names = self.names_dict_lookup(names_dict, position) + if names: + break + #if isinstance(scope, (pr.Function, er.FunctionExecution)): + #position = None + # Need checked for now for the whole names_dict approach. That only # works on the first name_list_scope, the second one may be the same # with a different name set (e.g. ModuleWrapper yields the module @@ -170,7 +184,7 @@ class NameFinder(object): or isinstance(name_list_scope, (pr.Lambda, er.Instance, InterpreterNamespace)) \ or isinstance(scope, compiled.CompiledObject): # Always reachable. - print('nons', scope) + print('nons', name.get_parent_scope(), self.scope) names.append(name) else: print('yess', scope) @@ -465,6 +479,23 @@ def _check_isinstance_type(evaluator, element, search_name): return result +def global_names_dict_generator(evaluator, scope, position): + """ + For global lookups. + """ + while scope is not None: + for names_dict in scope.names_dicts(): + yield names_dict, position + if scope.type == 'funcdef': + # The position should be reset if the current scope is a function. + position = None + scope = er.wrap(evaluator, scope.get_parent_scope()) + + # Add builtins to the global scope. + for names_dict in compiled.builtin.names_dicts(): + yield names_dict, None + + def get_names_of_scope(evaluator, scope, position=None, star_search=True, include_builtin=True): """ Get all completions (names) possible for the current scope. The star search diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index bd96b61b..c78ff2e6 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -257,6 +257,13 @@ class Array(IterableWrapper): for _, names in scope.scope_names_generator(): yield self, names + @underscore_memoization + def names_dicts(self): + # `array.type` is a string with the type, e.g. 'list'. + scope = self._evaluator.find_types(compiled.builtin, self.type)[0] + scope = self._evaluator.execute(scope)[0] # builtins only have one class + return scope.names_dicts() + @common.safe_property def parent(self): return compiled.builtin diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 8de9b3c4..6de7b0b3 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -231,7 +231,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): return helpers.FakeName(unicode(name), self, name.start_pos) def __getattr__(self, name): - if name not in ['start_pos', 'end_pos', 'get_imports', + if name not in ['start_pos', 'end_pos', 'get_imports', 'type', 'doc', 'raw_doc', 'asserts']: raise AttributeError("Instance %s: Don't touch this (%s)!" % (self, name)) @@ -355,6 +355,9 @@ class Wrapper(pr.Base): def is_class(self): return False + def names_dicts(self): + yield self.names_dict + @property @underscore_memoization def name(self): @@ -440,7 +443,7 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)): def __getattr__(self, name): if name not in ['start_pos', 'end_pos', 'parent', 'asserts', 'raw_doc', 'doc', 'get_imports', 'get_parent_until', 'get_code', - 'subscopes', 'names_dict']: + 'subscopes', 'names_dict', 'type']: raise AttributeError("Don't touch this: %s of %s !" % (name, self)) return getattr(self.base, name) @@ -625,7 +628,11 @@ class FunctionExecution(Executed): self.base.names_dict return LazyDict(self.base.names_dict, self._copy_list) """ - + + def names_dicts(self): + self.children + yield dict((k, [self._copy_dict[v] for v in values]) + for k, values in self.base.names_dict.items()) @memoize_default(default=NO_DEFAULT) def _get_params(self): @@ -669,7 +676,7 @@ class FunctionExecution(Executed): return objects def __getattr__(self, name): - if name not in ['start_pos', 'end_pos', 'imports', '_sub_module']: + if name not in ['start_pos', 'end_pos', 'imports', '_sub_module', 'type']: raise AttributeError('Tried to access %s: %s. Why?' % (name, self)) return getattr(self.base, name) @@ -740,6 +747,16 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): if sub_modules: yield self, self._sub_modules() + def names_dicts(self): + yield self.base.names_dict + yield self._module_attributes_dict() + + for star_module in self.star_imports(): + yield star_module.names_dict + + yield dict((str(n), [n]) for n in self.base.global_names) + yield self._sub_modules_dict() + @cache_star_import @memoize_default([]) def star_imports(self): @@ -754,6 +771,15 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): modules += new return modules + @memoize_default() + def _module_attributes_dict(self): + def parent_callback(): + return self._evaluator.execute(compiled.create(self._evaluator, str))[0] + + names = ['__file__', '__package__', '__doc__', '__name__'] + # All the additional module attributes are strings. + return dict((n, [helpers.LazyName(n, parent_callback)]) for n in names) + @memoize_default() def _module_attributes(self): def parent_callback(): @@ -794,6 +820,32 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): return names + @memoize_default() + def _sub_modules_dict(self): + """ + Lists modules in the directory of this module (if this module is a + package). + """ + path = self._module.path + names = {} + if path is not None and path.endswith(os.path.sep + '__init__.py'): + mods = pkgutil.iter_modules([os.path.dirname(path)]) + for module_loader, name, is_pkg in mods: + fake_n = helpers.FakeName(name) + # It's obviously a relative import to the current module. + imp = helpers.FakeImport(fake_n, self, level=1) + fake_n.parent = imp + names[name] = fake_n + + # TODO add something like this in the future, its cleaner than the + # import hacks. + # ``os.path`` is a hardcoded exception, because it's a + # ``sys.modules`` modification. + #if str(self.name) == 'os': + # names.append(helpers.FakeName('path', parent=self)) + + return names + def __getattr__(self, name): return getattr(self._module, name) diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 9c92fa13..8cc38583 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -139,7 +139,7 @@ class Base(object): Returns the underlying scope. """ scope = self.parent - while scope.parent is not None: + while scope is not None: if include_flows and isinstance(scope, Flow): return scope if scope.is_scope(): @@ -623,6 +623,7 @@ class SubModule(Scope, Module): """ __slots__ = ('path', 'global_names', 'used_names', '_name', 'line_offset', 'use_as_parent', 'error_statement_stacks') + type = 'file_input' def __init__(self, children): """ @@ -1259,6 +1260,9 @@ class CompFor(Simple): arr.append(name) return dct + def names_dicts(self): + yield self.names_dict + def get_defined_names(self): return _defined_names(self.children[1])