diff --git a/jedi/api_classes.py b/jedi/api_classes.py index 2a3ae97e..284dec6a 100644 --- a/jedi/api_classes.py +++ b/jedi/api_classes.py @@ -12,6 +12,7 @@ from jedi import common from jedi.parser import representation as pr from jedi import cache from jedi.evaluate import representation as er +from jedi.evaluate import iterable from jedi.evaluate import imports from jedi import keywords @@ -440,7 +441,7 @@ class Definition(BaseDefinition): if isinstance(d, pr.Name): return d.names[-1] if d.names else None - elif isinstance(d, er.Array): + elif isinstance(d, iterable.Array): return unicode(d.type) elif isinstance(d, (pr.Class, er.Class, er.Instance, er.Function, pr.Function)): @@ -493,7 +494,7 @@ class Definition(BaseDefinition): if isinstance(d, pr.Name): d = d.parent - if isinstance(d, er.Array): + if isinstance(d, iterable.Array): d = 'class ' + d.type elif isinstance(d, (pr.Class, er.Class, er.Instance)): d = 'class ' + unicode(d.name) diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 422c0e97..09b64e1c 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -81,6 +81,7 @@ from jedi.evaluate import representation as er from jedi.evaluate import builtin from jedi.evaluate import imports from jedi.evaluate import recursion +from jedi.evaluate import iterable from jedi.evaluate.cache import memoize_default from jedi import docstrings from jedi.evaluate import dynamic @@ -105,7 +106,7 @@ def get_defined_names_for_position(scope, position=None, start_scope=None): names = scope.get_defined_names() # Instances have special rules, always return all the possible completions, # because class variables are always valid and the `self.` variables, too. - if (not position or isinstance(scope, (er.Array, er.Instance)) + if (not position or isinstance(scope, (iterable.Array, er.Instance)) or start_scope != scope and isinstance(start_scope, (pr.Function, er.FunctionExecution))): return names @@ -563,7 +564,7 @@ class Evaluator(object): continue result += self.eval_call(call) elif call == '*': - if [r for r in result if isinstance(r, er.Array) + if [r for r in result if isinstance(r, iterable.Array) or isinstance(r, er.Instance) and str(r.name) == 'str']: # if it is an iterable, ignore * operations @@ -587,7 +588,7 @@ class Evaluator(object): current = next(path) if isinstance(current, pr.Array): - types = [er.Array(self, current)] + types = [iterable.Array(self, current)] else: if isinstance(current, pr.NamePart): # This is the first global lookup. @@ -677,7 +678,7 @@ class Evaluator(object): if obj.isinstance(er.Class): # There maybe executions of executions. return [er.Instance(self, obj, params)] - elif isinstance(obj, er.Generator): + elif isinstance(obj, iterable.Generator): return obj.iter_content() else: stmts = [] @@ -763,7 +764,7 @@ def get_iterator_types(inputs): # Take the first statement (for has always only # one, remember `in`). And follow it. for it in inputs: - if isinstance(it, (er.Generator, er.Array, dynamic.ArrayInstance)): + if isinstance(it, (iterable.Generator, iterable.Array, dynamic.ArrayInstance)): iterators.append(it) else: if not hasattr(it, 'execute_subscope_by_name'): @@ -776,7 +777,7 @@ def get_iterator_types(inputs): result = [] for gen in iterators: - if isinstance(gen, er.Array): + if isinstance(gen, iterable.Array): # Array is a little bit special, since this is an internal # array, but there's also the list builtin, which is # another thing. diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index ac479395..589ffcd0 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -333,10 +333,11 @@ def _check_array_additions(evaluator, compare_array, module, is_list): from jedi.evaluate import representation as er from jedi import evaluate + from jedi.evaluate import iterable def get_execution_parent(element, *stop_classes): """ Used to get an Instance/FunctionExecution parent """ - if isinstance(element, er.Array): + if isinstance(element, iterable.Array): stmt = element._array.parent else: # is an Instance with an ArrayInstance inside @@ -463,7 +464,7 @@ def check_flow_information(evaluator, flow, search_name, pos): def _check_isinstance_type(evaluator, stmt, search_name): - from jedi.evaluate import representation as er + from jedi.evaluate import iterable try: expression_list = stmt.expression_list() # this might be removed if we analyze and, etc @@ -487,6 +488,6 @@ def _check_isinstance_type(evaluator, stmt, search_name): result = [] for c in evaluator.eval_call(classes[0]): - for typ in (c.get_index_types() if isinstance(c, er.Array) else [c]): + for typ in (c.get_index_types() if isinstance(c, iterable.Array) else [c]): result += evaluator.execute(typ) return result diff --git a/jedi/evaluate/interfaces.py b/jedi/evaluate/interfaces.py deleted file mode 100644 index cee5b244..00000000 --- a/jedi/evaluate/interfaces.py +++ /dev/null @@ -1,3 +0,0 @@ -class Iterable(): - """Parent class of Generator and Array, exists due to import restrictions.""" - pass diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py new file mode 100644 index 00000000..cb655760 --- /dev/null +++ b/jedi/evaluate/iterable.py @@ -0,0 +1,182 @@ +import itertools + +from jedi._compatibility import use_metaclass +from jedi import common +from jedi.parser import representation as pr +from jedi import debug +from jedi.evaluate import builtin +from jedi.evaluate import dynamic +from jedi.evaluate.cache import CachedMetaClass + + +class Generator(use_metaclass(CachedMetaClass, pr.Base)): + """ Cares for `yield` statements. """ + def __init__(self, evaluator, func, var_args): + super(Generator, self).__init__() + self._evaluator = evaluator + self.func = func + self.var_args = var_args + + def get_defined_names(self): + """ + Returns a list of names that define a generator, which can return the + content of a generator. + """ + names = [] + none_pos = (0, 0) + executes_generator = ('__next__', 'send') + for n in ('close', 'throw') + executes_generator: + name = pr.Name(builtin.Builtin.scope, [(n, none_pos)], + none_pos, none_pos) + if n in executes_generator: + name.parent = self + else: + name.parent = builtin.Builtin.scope + names.append(name) + debug.dbg('generator names', names) + return names + + def iter_content(self): + """ returns the content of __iter__ """ + return self._evaluator.execute(self.func, self.var_args, True) + + def get_index_types(self, index=None): + debug.warning('Tried to get array access on a generator', self) + return [] + + def __getattr__(self, name): + if name not in ['start_pos', 'end_pos', 'parent', 'get_imports', + 'asserts', 'doc', 'docstr', 'get_parent_until', + 'get_code', 'subscopes']: + raise AttributeError("Accessing %s of %s is not allowed." + % (self, name)) + return getattr(self.func, name) + + def __repr__(self): + return "<%s of %s>" % (type(self).__name__, self.func) + + +class Array(use_metaclass(CachedMetaClass, pr.Base)): + """ + Used as a mirror to pr.Array, if needed. It defines some getter + methods which are important in this module. + """ + def __init__(self, evaluator, array): + self._evaluator = evaluator + self._array = array + + def get_index_types(self, index_arr=None): + """ Get the types of a specific index or all, if not given """ + if index_arr is not None: + if index_arr and [x for x in index_arr if ':' in x.expression_list()]: + # array slicing + return [self] + + index_possibilities = self._follow_values(index_arr) + if len(index_possibilities) == 1: + # This is indexing only one element, with a fixed index number, + # otherwise it just ignores the index (e.g. [1+1]). + index = index_possibilities[0] + + from jedi.evaluate.representation import Instance + if isinstance(index, Instance) \ + and str(index.name) in ['int', 'str'] \ + and len(index.var_args) == 1: + # TODO this is just very hackish and a lot of use cases are + # being ignored + with common.ignored(KeyError, IndexError, + UnboundLocalError, TypeError): + return self.get_exact_index_types(index.var_args[0]) + + result = list(self._follow_values(self._array.values)) + result += dynamic.check_array_additions(self._evaluator, self) + return set(result) + + def get_exact_index_types(self, mixed_index): + """ Here the index is an int/str. Raises IndexError/KeyError """ + index = mixed_index + if self.type == pr.Array.DICT: + index = None + for i, key_statement in enumerate(self._array.keys): + # Because we only want the key to be a string. + key_expression_list = key_statement.expression_list() + if len(key_expression_list) != 1: # cannot deal with complex strings + continue + key = key_expression_list[0] + if isinstance(key, pr.String): + str_key = key.value + elif isinstance(key, pr.Name): + str_key = str(key) + + if mixed_index == str_key: + index = i + break + if index is None: + raise KeyError('No key found in dictionary') + + # Can raise an IndexError + values = [self._array.values[index]] + return self._follow_values(values) + + def _follow_values(self, values): + """ helper function for the index getters """ + return list(itertools.chain.from_iterable(self._evaluator.eval_statement(v) + for v in values)) + + def get_defined_names(self): + """ + This method generates all `ArrayMethod` for one pr.Array. + It returns e.g. for a list: append, pop, ... + """ + # `array.type` is a string with the type, e.g. 'list'. + scope = self._evaluator.find_name(builtin.Builtin.scope, self._array.type)[0] + scope = self._evaluator.execute(scope)[0] # builtins only have one class + names = scope.get_defined_names() + return [ArrayMethod(n) for n in names] + + @property + def parent(self): + return builtin.Builtin.scope + + def get_parent_until(self): + return builtin.Builtin.scope + + def __getattr__(self, name): + if name not in ['type', 'start_pos', 'get_only_subelement', 'parent', + 'get_parent_until', 'items']: + raise AttributeError('Strange access on %s: %s.' % (self, name)) + return getattr(self._array, name) + + def __getitem__(self): + return self._array.__getitem__() + + def __iter__(self): + return self._array.__iter__() + + def __len__(self): + return self._array.__len__() + + def __repr__(self): + return "" % (type(self).__name__, self._array) + + +class ArrayMethod(object): + """ + A name, e.g. `list.append`, it is used to access the original array + methods. + """ + def __init__(self, name): + super(ArrayMethod, self).__init__() + self.name = name + + def __getattr__(self, name): + # Set access privileges: + if name not in ['parent', 'names', 'start_pos', 'end_pos', 'get_code']: + raise AttributeError('Strange accesson %s: %s.' % (self, name)) + return getattr(self.name, name) + + def get_parent_until(self): + return builtin.Builtin.scope + + def __repr__(self): + return "<%s of %s>" % (type(self).__name__, self.name) diff --git a/jedi/evaluate/recursion.py b/jedi/evaluate/recursion.py index a0bde9f2..51bb598e 100644 --- a/jedi/evaluate/recursion.py +++ b/jedi/evaluate/recursion.py @@ -11,7 +11,7 @@ from jedi.parser import representation as pr from jedi import debug from jedi import settings from jedi.evaluate import builtin -from jedi.evaluate import interfaces +from jedi.evaluate import iterable def recursion_decorator(func): @@ -145,7 +145,7 @@ class ExecutionRecursionDetector(object): if cls.execution_count > settings.max_executions: return True - if isinstance(execution.base, interfaces.Iterable): + if isinstance(execution.base, (iterable.Array, iterable.Generator)): return False module = execution.get_parent_until() if evaluate_generator or module == builtin.Builtin.scope: diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 170d6905..680e9495 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -9,10 +9,7 @@ instantiated. This class represents these cases. So, why is there also a ``Class`` class here? Well, there are decorators and they change classes in Python 3. """ -from __future__ import with_statement - import copy -import itertools from jedi._compatibility import use_metaclass, next, unicode from jedi.parser import representation as pr @@ -22,7 +19,7 @@ from jedi import common from jedi.evaluate import builtin from jedi.evaluate import recursion from jedi.evaluate.cache import memoize_default, CachedMetaClass -from jedi.evaluate.interfaces import Iterable +from jedi.evaluate.iterable import Array, Generator from jedi import docstrings from jedi.evaluate import dynamic @@ -680,174 +677,3 @@ class FunctionExecution(Executable): def __repr__(self): return "<%s of %s>" % \ (type(self).__name__, self.base) - - -class Generator(use_metaclass(CachedMetaClass, pr.Base, Iterable)): - """ Cares for `yield` statements. """ - def __init__(self, evaluator, func, var_args): - super(Generator, self).__init__() - self._evaluator = evaluator - self.func = func - self.var_args = var_args - - def get_defined_names(self): - """ - Returns a list of names that define a generator, which can return the - content of a generator. - """ - names = [] - none_pos = (0, 0) - executes_generator = ('__next__', 'send') - for n in ('close', 'throw') + executes_generator: - name = pr.Name(builtin.Builtin.scope, [(n, none_pos)], - none_pos, none_pos) - if n in executes_generator: - name.parent = self - else: - name.parent = builtin.Builtin.scope - names.append(name) - debug.dbg('generator names', names) - return names - - def iter_content(self): - """ returns the content of __iter__ """ - return self._evaluator.execute(self.func, self.var_args, True) - - def get_index_types(self, index=None): - debug.warning('Tried to get array access on a generator', self) - return [] - - def __getattr__(self, name): - if name not in ['start_pos', 'end_pos', 'parent', 'get_imports', - 'asserts', 'doc', 'docstr', 'get_parent_until', - 'get_code', 'subscopes']: - raise AttributeError("Accessing %s of %s is not allowed." - % (self, name)) - return getattr(self.func, name) - - def __repr__(self): - return "<%s of %s>" % (type(self).__name__, self.func) - - -class Array(use_metaclass(CachedMetaClass, pr.Base, Iterable)): - """ - Used as a mirror to pr.Array, if needed. It defines some getter - methods which are important in this module. - """ - def __init__(self, evaluator, array): - self._evaluator = evaluator - self._array = array - - def get_index_types(self, index_arr=None): - """ Get the types of a specific index or all, if not given """ - if index_arr is not None: - if index_arr and [x for x in index_arr if ':' in x.expression_list()]: - # array slicing - return [self] - - index_possibilities = self._follow_values(index_arr) - if len(index_possibilities) == 1: - # This is indexing only one element, with a fixed index number, - # otherwise it just ignores the index (e.g. [1+1]). - index = index_possibilities[0] - if isinstance(index, Instance) \ - and str(index.name) in ['int', 'str'] \ - and len(index.var_args) == 1: - # TODO this is just very hackish and a lot of use cases are - # being ignored - with common.ignored(KeyError, IndexError, - UnboundLocalError, TypeError): - return self.get_exact_index_types(index.var_args[0]) - - result = list(self._follow_values(self._array.values)) - result += dynamic.check_array_additions(self._evaluator, self) - return set(result) - - def get_exact_index_types(self, mixed_index): - """ Here the index is an int/str. Raises IndexError/KeyError """ - index = mixed_index - if self.type == pr.Array.DICT: - index = None - for i, key_statement in enumerate(self._array.keys): - # Because we only want the key to be a string. - key_expression_list = key_statement.expression_list() - if len(key_expression_list) != 1: # cannot deal with complex strings - continue - key = key_expression_list[0] - if isinstance(key, pr.String): - str_key = key.value - elif isinstance(key, pr.Name): - str_key = str(key) - - if mixed_index == str_key: - index = i - break - if index is None: - raise KeyError('No key found in dictionary') - - # Can raise an IndexError - values = [self._array.values[index]] - return self._follow_values(values) - - def _follow_values(self, values): - """ helper function for the index getters """ - return list(itertools.chain.from_iterable(self._evaluator.eval_statement(v) - for v in values)) - - def get_defined_names(self): - """ - This method generates all `ArrayMethod` for one pr.Array. - It returns e.g. for a list: append, pop, ... - """ - # `array.type` is a string with the type, e.g. 'list'. - scope = self._evaluator.find_name(builtin.Builtin.scope, self._array.type)[0] - scope = self._evaluator.execute(scope)[0] # builtins only have one class - names = scope.get_defined_names() - return [ArrayMethod(n) for n in names] - - @property - def parent(self): - return builtin.Builtin.scope - - def get_parent_until(self): - return builtin.Builtin.scope - - def __getattr__(self, name): - if name not in ['type', 'start_pos', 'get_only_subelement', 'parent', - 'get_parent_until', 'items']: - raise AttributeError('Strange access on %s: %s.' % (self, name)) - return getattr(self._array, name) - - def __getitem__(self): - return self._array.__getitem__() - - def __iter__(self): - return self._array.__iter__() - - def __len__(self): - return self._array.__len__() - - def __repr__(self): - return "" % (type(self).__name__, self._array) - - -class ArrayMethod(object): - """ - A name, e.g. `list.append`, it is used to access the original array - methods. - """ - def __init__(self, name): - super(ArrayMethod, self).__init__() - self.name = name - - def __getattr__(self, name): - # Set access privileges: - if name not in ['parent', 'names', 'start_pos', 'end_pos', 'get_code']: - raise AttributeError('Strange accesson %s: %s.' % (self, name)) - return getattr(self.name, name) - - def get_parent_until(self): - return builtin.Builtin.scope - - def __repr__(self): - return "<%s of %s>" % (type(self).__name__, self.name) diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py index 5fe41ffa..25857d9f 100644 --- a/jedi/evaluate/stdlib.py +++ b/jedi/evaluate/stdlib.py @@ -5,6 +5,7 @@ understand them with Jedi. from jedi.evaluate import builtin from jedi.evaluate import representation as er +from jedi.evaluate import iterable from jedi.parser import representation as pr from jedi import debug @@ -14,7 +15,7 @@ class NotInStdLib(LookupError): def execute(evaluator, obj, params): - if not isinstance(obj, (er.Generator, er.Array)): + if not isinstance(obj, (iterable.Generator, iterable.Array)): obj_name = str(obj.name) if obj.parent == builtin.Builtin.scope: # for now we just support builtin functions.