forked from VimPlug/jedi
Move the dynamic arrays code
This commit is contained in:
164
jedi/inference/value/dynamic_arrays.py
Normal file
164
jedi/inference/value/dynamic_arrays.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
"""
|
||||||
|
A module to deal with stuff like `list.append` and `set.add`.
|
||||||
|
|
||||||
|
Array modifications
|
||||||
|
*******************
|
||||||
|
|
||||||
|
If the content of an array (``set``/``list``) is requested somewhere, the
|
||||||
|
current module will be checked for appearances of ``arr.append``,
|
||||||
|
``arr.insert``, etc. If the ``arr`` name points to an actual array, the
|
||||||
|
content will be added
|
||||||
|
|
||||||
|
This can be really cpu intensive, as you can imagine. Because |jedi| has to
|
||||||
|
follow **every** ``append`` and check wheter it's the right array. However this
|
||||||
|
works pretty good, because in *slow* cases, the recursion detector and other
|
||||||
|
settings will stop this process.
|
||||||
|
|
||||||
|
It is important to note that:
|
||||||
|
|
||||||
|
1. Array modfications work only in the current module.
|
||||||
|
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||||
|
"""
|
||||||
|
from jedi import debug
|
||||||
|
from jedi import settings
|
||||||
|
from jedi.inference import recursion
|
||||||
|
from jedi.inference.base_value import ValueSet, NO_VALUES, HelperValueMixin
|
||||||
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
|
|
||||||
|
|
||||||
|
def check_array_additions(context, sequence):
|
||||||
|
""" Just a mapper function for the internal _internal_check_array_additions """
|
||||||
|
if sequence.array_type not in ('list', 'set'):
|
||||||
|
# TODO also check for dict updates
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
|
return _internal_check_array_additions(context, sequence)
|
||||||
|
|
||||||
|
|
||||||
|
@inference_state_method_cache(default=NO_VALUES)
|
||||||
|
@debug.increase_indent
|
||||||
|
def _internal_check_array_additions(context, sequence):
|
||||||
|
"""
|
||||||
|
Checks if a `Array` has "add" (append, insert, extend) statements:
|
||||||
|
|
||||||
|
>>> a = [""]
|
||||||
|
>>> a.append(1)
|
||||||
|
"""
|
||||||
|
from jedi.inference import arguments
|
||||||
|
|
||||||
|
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
||||||
|
module_context = context.get_root_context()
|
||||||
|
if not settings.dynamic_array_additions or module_context.is_compiled():
|
||||||
|
debug.dbg('Dynamic array search aborted.', color='MAGENTA')
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
|
def find_additions(context, arglist, add_name):
|
||||||
|
params = list(arguments.TreeArguments(context.inference_state, context, arglist).unpack())
|
||||||
|
result = set()
|
||||||
|
if add_name in ['insert']:
|
||||||
|
params = params[1:]
|
||||||
|
if add_name in ['append', 'add', 'insert']:
|
||||||
|
for key, lazy_value in params:
|
||||||
|
result.add(lazy_value)
|
||||||
|
elif add_name in ['extend', 'update']:
|
||||||
|
for key, lazy_value in params:
|
||||||
|
result |= set(lazy_value.infer().iterate())
|
||||||
|
return result
|
||||||
|
|
||||||
|
temp_param_add, settings.dynamic_params_for_other_modules = \
|
||||||
|
settings.dynamic_params_for_other_modules, False
|
||||||
|
|
||||||
|
is_list = sequence.name.string_name == 'list'
|
||||||
|
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
|
||||||
|
|
||||||
|
added_types = set()
|
||||||
|
for add_name in search_names:
|
||||||
|
try:
|
||||||
|
possible_names = module_context.tree_node.get_used_names()[add_name]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
for name in possible_names:
|
||||||
|
value_node = context.tree_node
|
||||||
|
if not (value_node.start_pos < name.start_pos < value_node.end_pos):
|
||||||
|
continue
|
||||||
|
trailer = name.parent
|
||||||
|
power = trailer.parent
|
||||||
|
trailer_pos = power.children.index(trailer)
|
||||||
|
try:
|
||||||
|
execution_trailer = power.children[trailer_pos + 1]
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if execution_trailer.type != 'trailer' \
|
||||||
|
or execution_trailer.children[0] != '(' \
|
||||||
|
or execution_trailer.children[1] == ')':
|
||||||
|
continue
|
||||||
|
|
||||||
|
random_context = context.create_context(name)
|
||||||
|
|
||||||
|
with recursion.execution_allowed(context.inference_state, power) as allowed:
|
||||||
|
if allowed:
|
||||||
|
found = infer_call_of_leaf(
|
||||||
|
random_context,
|
||||||
|
name,
|
||||||
|
cut_own_trailer=True
|
||||||
|
)
|
||||||
|
if sequence in found:
|
||||||
|
# The arrays match. Now add the results
|
||||||
|
added_types |= find_additions(
|
||||||
|
random_context,
|
||||||
|
execution_trailer.children[1],
|
||||||
|
add_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# reset settings
|
||||||
|
settings.dynamic_params_for_other_modules = temp_param_add
|
||||||
|
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
||||||
|
return added_types
|
||||||
|
|
||||||
|
|
||||||
|
def get_dynamic_array_instance(instance, arguments):
|
||||||
|
"""Used for set() and list() instances."""
|
||||||
|
ai = _ArrayInstance(instance, arguments)
|
||||||
|
from jedi.inference import arguments
|
||||||
|
return arguments.ValuesArguments([ValueSet([ai])])
|
||||||
|
|
||||||
|
|
||||||
|
class _ArrayInstance(HelperValueMixin):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
This is not a proper context, because it doesn't have to be. It's not used
|
||||||
|
in the wild, it's just used within typeshed as an argument to `__init__`
|
||||||
|
for set/list and never used in any other place.
|
||||||
|
"""
|
||||||
|
def __init__(self, instance, var_args):
|
||||||
|
self.instance = instance
|
||||||
|
self.var_args = var_args
|
||||||
|
|
||||||
|
def py__class__(self):
|
||||||
|
tuple_, = self.instance.inference_state.builtins_module.py__getattribute__('tuple')
|
||||||
|
return tuple_
|
||||||
|
|
||||||
|
def py__iter__(self, contextualized_node=None):
|
||||||
|
var_args = self.var_args
|
||||||
|
try:
|
||||||
|
_, lazy_value = next(var_args.unpack())
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for lazy in lazy_value.infer().iterate():
|
||||||
|
yield lazy
|
||||||
|
|
||||||
|
from jedi.inference import arguments
|
||||||
|
if isinstance(var_args, arguments.TreeArguments):
|
||||||
|
additions = _internal_check_array_additions(var_args.context, self.instance)
|
||||||
|
for addition in additions:
|
||||||
|
yield addition
|
||||||
|
|
||||||
|
def iterate(self, contextualized_node=None, is_async=False):
|
||||||
|
return self.py__iter__(contextualized_node)
|
||||||
@@ -18,6 +18,7 @@ from jedi.inference.value.function import \
|
|||||||
from jedi.inference.value.klass import ClassValue, apply_py__get__, \
|
from jedi.inference.value.klass import ClassValue, apply_py__get__, \
|
||||||
ClassFilter
|
ClassFilter
|
||||||
from jedi.inference.value import iterable
|
from jedi.inference.value import iterable
|
||||||
|
from jedi.inference.value.dynamic_arrays import get_dynamic_array_instance
|
||||||
from jedi.parser_utils import get_parent_scope
|
from jedi.parser_utils import get_parent_scope
|
||||||
|
|
||||||
|
|
||||||
@@ -263,7 +264,7 @@ class TreeInstance(AbstractInstanceValue):
|
|||||||
and parent_context.get_root_context().is_builtins_module():
|
and parent_context.get_root_context().is_builtins_module():
|
||||||
# compare the module path with the builtin name.
|
# compare the module path with the builtin name.
|
||||||
if settings.dynamic_array_additions:
|
if settings.dynamic_array_additions:
|
||||||
var_args = iterable.get_dynamic_array_instance(self, var_args)
|
var_args = get_dynamic_array_instance(self, var_args)
|
||||||
|
|
||||||
super(TreeInstance, self).__init__(inference_state, parent_context,
|
super(TreeInstance, self).__init__(inference_state, parent_context,
|
||||||
class_value, var_args)
|
class_value, var_args)
|
||||||
|
|||||||
@@ -1,45 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
Contains all classes and functions to deal with lists, dicts, generators and
|
Contains all classes and functions to deal with lists, dicts, generators and
|
||||||
iterators in general.
|
iterators in general.
|
||||||
|
|
||||||
Array modifications
|
|
||||||
*******************
|
|
||||||
|
|
||||||
If the content of an array (``set``/``list``) is requested somewhere, the
|
|
||||||
current module will be checked for appearances of ``arr.append``,
|
|
||||||
``arr.insert``, etc. If the ``arr`` name points to an actual array, the
|
|
||||||
content will be added
|
|
||||||
|
|
||||||
This can be really cpu intensive, as you can imagine. Because |jedi| has to
|
|
||||||
follow **every** ``append`` and check wheter it's the right array. However this
|
|
||||||
works pretty good, because in *slow* cases, the recursion detector and other
|
|
||||||
settings will stop this process.
|
|
||||||
|
|
||||||
It is important to note that:
|
|
||||||
|
|
||||||
1. Array modfications work only in the current module.
|
|
||||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from jedi import debug
|
|
||||||
from jedi import settings
|
|
||||||
from jedi._compatibility import force_unicode, is_py3
|
from jedi._compatibility import force_unicode, is_py3
|
||||||
from jedi.inference import compiled
|
from jedi.inference import compiled
|
||||||
from jedi.inference import analysis
|
from jedi.inference import analysis
|
||||||
from jedi.inference import recursion
|
|
||||||
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
|
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
|
||||||
LazyTreeValue
|
LazyTreeValue
|
||||||
from jedi.inference.helpers import get_int_or_none, is_string, \
|
from jedi.inference.helpers import get_int_or_none, is_string, \
|
||||||
infer_call_of_leaf, reraise_getitem_errors, SimpleGetItemNotFound
|
reraise_getitem_errors, SimpleGetItemNotFound
|
||||||
from jedi.inference.utils import safe_property, to_list
|
from jedi.inference.utils import safe_property, to_list
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.filters import LazyAttributeOverwrite, publish_method
|
from jedi.inference.filters import LazyAttributeOverwrite, publish_method
|
||||||
from jedi.inference.base_value import ValueSet, Value, NO_VALUES, \
|
from jedi.inference.base_value import ValueSet, Value, NO_VALUES, \
|
||||||
ContextualizedNode, iterate_values, HelperValueMixin, sentinel, \
|
ContextualizedNode, iterate_values, sentinel, \
|
||||||
LazyValueWrapper
|
LazyValueWrapper
|
||||||
from jedi.parser_utils import get_sync_comp_fors
|
from jedi.parser_utils import get_sync_comp_fors
|
||||||
from jedi.inference.context import CompForContext
|
from jedi.inference.context import CompForContext
|
||||||
|
from jedi.inference.value.dynamic_arrays import check_array_additions
|
||||||
|
|
||||||
|
|
||||||
class IterableMixin(object):
|
class IterableMixin(object):
|
||||||
@@ -385,7 +365,7 @@ class SequenceLiteralValue(Sequence):
|
|||||||
yield LazyKnownValue(Slice(self._defining_context, None, None, None))
|
yield LazyKnownValue(Slice(self._defining_context, None, None, None))
|
||||||
else:
|
else:
|
||||||
yield LazyTreeValue(self._defining_context, node)
|
yield LazyTreeValue(self._defining_context, node)
|
||||||
for addition in _check_array_additions(self._defining_context, self):
|
for addition in check_array_additions(self._defining_context, self):
|
||||||
yield addition
|
yield addition
|
||||||
|
|
||||||
def py__len__(self):
|
def py__len__(self):
|
||||||
@@ -632,143 +612,6 @@ def unpack_tuple_to_dict(value, types, exprlist):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
def _check_array_additions(context, sequence):
|
|
||||||
""" Just a mapper function for the internal _internal_check_array_additions """
|
|
||||||
if sequence.array_type not in ('list', 'set'):
|
|
||||||
# TODO also check for dict updates
|
|
||||||
return NO_VALUES
|
|
||||||
|
|
||||||
return _internal_check_array_additions(context, sequence)
|
|
||||||
|
|
||||||
|
|
||||||
@inference_state_method_cache(default=NO_VALUES)
|
|
||||||
@debug.increase_indent
|
|
||||||
def _internal_check_array_additions(context, sequence):
|
|
||||||
"""
|
|
||||||
Checks if a `Array` has "add" (append, insert, extend) statements:
|
|
||||||
|
|
||||||
>>> a = [""]
|
|
||||||
>>> a.append(1)
|
|
||||||
"""
|
|
||||||
from jedi.inference import arguments
|
|
||||||
|
|
||||||
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
|
||||||
module_context = context.get_root_context()
|
|
||||||
if not settings.dynamic_array_additions or module_context.is_compiled():
|
|
||||||
debug.dbg('Dynamic array search aborted.', color='MAGENTA')
|
|
||||||
return NO_VALUES
|
|
||||||
|
|
||||||
def find_additions(context, arglist, add_name):
|
|
||||||
params = list(arguments.TreeArguments(context.inference_state, context, arglist).unpack())
|
|
||||||
result = set()
|
|
||||||
if add_name in ['insert']:
|
|
||||||
params = params[1:]
|
|
||||||
if add_name in ['append', 'add', 'insert']:
|
|
||||||
for key, lazy_value in params:
|
|
||||||
result.add(lazy_value)
|
|
||||||
elif add_name in ['extend', 'update']:
|
|
||||||
for key, lazy_value in params:
|
|
||||||
result |= set(lazy_value.infer().iterate())
|
|
||||||
return result
|
|
||||||
|
|
||||||
temp_param_add, settings.dynamic_params_for_other_modules = \
|
|
||||||
settings.dynamic_params_for_other_modules, False
|
|
||||||
|
|
||||||
is_list = sequence.name.string_name == 'list'
|
|
||||||
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
|
|
||||||
|
|
||||||
added_types = set()
|
|
||||||
for add_name in search_names:
|
|
||||||
try:
|
|
||||||
possible_names = module_context.tree_node.get_used_names()[add_name]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
for name in possible_names:
|
|
||||||
value_node = context.tree_node
|
|
||||||
if not (value_node.start_pos < name.start_pos < value_node.end_pos):
|
|
||||||
continue
|
|
||||||
trailer = name.parent
|
|
||||||
power = trailer.parent
|
|
||||||
trailer_pos = power.children.index(trailer)
|
|
||||||
try:
|
|
||||||
execution_trailer = power.children[trailer_pos + 1]
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if execution_trailer.type != 'trailer' \
|
|
||||||
or execution_trailer.children[0] != '(' \
|
|
||||||
or execution_trailer.children[1] == ')':
|
|
||||||
continue
|
|
||||||
|
|
||||||
random_context = context.create_context(name)
|
|
||||||
|
|
||||||
with recursion.execution_allowed(context.inference_state, power) as allowed:
|
|
||||||
if allowed:
|
|
||||||
found = infer_call_of_leaf(
|
|
||||||
random_context,
|
|
||||||
name,
|
|
||||||
cut_own_trailer=True
|
|
||||||
)
|
|
||||||
if sequence in found:
|
|
||||||
# The arrays match. Now add the results
|
|
||||||
added_types |= find_additions(
|
|
||||||
random_context,
|
|
||||||
execution_trailer.children[1],
|
|
||||||
add_name
|
|
||||||
)
|
|
||||||
|
|
||||||
# reset settings
|
|
||||||
settings.dynamic_params_for_other_modules = temp_param_add
|
|
||||||
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
|
||||||
return added_types
|
|
||||||
|
|
||||||
|
|
||||||
def get_dynamic_array_instance(instance, arguments):
|
|
||||||
"""Used for set() and list() instances."""
|
|
||||||
ai = _ArrayInstance(instance, arguments)
|
|
||||||
from jedi.inference import arguments
|
|
||||||
return arguments.ValuesArguments([ValueSet([ai])])
|
|
||||||
|
|
||||||
|
|
||||||
class _ArrayInstance(HelperValueMixin):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
|
|
||||||
This is not a proper context, because it doesn't have to be. It's not used
|
|
||||||
in the wild, it's just used within typeshed as an argument to `__init__`
|
|
||||||
for set/list and never used in any other place.
|
|
||||||
"""
|
|
||||||
def __init__(self, instance, var_args):
|
|
||||||
self.instance = instance
|
|
||||||
self.var_args = var_args
|
|
||||||
|
|
||||||
def py__class__(self):
|
|
||||||
tuple_, = self.instance.inference_state.builtins_module.py__getattribute__('tuple')
|
|
||||||
return tuple_
|
|
||||||
|
|
||||||
def py__iter__(self, contextualized_node=None):
|
|
||||||
var_args = self.var_args
|
|
||||||
try:
|
|
||||||
_, lazy_value = next(var_args.unpack())
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for lazy in lazy_value.infer().iterate():
|
|
||||||
yield lazy
|
|
||||||
|
|
||||||
from jedi.inference import arguments
|
|
||||||
if isinstance(var_args, arguments.TreeArguments):
|
|
||||||
additions = _internal_check_array_additions(var_args.context, self.instance)
|
|
||||||
for addition in additions:
|
|
||||||
yield addition
|
|
||||||
|
|
||||||
def iterate(self, contextualized_node=None, is_async=False):
|
|
||||||
return self.py__iter__(contextualized_node)
|
|
||||||
|
|
||||||
|
|
||||||
class Slice(LazyValueWrapper):
|
class Slice(LazyValueWrapper):
|
||||||
def __init__(self, python_context, start, stop, step):
|
def __init__(self, python_context, start, stop, step):
|
||||||
self.inference_state = python_context.inference_state
|
self.inference_state = python_context.inference_state
|
||||||
|
|||||||
Reference in New Issue
Block a user