diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 151a0a91..b4c71b81 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,11 @@ Changelog 0.13.3 (2019-02-24) +++++++++++++++++++ +- reworked full_name + +0.13.3 (2019-02-24) ++++++++++++++++++++ + - Fixed an issue with embedded Python, see https://github.com/davidhalter/jedi-vim/issues/870 0.13.2 (2018-12-15) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 297329af..583c4648 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -9,7 +9,7 @@ import warnings from parso.python.tree import search_ancestor from jedi import settings -from jedi.evaluate.utils import ignored, unite +from jedi.evaluate.utils import unite from jedi.cache import memoize_method from jedi.evaluate import imports from jedi.evaluate import compiled @@ -49,8 +49,7 @@ class BaseDefinition(object): '_io': 'io', '_functools': 'functools', '_sqlite3': 'sqlite3', - '__builtin__': '', - 'builtins': '', + '__builtin__': 'builtins', } _tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in { @@ -162,46 +161,6 @@ class BaseDefinition(object): return context.api_type return self._name.api_type - def _path(self): - """The path to a module/class/function definition.""" - def to_reverse(): - name = self._name - if name.api_type == 'module': - try: - name = list(name.infer())[0].name - except IndexError: - pass - - if name.api_type in 'module': - module_contexts = name.infer() - if module_contexts: - module_context, = module_contexts - for n in reversed(module_context.py__name__().split('.')): - yield n - else: - # We don't really know anything about the path here. This - # module is just an import that would lead in an - # ImportError. So simply return the name. - yield name.string_name - return - else: - yield name.string_name - - parent_context = name.parent_context - while parent_context is not None: - try: - method = parent_context.py__name__ - except AttributeError: - try: - yield parent_context.name.string_name - except AttributeError: - pass - else: - for name in reversed(method().split('.')): - yield name - parent_context = parent_context.parent_context - return reversed(list(to_reverse())) - @property def module_name(self): """ @@ -301,18 +260,20 @@ class BaseDefinition(object): be `````. However most users find the latter more practical. """ - path = list(self._path()) - # TODO add further checks, the mapping should only occur on stdlib. - if not path: - return None # for keywords the path is empty + if not self._name.is_context_name: + return None - with ignored(KeyError): - path[0] = self._mapping[path[0]] - for key, repl in self._tuple_mapping.items(): - if tuple(path[:len(key)]) == key: - path = [repl] + path[len(key):] + names = self._name.get_qualified_names(include_module_names=True) + if names is None: + return names - return '.'.join(path if path[0] else path[1:]) + names = list(names) + try: + names[0] = self._mapping[names[0]] + except KeyError: + pass + + return '.'.join(names) def is_stub(self): if not self._name.is_context_name: diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index bf2aa848..f986bb1b 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -201,7 +201,7 @@ class Context(HelperContextMixin, BaseContext): return NO_CONTEXTS def get_qualified_names(self): - # Returns Optional[List[str]] + # Returns Optional[Tuple[str, ...]] return None def is_stub(self): diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index e28fd994..04d00d94 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -300,7 +300,7 @@ class DirectObjectAccess(object): name = try_to_get_name(type(self._obj)) if name is None: return () - return name.split('.') + return tuple(name.split('.')) def dir(self): return list(map(force_unicode, dir(self._obj))) diff --git a/jedi/evaluate/context/function.py b/jedi/evaluate/context/function.py index c9dfdeba..b5bdd99c 100644 --- a/jedi/evaluate/context/function.py +++ b/jedi/evaluate/context/function.py @@ -45,9 +45,9 @@ class FunctionAndClassBase(TreeContext): if n is None: # This means that the parent class lives within a function. return None - return n + [self.py__name__()] + return n + (self.py__name__(),) elif self.parent_context.is_module(): - return [self.py__name__()] + return (self.py__name__(),) else: return None @@ -164,7 +164,7 @@ class MethodContext(FunctionContext): names = self.class_context.get_qualified_names() if names is None: return None - return names + [self.py__name__()] + return names + (self.py__name__(),) class FunctionExecutionContext(TreeContext): diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index e05f2c24..c17375fd 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -73,6 +73,9 @@ class AbstractInstanceContext(Context): def is_instance(self): return True + def get_qualified_names(self): + return self.class_context.get_qualified_names() + def get_annotated_class_object(self): return self.class_context # This is the default. diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index 884e8585..3ecf4fec 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -164,7 +164,7 @@ class ModuleMixin(SubModuleDictMixin): it's reachable and not `None`. With this information we can add qualified names on top for all context children. """ - return [] + return () class ModuleContext(ModuleMixin, TreeContext): @@ -182,7 +182,7 @@ class ModuleContext(ModuleMixin, TreeContext): self._path = None else: self._path = file_io.path - self.string_names = string_names + self.string_names = string_names # Optional[Tuple[str, ...]] self.code_lines = code_lines self.is_package = is_package diff --git a/jedi/evaluate/names.py b/jedi/evaluate/names.py index 64289371..8df3b183 100644 --- a/jedi/evaluate/names.py +++ b/jedi/evaluate/names.py @@ -28,7 +28,7 @@ class AbstractNameDefinition(object): return {self} @abstractmethod - def get_qualified_names(self): + def get_qualified_names(self, include_module_names=False): raise NotImplementedError def get_root_context(self): @@ -53,11 +53,21 @@ class AbstractTreeName(AbstractNameDefinition): self.parent_context = parent_context self.tree_name = tree_name - def get_qualified_names(self): + def get_qualified_names(self, include_module_names=False): + import_node = search_ancestor(self.tree_name, 'import_name', 'import_from') + if import_node is not None: + return tuple(n.value for n in import_node.get_path_for_name(self.tree_name)) + parent_names = self.parent_context.get_qualified_names() if parent_names is None: return None - return parent_names + [self.tree_name.value] + parent_names += (self.tree_name.value,) + if include_module_names: + module_names = self.get_root_context().string_names + if module_names is None: + return None + return module_names + parent_names + return parent_names def goto(self): return self.parent_context.evaluator.goto(self.parent_context, self.tree_name) @@ -79,8 +89,15 @@ class ContextNameMixin(object): def infer(self): return ContextSet([self._context]) - def get_qualified_names(self): - return self._context.get_qualified_names() + def get_qualified_names(self, include_module_names=False): + qualified_names = self._context.get_qualified_names() + if qualified_names is None or not include_module_names: + return qualified_names + + module_names = self.get_root_context().string_names + if module_names is None: + return None + return module_names + qualified_names def get_root_context(self): if self.parent_context is None: # A module @@ -185,8 +202,16 @@ class ImportName(AbstractNameDefinition): self._from_module_context = parent_context self.string_name = string_name - def get_qualified_names(self): - return [] + def get_qualified_names(self, include_module_names=False): + if include_module_names: + if self._level: + assert self._level == 1, "Everything else is not supported for now" + module_names = self._from_module_context.string_names + if module_names is None: + return module_names + return module_names + (self.string_name,) + return self.string_name + return () @property def parent_context(self): diff --git a/test/test_api/test_full_name.py b/test/test_api/test_full_name.py index 9956e6be..829c1247 100644 --- a/test/test_api/test_full_name.py +++ b/test/test_api/test_full_name.py @@ -39,7 +39,7 @@ class MixinTestFullName(object): self.check('import os; os.path.join', 'os.path.join') def test_builtin(self): - self.check('TypeError', 'TypeError') + self.check('TypeError', 'builtins.TypeError') class TestFullNameWithGotoDefinitions(MixinTestFullName, TestCase):