diff --git a/jedi/api/completion.py b/jedi/api/completion.py index e05e59df..108f6283 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -68,13 +68,14 @@ def get_user_scope(module, position): class Completion: - def __init__(self, evaluator, module, code_lines, position, call_signatures_method): + def __init__(self, evaluator, module_node, code_lines, position, call_signatures_method): self._evaluator = evaluator - self._module = evaluator.wrap(module, parent_context=None) + self._module_node = module_node + self._module = evaluator.wrap(module_node, parent_context=None) self._code_lines = code_lines # The first step of completions is to get the name - self._like_name = helpers.get_on_completion_name(module, code_lines, position) + self._like_name = helpers.get_on_completion_name(module_node, code_lines, position) # The actual cursor position is not what we need to calculate # everything. We want the start of the name we're on. self._position = position[0], position[1] - len(self._like_name) @@ -109,7 +110,7 @@ class Completion: try: self.stack = helpers.get_stack_at_position( - grammar, self._code_lines, self._module, self._position + grammar, self._code_lines, self._module_node, self._position ) except helpers.OnErrorLeaf as e: self.stack = None @@ -153,7 +154,7 @@ class Completion: # Also true for defining names as a class or function. return list(self._get_class_context_completions(is_function=True)) elif symbol_names[-1] == 'trailer' and nodes[-1] == '.': - dot = self._module.get_leaf_for_position(self._position) + dot = self._module_node.get_leaf_for_position(self._position) atom_expr = call_of_leaf(dot.get_previous_leaf()) completion_names += self._trailer_completions(atom_expr) else: @@ -171,7 +172,7 @@ class Completion: yield keywords.keyword(self._evaluator, k).name def _global_completions(self): - scope = get_user_scope(self._module, self._position) + scope = get_user_scope(self._module_node, self._position) if not scope.is_scope(): # Might be a flow (if/while/etc). scope = scope.get_parent_scope() scope = self._evaluator.create_context(scope) @@ -188,7 +189,7 @@ class Completion: return completion_names def _trailer_completions(self, atom_expr): - user_scope = get_user_scope(self._module, self._position) + user_scope = get_user_scope(self._module_node, self._position) contexts = self._evaluator.eval_element(self._evaluator.create_context(atom_expr), atom_expr) completion_names = [] debug.dbg('trailer completion contexts: %s', contexts) @@ -222,7 +223,7 @@ class Completion: Autocomplete inherited methods when overriding in child class. """ return - leaf = self._module.get_leaf_for_position(self._position, include_prefixes=True) + leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) cls = leaf.get_parent_until(tree.Class) if isinstance(cls, (tree.Class, tree.Function)): # Complete the methods that are defined in the super classes. diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index b30208b3..7d8702b8 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -64,8 +64,8 @@ def _is_on_comment(leaf, position): return '#' in line -def _get_code_for_stack(code_lines, module, position): - leaf = module.get_leaf_for_position(position, include_prefixes=True) +def _get_code_for_stack(code_lines, module_node, position): + leaf = module_node.get_leaf_for_position(position, include_prefixes=True) # It might happen that we're on whitespace or on a comment. This means # that we would not get the right leaf. if leaf.start_pos >= position: @@ -111,7 +111,7 @@ def _get_code_for_stack(code_lines, module, position): return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position) -def get_stack_at_position(grammar, code_lines, module, pos): +def get_stack_at_position(grammar, code_lines, module_node, pos): """ Returns the possible node names (e.g. import_from, xor_test or yield_stmt). """ @@ -126,7 +126,7 @@ def get_stack_at_position(grammar, code_lines, module, pos): else: yield token_ - code = _get_code_for_stack(code_lines, module, pos) + code = _get_code_for_stack(code_lines, module_node, pos) # We use a word to tell Jedi when we have reached the start of the # completion. # Use Z as a prefix because it's not part of a number suffix. @@ -196,7 +196,7 @@ def evaluate_goto_definition(evaluator, context, leaf): if leaf.type == 'name': # In case of a name we can just use goto_definition which does all the # magic itself. - return evaluator.goto_definitions(leaf) + return evaluator.goto_definitions(context, leaf) node = None parent = leaf.parent diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 85db5e6e..82a16cdf 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -71,6 +71,9 @@ class KeywordName(AbstractNameDefinition): self.string_name = name self.parent_context = evaluator.BUILTINS + def eval(self): + return set() + class Keyword(object): type = 'completion_keyword' diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 25ead36e..6ee6a410 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -416,6 +416,7 @@ class Evaluator(object): @debug.increase_indent def execute(self, obj, arguments=None): if not isinstance(arguments, param.Arguments): + raise NotImplementedError arguments = param.Arguments(self, arguments) if self.is_analysis: diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 4c0f2c0c..b6be0460 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -55,9 +55,8 @@ class CompiledObject(Context): @CheckAttribute def py__call__(self, params): if inspect.isclass(self.obj): - from jedi.evaluate.representation import Instance - return set([self]) - return set([Instance(self._evaluator, self.parent_context, self, params)]) + from jedi.evaluate.instance import CompiledInstance + return set([CompiledInstance(self._evaluator, self.parent_context, self, params)]) else: return set(self._execute_function(params)) @@ -261,22 +260,22 @@ class CompiledObject(Context): class CompiledName(AbstractNameDefinition): - def __init__(self, evaluator, compiled_obj, name): + def __init__(self, evaluator, parent_context, name): self._evaluator = evaluator - self._compiled_obj = compiled_obj + self.parent_context = parent_context self.string_name = name def __repr__(self): try: - name = self._compiled_obj.name # __name__ is not defined all the time + name = self.parent_context.name # __name__ is not defined all the time except AttributeError: name = None return '<%s: (%s).%s>' % (type(self).__name__, name, self.string_name) @underscore_memoization def infer(self): - module = self._compiled_obj.get_root_context() - return [_create_from_name(self._evaluator, module, self._compiled_obj, self.string_name)] + module = self.parent_context.get_root_context() + return [_create_from_name(self._evaluator, module, self.parent_context, self.string_name)] class CompiledContextName(AbstractNameDefinition): diff --git a/jedi/evaluate/context.py b/jedi/evaluate/context.py new file mode 100644 index 00000000..25298632 --- /dev/null +++ b/jedi/evaluate/context.py @@ -0,0 +1,30 @@ +class Context(object): + type = None # TODO remove + + def __init__(self, evaluator, parent_context=None): + self._evaluator = evaluator + self.parent_context = parent_context + + def get_parent_flow_context(self): + return self.parent_context + + def get_root_context(self): + context = self + while True: + if context.parent_context is None: + return context + context = context.parent_context + + def execute(self, arguments=None): + return self._evaluator.execute(self, arguments) + + +class TreeContext(Context): + def eval_node(self, node): + return self._evaluator.eval_element(self, node) + + +class FlowContext(TreeContext): + def get_parent_flow_context(self): + if 1: + return self.parent_context diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index 156e8d7c..0d65ca11 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -9,7 +9,7 @@ from jedi.evaluate import flow_analysis from jedi.common import to_list -class AbstractNameDefinition(object): +class AbstractNameDefinition(): start_pos = None string_name = None parent_context = None @@ -108,8 +108,8 @@ class AbstractUsedNamesFilter(AbstractFilter): return [TreeNameDefinition(self._context, name) for name in names] def values(self): - return self._convert_to_names(name for name_list in self._used_names.values() - for name in self._filter(name_list)) + return self._convert_names(name for name_list in self._used_names.values() + for name in self._filter(name_list)) class ParserTreeFilter(AbstractUsedNamesFilter): @@ -150,6 +150,7 @@ class FunctionExecutionFilter(ParserTreeFilter): origin_scope ) + @to_list def _convert_names(self, names): for name in names: param = search_ancestor(name, 'param') diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 01dd55bc..4b6a2da9 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -442,7 +442,7 @@ def _eval_param(evaluator, context, param, scope): res_new.add(scope.instance) else: inst = er.Instance(evaluator, context.parent_context.parent_context, context.parent_context, - Arguments(evaluator, context, ()), + Arguments(evaluator, context), is_generated=True) res_new.add(inst) return res_new diff --git a/jedi/evaluate/instance.py b/jedi/evaluate/instance.py new file mode 100644 index 00000000..ac3c4ccc --- /dev/null +++ b/jedi/evaluate/instance.py @@ -0,0 +1,221 @@ +from abc import abstractproperty + +from jedi import debug +from jedi.evaluate import compiled +from jedi.evaluate.filters import ParserTreeFilter, ContextName +from jedi.evaluate.context import Context + + +class AbstractInstanceContext(Context): + """ + This class is used to evaluate instances. + """ + def __init__(self, evaluator, parent_context, class_context, var_args): + super(AbstractInstanceContext, self).__init__(evaluator, parent_context) + # Generated instances are classes that are just generated by self + # (No var_args) used. + self._class_context = class_context + self.var_args = var_args + + ##### + """" + if class_context.name.string_name in ['list', 'set'] \ + and evaluator.BUILTINS == parent_context.get_root_context(): + # compare the module path with the builtin name. + self.var_args = iterable.check_array_instances(evaluator, self) + elif not is_generated: + # Need to execute the __init__ function, because the dynamic param + # searching needs it. + try: + method = self.get_subscope_by_name('__init__') + except KeyError: + pass + else: + self._init_execution = evaluator.execute(method, self.var_args) + """ + + def is_class(self): + return False + + @property + def py__call__(self): + names = self._get_function_slot_names('__call__') + if not names: + # Means the Instance is not callable. + raise AttributeError + + def execute(arguments): + contexts = set() + for name in names: + for context in name.infer(): + context.execute(arguments) + return contexts + return execute + + def py__class__(self): + return self.class_context + + def py__bool__(self): + # Signalize that we don't know about the bool type. + return None + + def _get_function_slot_names(self, name): + # Python classes don't look at the dictionary of the instance when + # looking up `__call__`. This is something that has to do with Python's + # internal slot system (note: not __slots__, but C slots). + for filter in self.get_filters(include_self_names=False): + names = filter.get(name) + if names: + return names + return [] + + def execute_function_slot(self, name, *args): + raise NotImplementedError + method = self.get_subscope_by_name(name) + return self._evaluator.execute_evaluated(method, *args) + + def get_descriptor_returns(self, obj): + """ Throws a KeyError if there's no method. """ + raise NotImplementedError + # Arguments in __get__ descriptors are obj, class. + # `method` is the new parent of the array, don't know if that's good. + none_obj = compiled.create(self._evaluator, None) + args = [obj, obj.base] if isinstance(obj, Instance) else [none_obj, obj] + try: + return self.execute_subscope_by_name('__get__', *args) + except KeyError: + return set([self]) + + def get_filters(self, search_global=None, until_position=None, + origin_scope=None, include_self_names=True): + if include_self_names: + for cls in self._class_context.py__mro__(): + if isinstance(cls, compiled.CompiledObject): + yield SelfNameFilter(self._evaluator, self, cls, origin_scope) + else: + yield SelfNameFilter(self._evaluator, self, cls.base, origin_scope) + + for cls in self._class_context.py__mro__(): + if isinstance(cls, compiled.CompiledObject): + yield CompiledInstanceClassFilter(self._evaluator, self, cls) + else: + yield InstanceClassFilter(self._evaluator, self, cls.base, origin_scope) + + def py__getitem__(self, index): + try: + method = self.get_subscope_by_name('__getitem__') + except KeyError: + debug.warning('No __getitem__, cannot access the array.') + return set() + else: + index_obj = compiled.create(self._evaluator, index) + return self._evaluator.execute_evaluated(method, index_obj) + + def py__iter__(self): + try: + method = self.get_subscope_by_name('__iter__') + except KeyError: + debug.warning('No __iter__ on %s.' % self) + return + else: + iters = self._evaluator.execute(method) + for generator in iters: + if isinstance(generator, Instance): + # `__next__` logic. + name = '__next__' if is_py3 else 'next' + try: + yield generator.execute_subscope_by_name(name) + except KeyError: + debug.warning('Instance has no __next__ function in %s.', generator) + else: + for typ in generator.py__iter__(): + yield typ + + @abstractproperty + def name(self): + pass + + def __repr__(self): + return "<%s of %s(%s)>" % (type(self).__name__, self._class_context, + self.var_args) + + +class CompiledInstance(AbstractInstanceContext): + @property + def name(self): + return compiled.CompiledContextName(self, self._class_context.name.string_name) + + +class TreeInstance(AbstractInstanceContext): + @property + def name(self): + return ContextName(self, self._class_context.name) + + +class CompiledInstanceClassFilter(compiled.CompiledObjectFilter): + def __init__(self, evaluator, instance, compiled_object): + super(CompiledInstanceClassFilter, self).__init__( + evaluator, + compiled_object, + is_instance=True, + ) + self._instance = instance + + def _filter(self, names): + names = super(CompiledInstanceClassFilter, self)._filter(names) + return [get_instance_el(self._evaluator, self._instance, name, True) + for name in names] + + +class InstanceClassFilter(ParserTreeFilter): + def __init__(self, evaluator, context, parser_scope, origin_scope): + super(InstanceClassFilter, self).__init__( + evaluator=evaluator, + context=context, + parser_scope=parser_scope, + origin_scope=origin_scope + ) + + def _equals_origin_scope(self): + node = self._origin_scope + while node is not None: + if node == self._parser_scope or node == self._context: + return True + node = node.get_parent_scope() + return False + + def _access_possible(self, name): + return not name.value.startswith('__') or name.value.endswith('__') \ + or self._equals_origin_scope() + + def _filter(self, names): + names = super(InstanceClassFilter, self)._filter(names) + return [get_instance_el(self._evaluator, self._context, name, True) + for name in names if self._access_possible(name)] + + def _check_flows(self, names): + return names + + +class SelfNameFilter(InstanceClassFilter): + def _filter(self, names): + names = self._filter_self_names(names) + if isinstance(self._parser_scope, compiled.CompiledObject): + # This would be for builtin skeletons, which are not yet supported. + return [] + start, end = self._parser_scope.start_pos, self._parser_scope.end_pos + return [n for n in names if start < n.start_pos < end] + + def _filter_self_names(self, names): + for name in names: + trailer = name.parent + if tree.is_node(trailer, 'trailer') \ + and len(trailer.children) == 2 \ + and trailer.children[0] == '.': + if name.is_definition() and self._access_possible(name): + init_execution = self._context._get_init_execution() + # Hopefully we can somehow change this. + if init_execution is not None and \ + init_execution.start_pos < name.start_pos < init_execution.end_pos: + name = init_execution.name_for_position(name.start_pos) + yield get_instance_el(self._evaluator, self._context, name) diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index fbbe8066..e5d6007b 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -680,7 +680,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list): return set() def check_additions(arglist, add_name): - params = list(param.Arguments(evaluator, arglist).unpack()) + params = list(param.Arguments(evaluator, context, arglist).unpack()) result = set() if add_name in ['insert']: params = params[1:] diff --git a/jedi/evaluate/param.py b/jedi/evaluate/param.py index 9654e418..58cbeace 100644 --- a/jedi/evaluate/param.py +++ b/jedi/evaluate/param.py @@ -28,7 +28,7 @@ def try_iter_content(types, depth=0): class Arguments(tree.Base): - def __init__(self, evaluator, context, argument_node, trailer): + def __init__(self, evaluator, context, argument_node, trailer=None): """ The argument_node is either a parser node or a list of evaluated objects. Those evaluated objects may be lists of evaluated objects diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index fac7c1fa..9b4b123a 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -58,6 +58,7 @@ from jedi.evaluate import imports from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \ GlobalNameFilter, DictFilter, ContextName from jedi.evaluate.context import TreeContext +from jedi.evaluate.instance import TreeInstance class Executed(TreeContext): @@ -77,15 +78,15 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): """ This class is used to evaluate instances. """ - def __init__(self, evaluator, parent_context, base, var_args, is_generated=False): - super(Instance, self).__init__(evaluator, parent_context, base, var_args) - self.decorates = None + def __init__(self, evaluator, parent_context, class_context, var_args, is_generated=False): + super(Instance, self).__init__(evaluator, parent_context, var_args) # Generated instances are classes that are just generated by self # (No var_args) used. self.is_generated = is_generated + self._class_context = class_context self._init_execution = None - if base.name.string_name in ['list', 'set'] \ + if class_context.name.string_name in ['list', 'set'] \ and evaluator.BUILTINS == parent_context.get_root_context(): # compare the module path with the builtin name. self.var_args = iterable.check_array_instances(evaluator, self) @@ -116,7 +117,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): return actual def py__class__(self): - return self.base + return self.class_context def py__bool__(self): # Signalize that we don't know about the bool type. @@ -174,7 +175,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): return names def get_subscope_by_name(self, name): - sub = self.base.get_subscope_by_name(name) + sub = self._class_context.get_subscope_by_name(name) return get_instance_el(self._evaluator, self, sub, True) def execute_subscope_by_name(self, name, *args): @@ -196,7 +197,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): def names_dicts(self, search_global): yield self._self_names_dict() - for s in self.base.py__mro__()[1:]: + for s in self._class_context.py__mro__()[1:]: if not isinstance(s, compiled.CompiledObject): # Compiled objects don't have `self.` names. for inst in self._evaluator.execute(s): @@ -206,13 +207,13 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): yield LazyInstanceDict(self._evaluator, self, names_dict) def get_filters(self, search_global, until_position=None, origin_scope=None): - for cls in self.base.py__mro__(): + for cls in self._class_context.py__mro__(): if isinstance(cls, compiled.CompiledObject): yield SelfNameFilter(self._evaluator, self, cls, origin_scope) else: yield SelfNameFilter(self._evaluator, self, cls.base, origin_scope) - for cls in self.base.py__mro__(): + for cls in self._class_context.py__mro__(): if isinstance(cls, compiled.CompiledObject): yield CompiledInstanceClassFilter(self._evaluator, self, cls) else: @@ -251,83 +252,11 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): @property @underscore_memoization def name(self): - return ContextName(self, self.base.name) + return ContextName(self, self._class_context.name.string_name) def __repr__(self): - dec = '' - if self.decorates is not None: - dec = " decorates " + repr(self.decorates) - return "<%s of %s(%s)%s>" % (type(self).__name__, self.base, - self.var_args, dec) - - -class CompiledInstanceClassFilter(compiled.CompiledObjectFilter): - def __init__(self, evaluator, instance, compiled_object): - super(CompiledInstanceClassFilter, self).__init__( - evaluator, - compiled_object, - is_instance=True, - ) - self._instance = instance - - def _filter(self, names): - names = super(CompiledInstanceClassFilter, self)._filter(names) - return [get_instance_el(self._evaluator, self._instance, name, True) - for name in names] - - -class InstanceClassFilter(ParserTreeFilter): - def __init__(self, evaluator, context, parser_scope, origin_scope): - super(InstanceClassFilter, self).__init__( - evaluator=evaluator, - context=context, - parser_scope=parser_scope, - origin_scope=origin_scope - ) - - def _equals_origin_scope(self): - node = self._origin_scope - while node is not None: - if node == self._parser_scope or node == self._context: - return True - node = node.get_parent_scope() - return False - - def _access_possible(self, name): - return not name.value.startswith('__') or name.value.endswith('__') \ - or self._equals_origin_scope() - - def _filter(self, names): - names = super(InstanceClassFilter, self)._filter(names) - return [get_instance_el(self._evaluator, self._context, name, True) - for name in names if self._access_possible(name)] - - def _check_flows(self, names): - return names - - -class SelfNameFilter(InstanceClassFilter): - def _filter(self, names): - names = self._filter_self_names(names) - if isinstance(self._parser_scope, compiled.CompiledObject): - # This would be for builtin skeletons, which are not yet supported. - return [] - start, end = self._parser_scope.start_pos, self._parser_scope.end_pos - return [n for n in names if start < n.start_pos < end] - - def _filter_self_names(self, names): - for name in names: - trailer = name.parent - if tree.is_node(trailer, 'trailer') \ - and len(trailer.children) == 2 \ - and trailer.children[0] == '.': - if name.is_definition() and self._access_possible(name): - init_execution = self._context._get_init_execution() - # Hopefully we can somehow change this. - if init_execution is not None and \ - init_execution.start_pos < name.start_pos < init_execution.end_pos: - name = init_execution.name_for_position(name.start_pos) - yield get_instance_el(self._evaluator, self._context, name) + return "<%s of %s(%s)>" % (type(self).__name__, self._class_context, + self.var_args) class LazyInstanceDict(object): @@ -535,7 +464,7 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)): return [compiled.create(self._evaluator, object)] def py__call__(self, params): - return set([Instance(self._evaluator, self.parent_context, self, params)]) + return set([TreeInstance(self._evaluator, self.parent_context, self, params)]) def py__class__(self): return compiled.create(self._evaluator, type) @@ -864,6 +793,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)): self._evaluator = evaluator self.base = self._module = module self._parent_module = parent_module + self.path = None def names_dicts(self, search_global): yield self.base.names_dict