diff --git a/README.rst b/README.rst index 12015d13..08dbd5f5 100644 --- a/README.rst +++ b/README.rst @@ -122,7 +122,7 @@ Tips on how to use Jedi efficiently can be found `here API --- -You can find the documentation for the `API here `_. +You can find the documentation for the `API here `_. Autocompletion / Goto / Pydoc diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 8e100a39..9602e04a 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -14,7 +14,7 @@ from jedi.evaluate import imports from jedi.evaluate import compiled from jedi.evaluate.imports import ImportName from jedi.evaluate.context import instance -from jedi.evaluate.context import ClassContext, FunctionContext, FunctionExecutionContext +from jedi.evaluate.context import ClassContext, FunctionExecutionContext from jedi.api.keywords import KeywordName @@ -353,10 +353,7 @@ class BaseDefinition(object): return None if isinstance(context, FunctionExecutionContext): - # TODO the function context should be a part of the function - # execution context. - context = FunctionContext( - self._evaluator, context.parent_context, context.tree_node) + context = context.function_context return Definition(self._evaluator, context.name) def __repr__(self): diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 20e0b81c..c8c4a840 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -237,7 +237,7 @@ class Evaluator(object): if type_ == 'classdef': return [ClassContext(self, context, name.parent)] elif type_ == 'funcdef': - return [FunctionContext(self, context, name.parent)] + return [FunctionContext.from_context(context, name.parent)] if type_ == 'expr_stmt': is_simple_name = name.parent.type not in ('power', 'trailer') @@ -355,16 +355,15 @@ class Evaluator(object): parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef) if is_funcdef: + func = FunctionContext.from_context( + parent_context, + scope_node + ) if isinstance(parent_context, AnonymousInstance): func = BoundMethod( - self, parent_context, parent_context.class_context, - parent_context.parent_context, scope_node - ) - else: - func = FunctionContext( - self, - parent_context, - scope_node + instance=parent_context, + klass=parent_context.class_context, + function=func ) if is_nested and not node_is_object: return func.get_function_execution() diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index 19df52ea..51b98aa1 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -168,9 +168,10 @@ def iterate_contexts(contexts, contextualized_node=None, is_async=False): class TreeContext(Context): - def __init__(self, evaluator, parent_context=None): + def __init__(self, evaluator, parent_context, tree_node): super(TreeContext, self).__init__(evaluator, parent_context) self.predefined_names = {} + self.tree_node = tree_node def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.tree_node) diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index 0ef7e44d..610cec92 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -470,7 +470,7 @@ else: class _SPECIAL_OBJECTS(object): FUNCTION_CLASS = types.FunctionType - METHOD_CLASS = type(DirectObjectAccess.py__bool__) + BOUND_METHOD_CLASS = type(DirectObjectAccess(None, None).py__bool__) MODULE_CLASS = types.ModuleType GENERATOR_OBJECT = _a_generator(1.0) BUILTINS = builtins diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 3950aff8..ea59341e 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -55,7 +55,7 @@ class CompiledObject(Context): return FunctionContext( self.evaluator, parent_context=self.parent_context, - funcdef=self.tree_node + tree_node=self.tree_node ).py__call__(params) if self.access_handle.is_class(): from jedi.evaluate.context import CompiledInstance diff --git a/jedi/evaluate/context/function.py b/jedi/evaluate/context/function.py index 2bb3a9b8..992cecdf 100644 --- a/jedi/evaluate/context/function.py +++ b/jedi/evaluate/context/function.py @@ -38,17 +38,9 @@ class LambdaName(AbstractNameDefinition): return ContextSet(self._lambda_context) -class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)): - """ - Needed because of decorators. Decorators are evaluated here. - """ +class AbstractFunction(TreeContext): api_type = u'function' - def __init__(self, evaluator, parent_context, funcdef): - """ This should not be called directly """ - super(FunctionContext, self).__init__(evaluator, parent_context) - self.tree_node = funcdef - def get_filters(self, search_global, until_position=None, origin_scope=None): if search_global: yield ParserTreeFilter( @@ -62,6 +54,24 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)): for filter in scope.get_filters(search_global=False, origin_scope=origin_scope): yield filter + def get_param_names(self): + function_execution = self.get_function_execution() + return [ParamName(function_execution, param.name) + for param in self.tree_node.get_params()] + + @property + def name(self): + if self.tree_node.type == 'lambdef': + return LambdaName(self) + return ContextName(self, self.tree_node.name) + + def get_function_execution(self, arguments=None): + raise NotImplementedError + + def py__call__(self, arguments): + function_execution = self.get_function_execution(arguments) + return self.infer_function_execution(function_execution) + def infer_function_execution(self, function_execution): """ Created to be used by inheritance. @@ -84,35 +94,28 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)): else: return function_execution.get_return_values() + +class FunctionContext(use_metaclass(CachedMetaClass, AbstractFunction)): + """ + Needed because of decorators. Decorators are evaluated here. + """ + @classmethod + def from_context(cls, context, tree_node): + from jedi.evaluate.context import AbstractInstanceContext + + while context.is_class() or isinstance(context, AbstractInstanceContext): + context = context.parent_context + + return cls(context.evaluator, parent_context=context, tree_node=tree_node) + def get_function_execution(self, arguments=None): if arguments is None: arguments = AnonymousArguments() return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments) - def py__call__(self, arguments): - function_execution = self.get_function_execution(arguments) - return self.infer_function_execution(function_execution) - def py__class__(self): - # This differentiation is only necessary for Python2. Python3 does not - # use a different method class. - if isinstance(parser_utils.get_parent_scope(self.tree_node), tree.Class): - name = u'METHOD_CLASS' - else: - name = u'FUNCTION_CLASS' - return compiled.get_special_object(self.evaluator, name) - - @property - def name(self): - if self.tree_node.type == 'lambdef': - return LambdaName(self) - return ContextName(self, self.tree_node.name) - - def get_param_names(self): - function_execution = self.get_function_execution() - return [ParamName(function_execution, param.name) - for param in self.tree_node.get_params()] + return compiled.get_special_object(self.evaluator, u'FUNCTION_CLASS') class FunctionExecutionContext(TreeContext): @@ -127,9 +130,12 @@ class FunctionExecutionContext(TreeContext): function_execution_filter = FunctionExecutionFilter def __init__(self, evaluator, parent_context, function_context, var_args): - super(FunctionExecutionContext, self).__init__(evaluator, parent_context) + super(FunctionExecutionContext, self).__init__( + evaluator, + parent_context, + function_context.tree_node, + ) self.function_context = function_context - self.tree_node = function_context.tree_node self.var_args = var_args @evaluator_method_cache(default=NO_CONTEXTS) diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index f6fc344a..21e19e11 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -9,7 +9,8 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.arguments import AbstractArguments, AnonymousArguments from jedi.cache import memoize_method -from jedi.evaluate.context.function import FunctionExecutionContext, FunctionContext +from jedi.evaluate.context.function import FunctionExecutionContext, \ + FunctionContext, AbstractFunction from jedi.evaluate.context.klass import ClassContext, apply_py__get__ from jedi.evaluate.context import iterable from jedi.parser_utils import get_parent_scope @@ -158,10 +159,7 @@ class AbstractInstanceContext(Context): def name(self): pass - def _create_init_execution(self, class_context, func_node): - bound_method = BoundMethod( - self.evaluator, self, class_context, self.parent_context, func_node - ) + def _create_init_execution(self, class_context, bound_method): return self.function_execution_cls( self, class_context.parent_context, @@ -172,7 +170,12 @@ class AbstractInstanceContext(Context): def create_init_executions(self): for name in self.get_function_slot_names(u'__init__'): if isinstance(name, SelfName): - yield self._create_init_execution(name.class_context, name.tree_name.parent) + function = FunctionContext.from_context( + self.parent_context, + name.tree_name.parent + ) + bound_method = BoundMethod(self, name.class_context, function) + yield self._create_init_execution(name.class_context, bound_method) @evaluator_method_cache() def create_instance_context(self, class_context, node): @@ -184,13 +187,14 @@ class AbstractInstanceContext(Context): else: parent_context = self.create_instance_context(class_context, scope) if scope.type == 'funcdef': + func = FunctionContext.from_context( + parent_context, + scope, + ) + bound_method = BoundMethod(self, class_context, func) if scope.name.value == '__init__' and parent_context == class_context: - return self._create_init_execution(class_context, scope) + return self._create_init_execution(class_context, bound_method) else: - bound_method = BoundMethod( - self.evaluator, self, class_context, - parent_context, scope - ) return bound_method.get_function_execution() elif scope.type == 'classdef': class_context = ClassContext(self.evaluator, parent_context, scope) @@ -270,14 +274,8 @@ class CompiledInstanceName(compiled.CompiledName): for result_context in super(CompiledInstanceName, self).infer(): is_function = result_context.api_type == 'function' if result_context.tree_node is not None and is_function: - parent_context = result_context.parent_context - while parent_context.is_class(): - parent_context = parent_context.parent_context - yield BoundMethod( - result_context.evaluator, self._instance, self.parent_context, - parent_context, result_context.tree_node - ) + yield BoundMethod(self._instance, self.parent_context, result_context) else: if is_function: yield CompiledBoundMethod(result_context) @@ -301,11 +299,19 @@ class CompiledInstanceClassFilter(compiled.CompiledObjectFilter): self._evaluator, self._instance, self._compiled_object, name) -class BoundMethod(FunctionContext): - def __init__(self, evaluator, instance, class_context, *args, **kwargs): - super(BoundMethod, self).__init__(evaluator, *args, **kwargs) +class BoundMethod(AbstractFunction): + def __init__(self, instance, klass, function): + super(BoundMethod, self).__init__( + function.evaluator, + function.parent_context, + function.tree_node, + ) self._instance = instance - self._class_context = class_context + self._class = klass + self._function = function + + def py__class__(self): + return compiled.get_special_object(self.evaluator, u'BOUND_METHOD_CLASS') def get_function_execution(self, arguments=None): if arguments is None: @@ -353,14 +359,7 @@ class LazyInstanceClassName(SelfName): # Classes are never used to resolve anything within the # functions. Only other functions and modules will resolve # those things. - parent_context = result_context.parent_context - while parent_context.is_class(): - parent_context = parent_context.parent_context - - yield BoundMethod( - result_context.evaluator, self._instance, self.class_context, - parent_context, result_context.tree_node - ) + yield BoundMethod(self._instance, self.class_context, result_context) else: for c in apply_py__get__(result_context, self._instance): yield c diff --git a/jedi/evaluate/context/iterable.py b/jedi/evaluate/context/iterable.py index 06a769b8..104d158c 100644 --- a/jedi/evaluate/context/iterable.py +++ b/jedi/evaluate/context/iterable.py @@ -83,10 +83,6 @@ class CompForContext(TreeContext): def from_comp_for(cls, parent_context, comp_for): return cls(parent_context.evaluator, parent_context, comp_for) - def __init__(self, evaluator, parent_context, comp_for): - super(CompForContext, self).__init__(evaluator, parent_context) - self.tree_node = comp_for - def get_node(self): return self.tree_node diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index dcb620e0..93ccf99d 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -89,10 +89,6 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)): """ api_type = u'class' - def __init__(self, evaluator, parent_context, classdef): - super(ClassContext, self).__init__(evaluator, parent_context=parent_context) - self.tree_node = classdef - @evaluator_method_cache(default=()) def py__mro__(self): def add(cls): diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index bb1c8296..42da17f9 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -43,8 +43,11 @@ class ModuleContext(TreeContext): parent_context = None def __init__(self, evaluator, module_node, path, code_lines): - super(ModuleContext, self).__init__(evaluator, parent_context=None) - self.tree_node = module_node + super(ModuleContext, self).__init__( + evaluator, + parent_context=None, + tree_node=module_node + ) self._path = path self.code_lines = code_lines diff --git a/jedi/evaluate/context/namespace.py b/jedi/evaluate/context/namespace.py index c6ff1e63..f41898c7 100644 --- a/jedi/evaluate/context/namespace.py +++ b/jedi/evaluate/context/namespace.py @@ -4,7 +4,7 @@ from itertools import chain from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate import imports from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, ContextNameMixin -from jedi.evaluate.base_context import TreeContext, ContextSet +from jedi.evaluate.base_context import Context class ImplicitNSName(ContextNameMixin, AbstractNameDefinition): @@ -17,7 +17,7 @@ class ImplicitNSName(ContextNameMixin, AbstractNameDefinition): self.string_name = string_name -class ImplicitNamespaceContext(TreeContext): +class ImplicitNamespaceContext(Context): """ Provides support for implicit namespace packages """ diff --git a/jedi/evaluate/lazy_context.py b/jedi/evaluate/lazy_context.py index 97e4b068..84b375c1 100644 --- a/jedi/evaluate/lazy_context.py +++ b/jedi/evaluate/lazy_context.py @@ -1,6 +1,7 @@ from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS from jedi.common.utils import monkeypatch + class AbstractLazyContext(object): def __init__(self, data): self.data = data diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 307ab988..2ce0b19d 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -71,7 +71,7 @@ def eval_node(context, element): if typ in ('name', 'number', 'string', 'atom', 'strings', 'keyword'): return eval_atom(context, element) elif typ == 'lambdef': - return ContextSet(FunctionContext(evaluator, context, element)) + return ContextSet(FunctionContext.from_context(context, element)) elif typ == 'expr_stmt': return eval_expr_stmt(context, element) elif typ in ('power', 'atom_expr'): @@ -603,14 +603,10 @@ def _apply_decorators(context, node): decoratee_context = ClassContext( context.evaluator, parent_context=context, - classdef=node + tree_node=node ) else: - decoratee_context = FunctionContext( - context.evaluator, - parent_context=context, - funcdef=node - ) + decoratee_context = FunctionContext.from_context(context, node) initial = values = ContextSet(decoratee_context) for dec in reversed(node.get_decorators()): debug.dbg('decorator: %s %s', dec, values) diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index a40095b9..146a4978 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -132,7 +132,6 @@ def test_async(Script, environment): hey = 3 ho''' ) - print(code) comps = Script(code, column=4).completions() names = [c.name for c in comps] assert 'foo' in names diff --git a/test/test_evaluate/test_compiled.py b/test/test_evaluate/test_compiled.py index a0b3ca81..86375ee9 100644 --- a/test/test_evaluate/test_compiled.py +++ b/test/test_evaluate/test_compiled.py @@ -70,11 +70,7 @@ def test_method_completion(Script, environment): foo = Foo() foo.bar.__func__''') - if environment.version_info.major > 2: - result = [] - else: - result = ['__func__'] - assert [c.name for c in Script(code).completions()] == result + assert [c.name for c in Script(code).completions()] == ['__func__'] def test_time_docstring(Script): diff --git a/test/test_evaluate/test_namespace_package.py b/test/test_evaluate/test_namespace_package.py index 30f3b924..1b156583 100644 --- a/test/test_evaluate/test_namespace_package.py +++ b/test/test_evaluate/test_namespace_package.py @@ -77,10 +77,13 @@ def test_nested_namespace_package(Script): assert len(result) == 1 -def test_relative_import(Script, tmpdir): +def test_relative_import(Script, environment, tmpdir): """ Attempt a relative import in a very simple namespace package. """ + if environment.version_info < (3, 4): + pytest.skip() + directory = get_example_dir('namespace_package_relative_import') # Need to copy the content in a directory where there's no __init__.py. py.path.local(directory).copy(tmpdir) diff --git a/tox.ini b/tox.ini index 9ecda0fd..9d247acd 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,8 @@ deps = # coloroma for colored debug output colorama # Overwrite the parso version (only used sometimes). - git+https://github.com/davidhalter/parso.git -# -rrequirements.txt +# git+https://github.com/davidhalter/parso.git + -rrequirements.txt passenv = JEDI_TEST_ENVIRONMENT setenv = # https://github.com/tomchristie/django-rest-framework/issues/1957