1
0
forked from VimPlug/jedi

move dynamic array stuff to evaluate.iterable

This commit is contained in:
Dave Halter
2013-12-30 01:38:15 +01:00
parent e4692381cb
commit 7b936cf6ec
4 changed files with 182 additions and 183 deletions

View File

@@ -547,7 +547,7 @@ class Evaluator(object):
result.append(er.Function(self, call)) result.append(er.Function(self, call))
# With things like params, these can also be functions... # With things like params, these can also be functions...
elif isinstance(call, pr.Base) and call.isinstance( elif isinstance(call, pr.Base) and call.isinstance(
er.Function, er.Class, er.Instance, dynamic.ArrayInstance): er.Function, er.Class, er.Instance, iterable.ArrayInstance):
result.append(call) result.append(call)
# The string tokens are just operations (+, -, etc.) # The string tokens are just operations (+, -, etc.)
elif not isinstance(call, (str, unicode)): elif not isinstance(call, (str, unicode)):
@@ -764,7 +764,7 @@ def get_iterator_types(inputs):
# Take the first statement (for has always only # Take the first statement (for has always only
# one, remember `in`). And follow it. # one, remember `in`). And follow it.
for it in inputs: for it in inputs:
if isinstance(it, (iterable.Generator, iterable.Array, dynamic.ArrayInstance)): if isinstance(it, (iterable.Generator, iterable.Array, iterable.ArrayInstance)):
iterators.append(it) iterators.append(it)
else: else:
if not hasattr(it, 'execute_subscope_by_name'): if not hasattr(it, 'execute_subscope_by_name'):

View File

