1
0
forked from VimPlug/jedi

Move the dynamic arrays code

This commit is contained in:
Dave Halter
2019-08-25 17:07:50 +02:00
parent d31ca7e9f0
commit b7febc1960
3 changed files with 170 additions and 162 deletions

View 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)

View File

@@ -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)

View File

@@ -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