You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			201 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			201 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
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 whether 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 modifications 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, \
 | 
						|
    ValueWrapper
 | 
						|
from jedi.inference.lazy_value import LazyKnownValues
 | 
						|
from jedi.inference.helpers import infer_call_of_leaf
 | 
						|
from jedi.inference.cache import inference_state_method_cache
 | 
						|
 | 
						|
_sentinel = object()
 | 
						|
 | 
						|
 | 
						|
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 = _DynamicArrayAdditions(instance, arguments)
 | 
						|
    from jedi.inference import arguments
 | 
						|
    return arguments.ValuesArguments([ValueSet([ai])])
 | 
						|
 | 
						|
 | 
						|
class _DynamicArrayAdditions(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, arguments):
 | 
						|
        self._instance = instance
 | 
						|
        self._arguments = arguments
 | 
						|
 | 
						|
    def py__class__(self):
 | 
						|
        tuple_, = self._instance.inference_state.builtins_module.py__getattribute__('tuple')
 | 
						|
        return tuple_
 | 
						|
 | 
						|
    def py__iter__(self, contextualized_node=None):
 | 
						|
        arguments = self._arguments
 | 
						|
        try:
 | 
						|
            _, lazy_value = next(arguments.unpack())
 | 
						|
        except StopIteration:
 | 
						|
            pass
 | 
						|
        else:
 | 
						|
            yield from lazy_value.infer().iterate()
 | 
						|
 | 
						|
        from jedi.inference.arguments import TreeArguments
 | 
						|
        if isinstance(arguments, TreeArguments):
 | 
						|
            additions = _internal_check_array_additions(arguments.context, self._instance)
 | 
						|
            yield from additions
 | 
						|
 | 
						|
    def iterate(self, contextualized_node=None, is_async=False):
 | 
						|
        return self.py__iter__(contextualized_node)
 | 
						|
 | 
						|
 | 
						|
class _Modification(ValueWrapper):
 | 
						|
    def __init__(self, wrapped_value, assigned_values, contextualized_key):
 | 
						|
        super().__init__(wrapped_value)
 | 
						|
        self._assigned_values = assigned_values
 | 
						|
        self._contextualized_key = contextualized_key
 | 
						|
 | 
						|
    def py__getitem__(self, *args, **kwargs):
 | 
						|
        return self._wrapped_value.py__getitem__(*args, **kwargs) | self._assigned_values
 | 
						|
 | 
						|
    def py__simple_getitem__(self, index):
 | 
						|
        actual = [
 | 
						|
            v.get_safe_value(_sentinel)
 | 
						|
            for v in self._contextualized_key.infer()
 | 
						|
        ]
 | 
						|
        if index in actual:
 | 
						|
            return self._assigned_values
 | 
						|
        return self._wrapped_value.py__simple_getitem__(index)
 | 
						|
 | 
						|
 | 
						|
class DictModification(_Modification):
 | 
						|
    def py__iter__(self, contextualized_node=None):
 | 
						|
        yield from self._wrapped_value.py__iter__(contextualized_node)
 | 
						|
        yield self._contextualized_key
 | 
						|
 | 
						|
    def get_key_values(self):
 | 
						|
        return self._wrapped_value.get_key_values() | self._contextualized_key.infer()
 | 
						|
 | 
						|
 | 
						|
class ListModification(_Modification):
 | 
						|
    def py__iter__(self, contextualized_node=None):
 | 
						|
        yield from self._wrapped_value.py__iter__(contextualized_node)
 | 
						|
        yield LazyKnownValues(self._assigned_values)
 |