@@ -57,7 +57,6 @@ from jedi import cache
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import modules from jedi import modules
from jedi import settings from jedi import settings
from jedi import debug
from jedi.parser import fast as fast_parser from jedi.parser import fast as fast_parser
from jedi.evaluate.cache import memoize_default from jedi.evaluate.cache import memoize_default
@@ -232,18 +231,6 @@ def search_params(evaluator, param):
return result return result
def check_array_additions(evaluator, array):
""" Just a mapper function for the internal _check_array_additions """
if not pr.Array.is_type(array._array, pr.Array.LIST, pr.Array.SET):
# TODO also check for dict updates
return []
is_list = array._array.type == 'list'
current_module = array._array.get_parent_until()
res = _check_array_additions(evaluator, array, current_module, is_list)
return res
def _scan_statement(stmt, search_name, assignment_details=False): def _scan_statement(stmt, search_name, assignment_details=False):
""" Returns the function Call that match search_name in an Array. """ """ Returns the function Call that match search_name in an Array. """
def scan_array(arr, search_name): def scan_array(arr, search_name):
@@ -280,163 +267,6 @@ def _scan_statement(stmt, search_name, assignment_details=False):
return result return result
@memoize_default([], evaluator_is_first_arg=True)
def _check_array_additions(evaluator, compare_array, module, is_list):
"""
Checks if a `pr.Array` has "add" statements:
>>> a = [""]
>>> a.append(1)
"""
if not settings.dynamic_array_additions or module.is_builtin():
return []
def check_calls(calls, add_name):
"""
Calls are processed here. The part before the call is searched and
compared with the original Array.
"""
result = []
for c in calls:
call_path = list(c.generate_call_path())
separate_index = call_path.index(add_name)
if add_name == call_path[-1] or separate_index == 0:
# this means that there is no execution -> [].append
# or the keyword is at the start -> append()
continue
backtrack_path = iter(call_path[:separate_index])
position = c.start_pos
scope = c.get_parent_until(pr.IsScope)
found = evaluator.eval_call_path(backtrack_path, scope, position)
if not compare_array in found:
continue
params = call_path[separate_index + 1]
if not params.values:
continue # no params: just ignore it
if add_name in ['append', 'add']:
for param in params:
result += evaluator.eval_statement(param)
elif add_name in ['insert']:
try:
second_param = params[1]
except IndexError:
continue
else:
result += evaluator.eval_statement(second_param)
elif add_name in ['extend', 'update']:
for param in params:
iterators = evaluator.eval_statement(param)
result += evaluate.get_iterator_types(iterators)
return result
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, iterable.Array):
stmt = element._array.parent
else:
# is an Instance with an ArrayInstance inside
stmt = element.var_args[0].var_args.parent
if isinstance(stmt, er.InstanceElement):
stop_classes = list(stop_classes) + [er.Function]
return stmt.get_parent_until(stop_classes)
temp_param_add = settings.dynamic_params_for_other_modules
settings.dynamic_params_for_other_modules = False
search_names = ['append', 'extend', 'insert'] if is_list else \
['add', 'update']
comp_arr_parent = get_execution_parent(compare_array, er.FunctionExecution)
possible_stmts = []
res = []
for n in search_names:
try:
possible_stmts += module.used_names[n]
except KeyError:
continue
for stmt in possible_stmts:
# Check if the original scope is an execution. If it is, one
# can search for the same statement, that is in the module
# dict. Executions are somewhat special in jedi, since they
# literally copy the contents of a function.
if isinstance(comp_arr_parent, er.FunctionExecution):
stmt = comp_arr_parent. \
get_statement_for_position(stmt.start_pos)
if stmt is None:
continue
# InstanceElements are special, because they don't get copied,
# but have this wrapper around them.
if isinstance(comp_arr_parent, er.InstanceElement):
stmt = er.InstanceElement(comp_arr_parent.instance, stmt)
if evaluator.recursion_detector.push_stmt(stmt):
# check recursion
continue
res += check_calls(_scan_statement(stmt, n), n)
evaluator.recursion_detector.pop_stmt()
# reset settings
settings.dynamic_params_for_other_modules = temp_param_add
return res
def check_array_instances(evaluator, instance):
"""Used for set() and list() instances."""
if not settings.dynamic_arrays_instances:
return instance.var_args
ai = ArrayInstance(evaluator, instance)
return [ai]
class ArrayInstance(pr.Base):
"""
Used for the usage of set() and list().
This is definitely a hack, but a good one :-)
It makes it possible to use set/list conversions.
"""
def __init__(self, evaluator, instance):
self._evaluator = evaluator
self.instance = instance
self.var_args = instance.var_args
def iter_content(self):
"""
The index is here just ignored, because of all the appends, etc.
lists/sets are too complicated too handle that.
"""
items = []
from jedi import evaluate
for stmt in self.var_args:
for typ in self._evaluator.eval_statement(stmt):
if isinstance(typ, evaluate.er.Instance) and len(typ.var_args):
array = typ.var_args[0]
if isinstance(array, ArrayInstance):
# prevent recursions
# TODO compare Modules
if self.var_args.start_pos != array.var_args.start_pos:
items += array.iter_content()
else:
debug.warning(
'ArrayInstance recursion',
self.var_args)
continue
items += evaluate.get_iterator_types([typ])
# TODO check if exclusion of tuple is a problem here.
if isinstance(self.var_args, tuple) or self.var_args.parent is None:
return [] # generated var_args should not be checked for arrays
module = self.var_args.get_parent_until()
is_list = str(self.instance.name) == 'list'
items += _check_array_additions(self._evaluator, self.instance, module, is_list)
return items
def check_flow_information(evaluator, flow, search_name, pos): def check_flow_information(evaluator, flow, search_name, pos):
""" Try to find out the type of a variable just with the information that """ Try to find out the type of a variable just with the information that
is given by the flows: e.g. It is also responsible for assert checks.:: is given by the flows: e.g. It is also responsible for assert checks.::

View File

