From 6940900c58cbe62b74b467bd48536304cefd1ebe Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 3 Dec 2016 22:17:38 +0100 Subject: [PATCH] A lot more fixes - fix all evaluate integration tests. --- jedi/api/__init__.py | 22 +++++--- jedi/api/classes.py | 57 +++++++++++--------- jedi/evaluate/compiled/__init__.py | 22 ++++++++ jedi/evaluate/imports.py | 2 +- jedi/evaluate/representation.py | 8 ++- test/test_api/test_api.py | 4 +- test/test_evaluate/test_compiled.py | 19 ++++--- test/test_evaluate/test_helpers.py | 2 +- test/test_evaluate/test_imports.py | 2 +- test/test_evaluate/test_namespace_package.py | 6 +-- 10 files changed, 91 insertions(+), 53 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 257f8c8d..9b1f1c1f 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -216,11 +216,9 @@ class Script(object): """ def filter_follow_imports(names): for name in names: - definition = name.get_definition() - if definition.type in ('import_name', 'import_from'): - imp = imports.ImportWrapper(context, name) - for name in filter_follow_imports(imp.follow(is_goto=True)): - yield name + if isinstance(name, (imports.ImportName, TreeNameDefinition)): + for context in name.infer(): + yield context.name else: yield name @@ -436,13 +434,21 @@ def names(source=None, path=None, encoding='utf-8', all_scopes=False, ``definitions=True``. E.g. ``a = b`` returns ``b``. """ def def_ref_filter(_def): - is_def = _def.is_definition() + is_def = _def._name.tree_name.is_definition() return definitions and is_def or references and not is_def # Set line/column to a random position, because they don't matter. script = Script(source, line=1, column=0, path=path, encoding=encoding) - defs = [classes.Definition(script._evaluator, name_part) - for name_part in get_module_names(script._get_module().module_node, all_scopes)] + module_context = script._get_module() + defs = [ + classes.Definition( + script._evaluator, + TreeNameDefinition( + module_context.create_context(name), + name + ) + ) for name in get_module_names(script._get_module_node(), all_scopes) + ] return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column)) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 142dc537..61aabd9c 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -18,7 +18,9 @@ from jedi.evaluate import instance from jedi.evaluate import iterable from jedi.evaluate import imports from jedi.evaluate import compiled +from jedi.evaluate.filters import ParamName from jedi.evaluate.finder import filter_definition_names +from jedi.api.keywords import KeywordName def defined_names(evaluator, scope): @@ -63,7 +65,7 @@ class BaseDefinition(object): An instance of :class:`jedi.parser.reprsentation.Name` subclass. """ #self._definition = list(self._name.infer())[0] - #self.is_keyword = isinstance(self._definition, keywords.Keyword) + self.is_keyword = isinstance(self._name, KeywordName) self._definition = None # generate a path to the definition @@ -232,9 +234,9 @@ class BaseDefinition(object): """ if raw: - return _Help(self._definition).raw() + return _Help(self._name.parent_context).raw() else: - return _Help(self._definition).full() + return _Help(self._name.parent_context).full() @property def doc(self): @@ -259,7 +261,7 @@ class BaseDefinition(object): @property def description(self): """A textual description of the object.""" - return unicode(self._name) + return unicode(self._name.string_name) @property def full_name(self): @@ -298,7 +300,12 @@ class BaseDefinition(object): return '.'.join(path if path[0] else path[1:]) def goto_assignments(self): - defs = self._evaluator.goto(self._name) + try: + tree_name = self._name.tree_name + except AttributeError: + return self + + defs = self._evaluator.goto(self._name.parent_context, tree_name) return [Definition(self._evaluator, d) for d in defs] @memoize_method @@ -306,6 +313,7 @@ class BaseDefinition(object): """ Follow both statements and imports, as far as possible. """ + return self._name.infer() if self._name.api_type == 'expr_stmt': return self._evaluator.eval_statement(self._definition) elif self._name.api_type == 'import': @@ -351,9 +359,7 @@ class BaseDefinition(object): return [_Param(self._evaluator, n) for n in get_param_names(context)] def parent(self): - scope = self._definition.get_parent_scope() - scope = self._evaluator.wrap(scope) - return Definition(self._evaluator, scope.name) + return Definition(self._evaluator, self._name.parent_context.name) def __repr__(self): return "<%s %s>" % (type(self).__name__, self.description) @@ -371,7 +377,7 @@ class BaseDefinition(object): if self.in_builtin_module(): return '' - path = self._definition.get_parent_until().path + path = self._name.get_root_context().py__file__() parser = load_parser(path) lines = common.splitlines(parser.source) @@ -401,7 +407,7 @@ class Completion(BaseDefinition): and self.type == 'Function': append = '(' - if isinstance(self._definition, tree.Param) and self._stack is not None: + if isinstance(self._name, ParamName) and self._stack is not None: node_names = list(self._stack.get_node_names(self._evaluator.grammar)) if 'trailer' in node_names and 'argument' not in node_names: append += '=' @@ -438,9 +444,10 @@ class Completion(BaseDefinition): @property def description(self): """Provide a description of the completion object.""" - if self._definition is None: - return self._name.string_name + # TODO improve the class structure. + return Definition.description.__get__(self) + # TODO remove t = self.type if t == 'statement' or t == 'import': desc = self._definition.get_code() @@ -461,20 +468,20 @@ class Completion(BaseDefinition): the ``foo.docstring(fast=False)`` on every object, because it parses all libraries starting with ``a``. """ - definition = self._definition - if isinstance(definition, tree.Import): - raise DeprecationWarning - i = imports.ImportWrapper(self._evaluator, self._name) - if len(i.import_path) > 1 or not fast: - followed = self._follow_statements_imports() + context = self._name.parent_context + if isinstance(self._name, imports.ImportName): + if fast: + return '' + else: + followed = self._name.infer() if followed: # TODO: Use all of the followed objects as input to Documentation. - definition = list(followed)[0] + context = next(iter(followed)) if raw: - return _Help(definition).raw() + return _Help(context).raw() else: - return _Help(definition).full() + return _Help(context).full() @property def type(self): @@ -772,7 +779,7 @@ class _Help(object): def full(self): try: - return self._name.doc + return self._name.get_node().doc except AttributeError: return self.raw() @@ -782,7 +789,7 @@ class _Help(object): See :attr:`doc` for example. """ - try: - return self._name.raw_doc - except AttributeError: + node = self._name.get_node() + if node is None: return '' + return node.raw_doc diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 1f6b14e9..04573049 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -112,6 +112,17 @@ class CompiledObject(Context): params.append(Param(parts, self)) return params + def get_param_names(self): + params_str, ret = self._parse_function_doc() + tokens = params_str.split(',') + if inspect.ismethoddescriptor(self.obj): + tokens.insert(0, 'self') + for p in tokens: + parts = p.strip().split('=') + if len(parts) > 1: + parts.insert(1, Operator('=', (0, 0))) + yield UnresolvableParamName(self, p[0]) + def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, repr(self.obj)) @@ -278,6 +289,17 @@ class CompiledName(AbstractNameDefinition): return [_create_from_name(self._evaluator, module, self.parent_context, self.string_name)] +class UnresolvableParamName(AbstractNameDefinition): + api_type = 'param' + + def __init__(self, compiled_obj, name): + self.parent_context = compiled_obj.parent_context + self.string_name = name + + def infer(self): + return set() + + class CompiledContextName(AbstractNameDefinition): def __init__(self, parent_context, name): self.string_name = name diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index dcd5b33a..be035066 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -431,7 +431,7 @@ class Importer(object): if self.str_import_path == ('flask', 'ext'): # List Flask extensions like ``flask_foo`` for mod in self._get_module_names(): - modname = str(mod) + modname = mod.string_name if modname.startswith('flask_'): extname = modname[len('flask_'):] names.append(self._generate_name(extname)) diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index c07a29ed..2578f37e 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -564,10 +564,14 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)): # modules on sys_path or whatever the search_path is. paths = set() for s in search_path: - other = os.path.join(s, unicode(self.name)) + other = os.path.join(s, self.name.string_name) if os.path.isdir(other): paths.add(other) - return list(paths) + if paths: + return list(paths) + # TODO I'm not sure if this is how nested namespace + # packages work. The tests are not really good enough to + # show that. # Default to this. return [self._get_init_directory()] diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index d388c517..211ec8cd 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -22,8 +22,8 @@ def test_preload_modules(): api.preload_module('sys') check_loaded() # compiled (c_builtin) modules shouldn't be in the cache. - api.preload_module('json', 'token') - check_loaded('json', 'token') + api.preload_module('types', 'token') + check_loaded('types', 'token') utils.parser_cache = temp_cache diff --git a/test/test_evaluate/test_compiled.py b/test/test_evaluate/test_compiled.py index 762260e4..4d5070e5 100644 --- a/test/test_evaluate/test_compiled.py +++ b/test/test_evaluate/test_compiled.py @@ -2,8 +2,8 @@ from textwrap import dedent from jedi._compatibility import builtins, is_py3 from jedi.parser import load_grammar -from jedi.parser.tree import Function -from jedi.evaluate import compiled, representation +from jedi.evaluate import compiled, instance +from jedi.evaluate.representation import FunctionContext from jedi.evaluate import Evaluator from jedi import Script @@ -16,25 +16,24 @@ def test_simple(): e = _evaluator() bltn = compiled.CompiledObject(e, builtins) obj = compiled.CompiledObject(e, '_str_', bltn) - upper = e.find_types(obj, 'upper') - assert len(upper) == 1 - objs = list(e.execute(list(upper)[0])) + upper, = obj.py__getattribute__('upper') + objs = list(upper.execute_evaluated()) assert len(objs) == 1 - assert isinstance(objs[0], representation.Instance) + assert isinstance(objs[0], instance.CompiledInstance) def test_fake_loading(): e = _evaluator() - assert isinstance(compiled.create(e, next), Function) + assert isinstance(compiled.create(e, next), FunctionContext) builtin = compiled.get_special_object(e, 'BUILTINS') - string = builtin.get_subscope_by_name('str') + string, = builtin.py__getattribute__('str') from_name = compiled._create_from_name(e, builtin, string, '__init__') - assert isinstance(from_name, Function) + assert isinstance(from_name, FunctionContext) def test_fake_docstr(): - assert compiled.create(_evaluator(), next).raw_doc == next.__doc__ + assert compiled.create(_evaluator(), next).get_node().raw_doc == next.__doc__ def test_parse_function_doc_illegal_docstr(): diff --git a/test/test_evaluate/test_helpers.py b/test/test_evaluate/test_helpers.py index 02e6d36b..4484c2ba 100644 --- a/test/test_evaluate/test_helpers.py +++ b/test/test_evaluate/test_helpers.py @@ -10,7 +10,7 @@ def test_call_of_leaf_in_brackets(): type(x) """) last_x = names(s, references=True, definitions=False)[-1] - name = last_x._name + name = last_x._name.tree_name call = helpers.call_of_leaf(name) assert call == name diff --git a/test/test_evaluate/test_imports.py b/test/test_evaluate/test_imports.py index 8eb23874..bbf5085f 100644 --- a/test/test_evaluate/test_imports.py +++ b/test/test_evaluate/test_imports.py @@ -97,7 +97,7 @@ def test_not_importable_file(): def test_import_unique(): src = "import os; os.path" defs = jedi.Script(src, path='example.py').goto_definitions() - defs = [d._definition for d in defs] + defs = [d._name.parent_context for d in defs] assert len(defs) == len(set(defs)) diff --git a/test/test_evaluate/test_namespace_package.py b/test/test_evaluate/test_namespace_package.py index 3beed65a..9d3aeb75 100644 --- a/test/test_evaluate/test_namespace_package.py +++ b/test/test_evaluate/test_namespace_package.py @@ -49,16 +49,16 @@ def test_namespace_package(): for c in script_with_path(source + '; x.').completions(): if c.name == 'foo': completion = c - solution = "statement: foo = '%s'" % solution + solution = "foo = '%s'" % solution assert completion.description == solution def test_nested_namespace_package(): - CODE = 'from nested_namespaces.namespace.pkg import CONST' + code = 'from nested_namespaces.namespace.pkg import CONST' sys_path = [dirname(__file__)] - script = jedi.Script(sys_path=sys_path, source=CODE, line=1, column=45) + script = jedi.Script(sys_path=sys_path, source=code, line=1, column=45) result = script.goto_definitions()