| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- import re
- from itertools import zip_longest
- from parso.python import tree
- from jedi import debug
- from jedi.inference.utils import PushBackIterator
- from jedi.inference import analysis
- from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
- LazyTreeValue, get_merged_lazy_value
- from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
- from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
- from jedi.inference.value import iterable
- from jedi.inference.cache import inference_state_as_method_param_cache
- def try_iter_content(types, depth=0):
- """Helper method for static analysis."""
- if depth > 10:
- # It's possible that a loop has references on itself (especially with
- # CompiledValue). Therefore don't loop infinitely.
- return
- for typ in types:
- try:
- f = typ.py__iter__
- except AttributeError:
- pass
- else:
- for lazy_value in f():
- try_iter_content(lazy_value.infer(), depth + 1)
- class ParamIssue(Exception):
- pass
- def repack_with_argument_clinic(clinic_string):
- """
- Transforms a function or method with arguments to the signature that is
- given as an argument clinic notation.
- Argument clinic is part of CPython and used for all the functions that are
- implemented in C (Python 3.7):
- str.split.__text_signature__
- # Results in: '($self, /, sep=None, maxsplit=-1)'
- """
- def decorator(func):
- def wrapper(value, arguments):
- try:
- args = tuple(iterate_argument_clinic(
- value.inference_state,
- arguments,
- clinic_string,
- ))
- except ParamIssue:
- return NO_VALUES
- else:
- return func(value, *args)
- return wrapper
- return decorator
- def iterate_argument_clinic(inference_state, arguments, clinic_string):
- """Uses a list with argument clinic information (see PEP 436)."""
- clinic_args = list(_parse_argument_clinic(clinic_string))
- iterator = PushBackIterator(arguments.unpack())
- for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
- if stars == 1:
- lazy_values = []
- for key, argument in iterator:
- if key is not None:
- iterator.push_back((key, argument))
- break
- lazy_values.append(argument)
- yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
- lazy_values
- continue
- elif stars == 2:
- raise NotImplementedError()
- key, argument = next(iterator, (None, None))
- if key is not None:
- debug.warning('Keyword arguments in argument clinic are currently not supported.')
- raise ParamIssue
- if argument is None and not optional:
- debug.warning('TypeError: %s expected at least %s arguments, got %s',
- name, len(clinic_args), i)
- raise ParamIssue
- value_set = NO_VALUES if argument is None else argument.infer()
- if not value_set and not optional:
- # For the stdlib we always want values. If we don't get them,
- # that's ok, maybe something is too hard to resolve, however,
- # we will not proceed with the type inference of that function.
- debug.warning('argument_clinic "%s" not resolvable.', name)
- raise ParamIssue
- yield value_set
- def _parse_argument_clinic(string):
- allow_kwargs = False
- optional = False
- while string:
- # Optional arguments have to begin with a bracket. And should always be
- # at the end of the arguments. This is therefore not a proper argument
- # clinic implementation. `range()` for exmple allows an optional start
- # value at the beginning.
- match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
- string = string[len(match.group(0)):]
- if not match.group(2): # A slash -> allow named arguments
- allow_kwargs = True
- continue
- optional = optional or bool(match.group(1))
- word = match.group(2)
- stars = word.count('*')
- word = word[stars:]
- yield (word, optional, allow_kwargs, stars)
- if stars:
- allow_kwargs = True
- class _AbstractArgumentsMixin:
- def unpack(self, funcdef=None):
- raise NotImplementedError
- def get_calling_nodes(self):
- return []
- class AbstractArguments(_AbstractArgumentsMixin):
- context = None
- argument_node = None
- trailer = None
- def unpack_arglist(arglist):
- if arglist is None:
- return
- if arglist.type != 'arglist' and not (
- arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
- yield 0, arglist
- return
- iterator = iter(arglist.children)
- for child in iterator:
- if child == ',':
- continue
- elif child in ('*', '**'):
- c = next(iterator, None)
- assert c is not None
- yield len(child.value), c
- elif child.type == 'argument' and \
- child.children[0] in ('*', '**'):
- assert len(child.children) == 2
- yield len(child.children[0].value), child.children[1]
- else:
- yield 0, child
- class TreeArguments(AbstractArguments):
- def __init__(self, inference_state, context, argument_node, trailer=None):
- """
- :param argument_node: May be an argument_node or a list of nodes.
- """
- self.argument_node = argument_node
- self.context = context
- self._inference_state = inference_state
- self.trailer = trailer # Can be None, e.g. in a class definition.
- @classmethod
- @inference_state_as_method_param_cache()
- def create_cached(cls, *args, **kwargs):
- return cls(*args, **kwargs)
- def unpack(self, funcdef=None):
- named_args = []
- for star_count, el in unpack_arglist(self.argument_node):
- if star_count == 1:
- arrays = self.context.infer_node(el)
- iterators = [_iterate_star_args(self.context, a, el, funcdef)
- for a in arrays]
- for values in list(zip_longest(*iterators)):
- yield None, get_merged_lazy_value(
- [v for v in values if v is not None]
- )
- elif star_count == 2:
- arrays = self.context.infer_node(el)
- for dct in arrays:
- yield from _star_star_dict(self.context, dct, el, funcdef)
- else:
- if el.type == 'argument':
- c = el.children
- if len(c) == 3: # Keyword argument.
- named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
- else: # Generator comprehension.
- # Include the brackets with the parent.
- sync_comp_for = el.children[1]
- if sync_comp_for.type == 'comp_for':
- sync_comp_for = sync_comp_for.children[1]
- comp = iterable.GeneratorComprehension(
- self._inference_state,
- defining_context=self.context,
- sync_comp_for_node=sync_comp_for,
- entry_node=el.children[0],
- )
- yield None, LazyKnownValue(comp)
- else:
- yield None, LazyTreeValue(self.context, el)
- # Reordering arguments is necessary, because star args sometimes appear
- # after named argument, but in the actual order it's prepended.
- yield from named_args
- def _as_tree_tuple_objects(self):
- for star_count, argument in unpack_arglist(self.argument_node):
- default = None
- if argument.type == 'argument':
- if len(argument.children) == 3: # Keyword argument.
- argument, default = argument.children[::2]
- yield argument, default, star_count
- def iter_calling_names_with_star(self):
- for name, default, star_count in self._as_tree_tuple_objects():
- # TODO this function is a bit strange. probably refactor?
- if not star_count or not isinstance(name, tree.Name):
- continue
- yield TreeNameDefinition(self.context, name)
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
- def get_calling_nodes(self):
- old_arguments_list = []
- arguments = self
- while arguments not in old_arguments_list:
- if not isinstance(arguments, TreeArguments):
- break
- old_arguments_list.append(arguments)
- for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
- names = calling_name.goto()
- if len(names) != 1:
- break
- if isinstance(names[0], AnonymousParamName):
- # Dynamic parameters should not have calling nodes, because
- # they are dynamic and extremely random.
- return []
- if not isinstance(names[0], ParamName):
- break
- executed_param_name = names[0].get_executed_param_name()
- arguments = executed_param_name.arguments
- break
- if arguments.argument_node is not None:
- return [ContextualizedNode(arguments.context, arguments.argument_node)]
- if arguments.trailer is not None:
- return [ContextualizedNode(arguments.context, arguments.trailer)]
- return []
- class ValuesArguments(AbstractArguments):
- def __init__(self, values_list):
- self._values_list = values_list
- def unpack(self, funcdef=None):
- for values in self._values_list:
- yield None, LazyKnownValues(values)
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self._values_list)
- class TreeArgumentsWrapper(_AbstractArgumentsMixin):
- def __init__(self, arguments):
- self._wrapped_arguments = arguments
- @property
- def context(self):
- return self._wrapped_arguments.context
- @property
- def argument_node(self):
- return self._wrapped_arguments.argument_node
- @property
- def trailer(self):
- return self._wrapped_arguments.trailer
- def unpack(self, func=None):
- raise NotImplementedError
- def get_calling_nodes(self):
- return self._wrapped_arguments.get_calling_nodes()
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)
- def _iterate_star_args(context, array, input_node, funcdef=None):
- if not array.py__getattribute__('__iter__'):
- if funcdef is not None:
- # TODO this funcdef should not be needed.
- m = "TypeError: %s() argument after * must be a sequence, not %s" \
- % (funcdef.name.value, array)
- analysis.add(context, 'type-error-star', input_node, message=m)
- try:
- iter_ = array.py__iter__
- except AttributeError:
- pass
- else:
- yield from iter_()
- def _star_star_dict(context, array, input_node, funcdef):
- from jedi.inference.value.instance import CompiledInstance
- if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
- # For now ignore this case. In the future add proper iterators and just
- # make one call without crazy isinstance checks.
- return {}
- elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
- return array.exact_key_items()
- else:
- if funcdef is not None:
- m = "TypeError: %s argument after ** must be a mapping, not %s" \
- % (funcdef.name.value, array)
- analysis.add(context, 'type-error-star-star', input_node, message=m)
- return {}
|