@@ -1,12 +1,13 @@
import itertools import itertools
from jedi._compatibility import use_metaclass
from jedi import common from jedi import common
from jedi.parser import representation as pr
from jedi import debug from jedi import debug
from jedi import settings
from jedi._compatibility import use_metaclass
from jedi.parser import representation as pr
from jedi.evaluate import builtin from jedi.evaluate import builtin
from jedi.evaluate import dynamic from jedi.evaluate import dynamic
from jedi.evaluate.cache import CachedMetaClass from jedi.evaluate.cache import CachedMetaClass, memoize_default
class Generator(use_metaclass(CachedMetaClass, pr.Base)): class Generator(use_metaclass(CachedMetaClass, pr.Base)):
@@ -89,7 +90,7 @@ class Array(use_metaclass(CachedMetaClass, pr.Base)):
return self.get_exact_index_types(index.var_args[0]) return self.get_exact_index_types(index.var_args[0])
result = list(self._follow_values(self._array.values)) result = list(self._follow_values(self._array.values))
result += dynamic.check_array_additions(self._evaluator, self) result += check_array_additions(self._evaluator, self)
return set(result) return set(result)
def get_exact_index_types(self, mixed_index): def get_exact_index_types(self, mixed_index):
@@ -180,3 +181,172 @@ class ArrayMethod(object):
def __repr__(self): def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.name) return "<%s of %s>" % (type(self).__name__, self.name)
def check_array_additions(evaluator, array):
""" Just a mapper function for the internal _check_array_additions """
if not pr.Array.is_type(array._array, pr.Array.LIST, pr.Array.SET):
# TODO also check for dict updates
return []
is_list = array._array.type == 'list'
current_module = array._array.get_parent_until()
res = _check_array_additions(evaluator, array, current_module, is_list)
return res
@memoize_default([], evaluator_is_first_arg=True)
def _check_array_additions(evaluator, compare_array, module, is_list):
"""
Checks if a `pr.Array` has "add" statements:
>>> a = [""]
>>> a.append(1)
"""
if not settings.dynamic_array_additions or module.is_builtin():
return []
def check_calls(calls, add_name):
"""
Calls are processed here. The part before the call is searched and
compared with the original Array.
"""
result = []
for c in calls:
call_path = list(c.generate_call_path())
separate_index = call_path.index(add_name)
if add_name == call_path[-1] or separate_index == 0:
# this means that there is no execution -> [].append
# or the keyword is at the start -> append()
continue
backtrack_path = iter(call_path[:separate_index])
position = c.start_pos
scope = c.get_parent_until(pr.IsScope)
found = evaluator.eval_call_path(backtrack_path, scope, position)
if not compare_array in found:
continue
params = call_path[separate_index + 1]
if not params.values:
continue # no params: just ignore it
if add_name in ['append', 'add']:
for param in params:
result += evaluator.eval_statement(param)
elif add_name in ['insert']:
try:
second_param = params[1]
except IndexError:
continue
else:
result += evaluator.eval_statement(second_param)
elif add_name in ['extend', 'update']:
for param in params:
iterators = evaluator.eval_statement(param)
result += evaluate.get_iterator_types(iterators)
return result
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, iterable.Array):
stmt = element._array.parent
else:
# is an Instance with an ArrayInstance inside
stmt = element.var_args[0].var_args.parent
if isinstance(stmt, er.InstanceElement):
stop_classes = list(stop_classes) + [er.Function]
return stmt.get_parent_until(stop_classes)
temp_param_add = settings.dynamic_params_for_other_modules
settings.dynamic_params_for_other_modules = False
search_names = ['append', 'extend', 'insert'] if is_list else \
['add', 'update']
comp_arr_parent = get_execution_parent(compare_array, er.FunctionExecution)
possible_stmts = []
res = []
for n in search_names:
try:
possible_stmts += module.used_names[n]
except KeyError:
continue
for stmt in possible_stmts:
# Check if the original scope is an execution. If it is, one
# can search for the same statement, that is in the module
# dict. Executions are somewhat special in jedi, since they
# literally copy the contents of a function.
if isinstance(comp_arr_parent, er.FunctionExecution):
stmt = comp_arr_parent. \
get_statement_for_position(stmt.start_pos)
if stmt is None:
continue
# InstanceElements are special, because they don't get copied,
# but have this wrapper around them.
if isinstance(comp_arr_parent, er.InstanceElement):
stmt = er.InstanceElement(comp_arr_parent.instance, stmt)
if evaluator.recursion_detector.push_stmt(stmt):
# check recursion
continue
res += check_calls(dynamic._scan_statement(stmt, n), n)
evaluator.recursion_detector.pop_stmt()
# reset settings
settings.dynamic_params_for_other_modules = temp_param_add
return res
def check_array_instances(evaluator, instance):
"""Used for set() and list() instances."""
if not settings.dynamic_arrays_instances:
return instance.var_args
ai = ArrayInstance(evaluator, instance)
return [ai]
class ArrayInstance(pr.Base):
"""
Used for the usage of set() and list().
This is definitely a hack, but a good one :-)
It makes it possible to use set/list conversions.
"""
def __init__(self, evaluator, instance):
self._evaluator = evaluator
self.instance = instance
self.var_args = instance.var_args
def iter_content(self):
"""
The index is here just ignored, because of all the appends, etc.
lists/sets are too complicated too handle that.
"""
items = []
from jedi import evaluate
for stmt in self.var_args:
for typ in self._evaluator.eval_statement(stmt):
if isinstance(typ, evaluate.er.Instance) and len(typ.var_args):
array = typ.var_args[0]
if isinstance(array, ArrayInstance):
# prevent recursions
# TODO compare Modules
if self.var_args.start_pos != array.var_args.start_pos:
items += array.iter_content()
else:
debug.warning(
'ArrayInstance recursion',
self.var_args)
continue
items += evaluate.get_iterator_types([typ])
# TODO check if exclusion of tuple is a problem here.
if isinstance(self.var_args, tuple) or self.var_args.parent is None:
return [] # generated var_args should not be checked for arrays
module = self.var_args.get_parent_until()
is_list = str(self.instance.name) == 'list'
items += _check_array_additions(self._evaluator, self.instance, module, is_list)
return items

