1import re
2from itertools import zip_longest
3
4from parso.python import tree
5
6from jedi import debug
7from jedi.inference.utils import PushBackIterator
8from jedi.inference import analysis
9from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
10    LazyTreeValue, get_merged_lazy_value
11from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
12from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
13from jedi.inference.value import iterable
14from jedi.inference.cache import inference_state_as_method_param_cache
15
16
17def try_iter_content(types, depth=0):
18    """Helper method for static analysis."""
19    if depth > 10:
20        # It's possible that a loop has references on itself (especially with
21        # CompiledValue). Therefore don't loop infinitely.
22        return
23
24    for typ in types:
25        try:
26            f = typ.py__iter__
27        except AttributeError:
28            pass
29        else:
30            for lazy_value in f():
31                try_iter_content(lazy_value.infer(), depth + 1)
32
33
34class ParamIssue(Exception):
35    pass
36
37
38def repack_with_argument_clinic(clinic_string):
39    """
40    Transforms a function or method with arguments to the signature that is
41    given as an argument clinic notation.
42
43    Argument clinic is part of CPython and used for all the functions that are
44    implemented in C (Python 3.7):
45
46        str.split.__text_signature__
47        # Results in: '($self, /, sep=None, maxsplit=-1)'
48    """
49    def decorator(func):
50        def wrapper(value, arguments):
51            try:
52                args = tuple(iterate_argument_clinic(
53                    value.inference_state,
54                    arguments,
55                    clinic_string,
56                ))
57            except ParamIssue:
58                return NO_VALUES
59            else:
60                return func(value, *args)
61
62        return wrapper
63    return decorator
64
65
66def iterate_argument_clinic(inference_state, arguments, clinic_string):
67    """Uses a list with argument clinic information (see PEP 436)."""
68    clinic_args = list(_parse_argument_clinic(clinic_string))
69
70    iterator = PushBackIterator(arguments.unpack())
71    for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
72        if stars == 1:
73            lazy_values = []
74            for key, argument in iterator:
75                if key is not None:
76                    iterator.push_back((key, argument))
77                    break
78
79                lazy_values.append(argument)
80            yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
81            lazy_values
82            continue
83        elif stars == 2:
84            raise NotImplementedError()
85        key, argument = next(iterator, (None, None))
86        if key is not None:
87            debug.warning('Keyword arguments in argument clinic are currently not supported.')
88            raise ParamIssue
89        if argument is None and not optional:
90            debug.warning('TypeError: %s expected at least %s arguments, got %s',
91                          name, len(clinic_args), i)
92            raise ParamIssue
93
94        value_set = NO_VALUES if argument is None else argument.infer()
95
96        if not value_set and not optional:
97            # For the stdlib we always want values. If we don't get them,
98            # that's ok, maybe something is too hard to resolve, however,
99            # we will not proceed with the type inference of that function.
100            debug.warning('argument_clinic "%s" not resolvable.', name)
101            raise ParamIssue
102        yield value_set
103
104
105def _parse_argument_clinic(string):
106    allow_kwargs = False
107    optional = False
108    while string:
109        # Optional arguments have to begin with a bracket. And should always be
110        # at the end of the arguments. This is therefore not a proper argument
111        # clinic implementation. `range()` for exmple allows an optional start
112        # value at the beginning.
113        match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
114        string = string[len(match.group(0)):]
115        if not match.group(2):  # A slash -> allow named arguments
116            allow_kwargs = True
117            continue
118        optional = optional or bool(match.group(1))
119        word = match.group(2)
120        stars = word.count('*')
121        word = word[stars:]
122        yield (word, optional, allow_kwargs, stars)
123        if stars:
124            allow_kwargs = True
125
126
127class _AbstractArgumentsMixin:
128    def unpack(self, funcdef=None):
129        raise NotImplementedError
130
131    def get_calling_nodes(self):
132        return []
133
134
135class AbstractArguments(_AbstractArgumentsMixin):
136    context = None
137    argument_node = None
138    trailer = None
139
140
141def unpack_arglist(arglist):
142    if arglist is None:
143        return
144
145    if arglist.type != 'arglist' and not (
146            arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
147        yield 0, arglist
148        return
149
150    iterator = iter(arglist.children)
151    for child in iterator:
152        if child == ',':
153            continue
154        elif child in ('*', '**'):
155            c = next(iterator, None)
156            assert c is not None
157            yield len(child.value), c
158        elif child.type == 'argument' and \
159                child.children[0] in ('*', '**'):
160            assert len(child.children) == 2
161            yield len(child.children[0].value), child.children[1]
162        else:
163            yield 0, child
164
165
166class TreeArguments(AbstractArguments):
167    def __init__(self, inference_state, context, argument_node, trailer=None):
168        """
169        :param argument_node: May be an argument_node or a list of nodes.
170        """
171        self.argument_node = argument_node
172        self.context = context
173        self._inference_state = inference_state
174        self.trailer = trailer  # Can be None, e.g. in a class definition.
175
176    @classmethod
177    @inference_state_as_method_param_cache()
178    def create_cached(cls, *args, **kwargs):
179        return cls(*args, **kwargs)
180
181    def unpack(self, funcdef=None):
182        named_args = []
183        for star_count, el in unpack_arglist(self.argument_node):
184            if star_count == 1:
185                arrays = self.context.infer_node(el)
186                iterators = [_iterate_star_args(self.context, a, el, funcdef)
187                             for a in arrays]
188                for values in list(zip_longest(*iterators)):
189                    yield None, get_merged_lazy_value(
190                        [v for v in values if v is not None]
191                    )
192            elif star_count == 2:
193                arrays = self.context.infer_node(el)
194                for dct in arrays:
195                    yield from _star_star_dict(self.context, dct, el, funcdef)
196            else:
197                if el.type == 'argument':
198                    c = el.children
199                    if len(c) == 3:  # Keyword argument.
200                        named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
201                    else:  # Generator comprehension.
202                        # Include the brackets with the parent.
203                        sync_comp_for = el.children[1]
204                        if sync_comp_for.type == 'comp_for':
205                            sync_comp_for = sync_comp_for.children[1]
206                        comp = iterable.GeneratorComprehension(
207                            self._inference_state,
208                            defining_context=self.context,
209                            sync_comp_for_node=sync_comp_for,
210                            entry_node=el.children[0],
211                        )
212                        yield None, LazyKnownValue(comp)
213                else:
214                    yield None, LazyTreeValue(self.context, el)
215
216        # Reordering arguments is necessary, because star args sometimes appear
217        # after named argument, but in the actual order it's prepended.
218        yield from named_args
219
220    def _as_tree_tuple_objects(self):
221        for star_count, argument in unpack_arglist(self.argument_node):
222            default = None
223            if argument.type == 'argument':
224                if len(argument.children) == 3:  # Keyword argument.
225                    argument, default = argument.children[::2]
226            yield argument, default, star_count
227
228    def iter_calling_names_with_star(self):
229        for name, default, star_count in self._as_tree_tuple_objects():
230            # TODO this function is a bit strange. probably refactor?
231            if not star_count or not isinstance(name, tree.Name):
232                continue
233
234            yield TreeNameDefinition(self.context, name)
235
236    def __repr__(self):
237        return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
238
239    def get_calling_nodes(self):
240        old_arguments_list = []
241        arguments = self
242
243        while arguments not in old_arguments_list:
244            if not isinstance(arguments, TreeArguments):
245                break
246
247            old_arguments_list.append(arguments)
248            for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
249                names = calling_name.goto()
250                if len(names) != 1:
251                    break
252                if isinstance(names[0], AnonymousParamName):
253                    # Dynamic parameters should not have calling nodes, because
254                    # they are dynamic and extremely random.
255                    return []
256                if not isinstance(names[0], ParamName):
257                    break
258                executed_param_name = names[0].get_executed_param_name()
259                arguments = executed_param_name.arguments
260                break
261
262        if arguments.argument_node is not None:
263            return [ContextualizedNode(arguments.context, arguments.argument_node)]
264        if arguments.trailer is not None:
265            return [ContextualizedNode(arguments.context, arguments.trailer)]
266        return []
267
268
269class ValuesArguments(AbstractArguments):
270    def __init__(self, values_list):
271        self._values_list = values_list
272
273    def unpack(self, funcdef=None):
274        for values in self._values_list:
275            yield None, LazyKnownValues(values)
276
277    def __repr__(self):
278        return '<%s: %s>' % (self.__class__.__name__, self._values_list)
279
280
281class TreeArgumentsWrapper(_AbstractArgumentsMixin):
282    def __init__(self, arguments):
283        self._wrapped_arguments = arguments
284
285    @property
286    def context(self):
287        return self._wrapped_arguments.context
288
289    @property
290    def argument_node(self):
291        return self._wrapped_arguments.argument_node
292
293    @property
294    def trailer(self):
295        return self._wrapped_arguments.trailer
296
297    def unpack(self, func=None):
298        raise NotImplementedError
299
300    def get_calling_nodes(self):
301        return self._wrapped_arguments.get_calling_nodes()
302
303    def __repr__(self):
304        return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)
305
306
307def _iterate_star_args(context, array, input_node, funcdef=None):
308    if not array.py__getattribute__('__iter__'):
309        if funcdef is not None:
310            # TODO this funcdef should not be needed.
311            m = "TypeError: %s() argument after * must be a sequence, not %s" \
312                % (funcdef.name.value, array)
313            analysis.add(context, 'type-error-star', input_node, message=m)
314    try:
315        iter_ = array.py__iter__
316    except AttributeError:
317        pass
318    else:
319        yield from iter_()
320
321
322def _star_star_dict(context, array, input_node, funcdef):
323    from jedi.inference.value.instance import CompiledInstance
324    if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
325        # For now ignore this case. In the future add proper iterators and just
326        # make one call without crazy isinstance checks.
327        return {}
328    elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
329        return array.exact_key_items()
330    else:
331        if funcdef is not None:
332            m = "TypeError: %s argument after ** must be a mapping, not %s" \
333                % (funcdef.name.value, array)
334            analysis.add(context, 'type-error-star-star', input_node, message=m)
335        return {}
336