diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 731b6a71..beef3f6a 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -12,6 +12,7 @@ from jedi import debug from jedi.cache import underscore_memoization, memoize_method from jedi.parser.tree import Param, Base, Operator from jedi.evaluate.helpers import FakeName +from jedi.evaluate.filters import AbstractFilter from . import fake @@ -156,6 +157,9 @@ class CompiledObject(Base): def names_dicts(self, search_global, is_instance=False): return self._names_dict_ensure_one_dict(is_instance) + def get_filters(self, search_global, is_instance=False): + yield self._ensure_one_filter(is_instance) + @memoize_method def _names_dict_ensure_one_dict(self, is_instance): """ @@ -164,6 +168,14 @@ class CompiledObject(Base): """ return [LazyNamesDict(self._evaluator, self, is_instance)] + @memoize_method + def _ensure_one_filter(self, is_instance): + """ + search_global shouldn't change the fact that there's one dict, this way + there's only one `object`. + """ + return CompiledObjectFilter(self._evaluator, self, is_instance) + def get_subscope_by_name(self, name): if name in dir(self.obj): return CompiledName(self._evaluator, self, name).parent @@ -314,6 +326,50 @@ class LazyNamesDict(object): 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): + self._evaluator = evaluator + self._compiled_obj = compiled_obj + self._is_instance = is_instance + + @memoize_method + def get(self, name, until_position=None): + name = str(name) + try: + getattr(self._compiled_obj.obj, name) + except AttributeError: + return [] + 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, until_position=None): + raise NotImplementedError + 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 + + 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/filters.py b/jedi/evaluate/filters.py index b727da60..07878632 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -32,12 +32,12 @@ class ParserTreeFilter(AbstractFilter): def _filter(self, names, until_position): names = super(ParserTreeFilter, self)._filter(names, until_position) names = [n for n in names if n.is_definition()] - names = [n for n in names if n.get_parent_scope() == self._parser_scope] + names = [n for n in names if n.parent.get_parent_scope() == self._parser_scope] return names def get(self, name, until_position=None): try: - names = self._used_names[name] + names = self._used_names[str(name)] except KeyError: return [] @@ -45,3 +45,14 @@ class ParserTreeFilter(AbstractFilter): def values(self, name, until_position=None): return self._filter(self._used_names.values(), until_position) + + +class FunctionExecutionFilter(ParserTreeFilter): + def __init__(self, parser_scope, executed_function): + super(FunctionExecutionFilter, self).__init__(parser_scope) + self._executed_function = executed_function + + def _filter(self, names, until_position): + result = super(FunctionExecutionFilter, self)._filter(names, until_position) + + return [self._executed_function.name_for_position(name.start_pos) for name in result] diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index d7f8be09..ba243a08 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -34,6 +34,7 @@ from jedi.evaluate import flow_analysis from jedi.evaluate import param from jedi.evaluate import helpers from jedi.evaluate.cache import memoize_default +from jedi.evaluate.filters import ParserTreeFilter def filter_after_position(names, position, origin=None): @@ -238,6 +239,14 @@ class NameFinder(object): `names_dicts`), until a name fits. """ names = [] + for filter in get_global_filters(self._evaluator, self.scope): + names = filter.get(self.name_str, self.position) + if names: + break + debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self.name_str, + self.scope, names, self.position) + return names + for names_dict, position in names_dicts: names = self.names_dict_lookup(names_dict, position) if names: @@ -365,6 +374,8 @@ def _name_to_types(evaluator, name, scope): types = evaluator.eval_element(typ.node_from_name(name)) elif isinstance(typ, tree.Import): types = imports.ImportWrapper(evaluator, name).follow() + elif typ.isinstance(tree.Function, tree.Class): + types = [evaluator.wrap(typ)] elif typ.type == 'global_stmt': for s in _get_global_stmt_scopes(evaluator, typ, name): finder = NameFinder(evaluator, s, str(name)) @@ -610,6 +621,20 @@ def global_names_dict_generator(evaluator, scope, position): yield names_dict, None +def get_global_filters(evaluator, context): + """ + Returns all filters in order of priority for name resolution. + """ + while context is not None: + for filter in context.get_filters(search_global=True): + yield filter + context = evaluator.wrap(context.get_parent_scope()) + + # Add builtins to the global scope. + for filter in evaluator.BUILTINS.get_filters(search_global=True): + yield filter + + def check_tuple_assignments(evaluator, types, name): """ Checks if tuples are assigned. diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 1c1c48f9..48116e70 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -55,7 +55,7 @@ from jedi.evaluate import helpers from jedi.evaluate import param from jedi.evaluate import flow_analysis from jedi.evaluate import imports -from jedi.evaluate.filters import ParserTreeFilter +from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter class Executed(tree.Base): @@ -665,7 +665,7 @@ class FunctionExecution(Executed): def __init__(self, evaluator, base, *args, **kwargs): super(FunctionExecution, self).__init__(evaluator, base, *args, **kwargs) self._copy_dict = {} - funcdef = base.base_func + self._original_function = funcdef = base.base_func if isinstance(funcdef, mixed.MixedObject): # The extra information in mixed is not needed anymore. We can just # unpack it and give it the tree object. @@ -777,7 +777,7 @@ class FunctionExecution(Executed): del evaluator.predefined_if_name_dict_dict[for_stmt] def get_filters(self, search_global): - yield ParserTreeFilter(self.base) + yield FunctionExecutionFilter(self._original_function, self.base.base_func) @memoize_default(default=NO_DEFAULT) def _get_params(self):