View File

@@ -19,9 +19,8 @@ from jedi import common
from jedi.evaluate import builtin from jedi.evaluate import builtin
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate.cache import memoize_default, CachedMetaClass from jedi.evaluate.cache import memoize_default, CachedMetaClass
from jedi.evaluate.iterable import Array, Generator from jedi.evaluate import iterable
from jedi import docstrings from jedi import docstrings
from jedi.evaluate import dynamic
class Executable(pr.IsScope): class Executable(pr.IsScope):
@@ -51,7 +50,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
if str(base.name) in ['list', 'set'] \ if str(base.name) in ['list', 'set'] \
and builtin.Builtin.scope == base.get_parent_until(): and builtin.Builtin.scope == base.get_parent_until():
# compare the module path with the builtin name. # compare the module path with the builtin name.
self.var_args = dynamic.check_array_instances(evaluator, self) self.var_args = iterable.check_array_instances(evaluator, self)
else: else:
# need to execute the __init__ function, because the dynamic param # need to execute the __init__ function, because the dynamic param
# searching needs it. # searching needs it.
@@ -406,7 +405,7 @@ class FunctionExecution(Executable):
for listener in func.listeners: for listener in func.listeners:
listener.execute(self._get_params()) listener.execute(self._get_params())
if func.is_generator and not evaluate_generator: if func.is_generator and not evaluate_generator:
return [Generator(self._evaluator, func, self.var_args)] return [iterable.Generator(self._evaluator, func, self.var_args)]
else: else:
stmts = docstrings.find_return_types(self._evaluator, func) stmts = docstrings.find_return_types(self._evaluator, func)
for r in self.returns: for r in self.returns:
@@ -570,17 +569,17 @@ class FunctionExecution(Executable):
# *args must be some sort of an array, otherwise -> ignore # *args must be some sort of an array, otherwise -> ignore
for array in arrays: for array in arrays:
if isinstance(array, Array): if isinstance(array, iterable.Array):
for field_stmt in array: # yield from plz! for field_stmt in array: # yield from plz!
yield None, field_stmt yield None, field_stmt
elif isinstance(array, Generator): elif isinstance(array, iterable.Generator):
for field_stmt in array.iter_content(): for field_stmt in array.iter_content():
yield None, helpers.FakeStatement(field_stmt) yield None, helpers.FakeStatement(field_stmt)
# **kwargs # **kwargs
elif expression_list[0] == '**': elif expression_list[0] == '**':
arrays = self._evaluator.eval_expression_list(expression_list[1:]) arrays = self._evaluator.eval_expression_list(expression_list[1:])
for array in arrays: for array in arrays:
if isinstance(array, Array): if isinstance(array, iterable.Array):
for key_stmt, value_stmt in array.items(): for key_stmt, value_stmt in array.items():
# first index, is the key if syntactically correct # first index, is the key if syntactically correct
call = key_stmt.expression_list()[0] call = key_stmt.expression_list()[0]