]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | This module is responsible for inferring *args and **kwargs for signatures. | |
3 | ||
4 | This means for example in this case:: | |
5 | ||
6 | def foo(a, b, c): ... | |
7 | ||
8 | def bar(*args): | |
9 | return foo(1, *args) | |
10 | ||
11 | The signature here for bar should be `bar(b, c)` instead of bar(*args). | |
12 | """ | |
13 | from inspect import Parameter | |
14 | ||
15 | from parso import tree | |
16 | ||
17 | from jedi.inference.utils import to_list | |
18 | from jedi.inference.names import ParamNameWrapper | |
19 | from jedi.inference.helpers import is_big_annoying_library | |
20 | ||
21 | ||
22 | def _iter_nodes_for_param(param_name): | |
23 | from parso.python.tree import search_ancestor | |
24 | from jedi.inference.arguments import TreeArguments | |
25 | ||
26 | execution_context = param_name.parent_context | |
27 | # Walk up the parso tree to get the FunctionNode we want. We use the parso | |
28 | # tree rather than going via the execution context so that we're agnostic of | |
29 | # the specific scope we're evaluating within (i.e: module or function, | |
30 | # etc.). | |
31 | function_node = tree.search_ancestor(param_name.tree_name, 'funcdef', 'lambdef') | |
32 | module_node = function_node.get_root_node() | |
33 | start = function_node.children[-1].start_pos | |
34 | end = function_node.children[-1].end_pos | |
35 | for name in module_node.get_used_names().get(param_name.string_name): | |
36 | if start <= name.start_pos < end: | |
37 | # Is used in the function | |
38 | argument = name.parent | |
39 | if argument.type == 'argument' \ | |
40 | and argument.children[0] == '*' * param_name.star_count: | |
41 | trailer = search_ancestor(argument, 'trailer') | |
42 | if trailer is not None: # Make sure we're in a function | |
43 | context = execution_context.create_context(trailer) | |
44 | if _goes_to_param_name(param_name, context, name): | |
45 | values = _to_callables(context, trailer) | |
46 | ||
47 | args = TreeArguments.create_cached( | |
48 | execution_context.inference_state, | |
49 | context=context, | |
50 | argument_node=trailer.children[1], | |
51 | trailer=trailer, | |
52 | ) | |
53 | for c in values: | |
54 | yield c, args | |
55 | ||
56 | ||
57 | def _goes_to_param_name(param_name, context, potential_name): | |
58 | if potential_name.type != 'name': | |
59 | return False | |
60 | from jedi.inference.names import TreeNameDefinition | |
61 | found = TreeNameDefinition(context, potential_name).goto() | |
62 | return any(param_name.parent_context == p.parent_context | |
63 | and param_name.start_pos == p.start_pos | |
64 | for p in found) | |
65 | ||
66 | ||
67 | def _to_callables(context, trailer): | |
68 | from jedi.inference.syntax_tree import infer_trailer | |
69 | ||
70 | atom_expr = trailer.parent | |
71 | index = atom_expr.children[0] == 'await' | |
72 | # Infer atom first | |
73 | values = context.infer_node(atom_expr.children[index]) | |
74 | for trailer2 in atom_expr.children[index + 1:]: | |
75 | if trailer == trailer2: | |
76 | break | |
77 | values = infer_trailer(context, values, trailer2) | |
78 | return values | |
79 | ||
80 | ||
81 | def _remove_given_params(arguments, param_names): | |
82 | count = 0 | |
83 | used_keys = set() | |
84 | for key, _ in arguments.unpack(): | |
85 | if key is None: | |
86 | count += 1 | |
87 | else: | |
88 | used_keys.add(key) | |
89 | ||
90 | for p in param_names: | |
91 | if count and p.maybe_positional_argument(): | |
92 | count -= 1 | |
93 | continue | |
94 | if p.string_name in used_keys and p.maybe_keyword_argument(): | |
95 | continue | |
96 | yield p | |
97 | ||
98 | ||
99 | @to_list | |
100 | def process_params(param_names, star_count=3): # default means both * and ** | |
101 | if param_names: | |
102 | if is_big_annoying_library(param_names[0].parent_context): | |
103 | # At first this feature can look innocent, but it does a lot of | |
104 | # type inference in some cases, so we just ditch it. | |
105 | yield from param_names | |
106 | return | |
107 | ||
108 | used_names = set() | |
109 | arg_callables = [] | |
110 | kwarg_callables = [] | |
111 | ||
112 | kw_only_names = [] | |
113 | kwarg_names = [] | |
114 | arg_names = [] | |
115 | original_arg_name = None | |
116 | original_kwarg_name = None | |
117 | for p in param_names: | |
118 | kind = p.get_kind() | |
119 | if kind == Parameter.VAR_POSITIONAL: | |
120 | if star_count & 1: | |
121 | arg_callables = _iter_nodes_for_param(p) | |
122 | original_arg_name = p | |
123 | elif p.get_kind() == Parameter.VAR_KEYWORD: | |
124 | if star_count & 2: | |
125 | kwarg_callables = list(_iter_nodes_for_param(p)) | |
126 | original_kwarg_name = p | |
127 | elif kind == Parameter.KEYWORD_ONLY: | |
128 | if star_count & 2: | |
129 | kw_only_names.append(p) | |
130 | elif kind == Parameter.POSITIONAL_ONLY: | |
131 | if star_count & 1: | |
132 | yield p | |
133 | else: | |
134 | if star_count == 1: | |
135 | yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY) | |
136 | elif star_count == 2: | |
137 | kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY)) | |
138 | else: | |
139 | used_names.add(p.string_name) | |
140 | yield p | |
141 | ||
142 | # First process *args | |
143 | longest_param_names = () | |
144 | found_arg_signature = False | |
145 | found_kwarg_signature = False | |
146 | for func_and_argument in arg_callables: | |
147 | func, arguments = func_and_argument | |
148 | new_star_count = star_count | |
149 | if func_and_argument in kwarg_callables: | |
150 | kwarg_callables.remove(func_and_argument) | |
151 | else: | |
152 | new_star_count = 1 | |
153 | ||
154 | for signature in func.get_signatures(): | |
155 | found_arg_signature = True | |
156 | if new_star_count == 3: | |
157 | found_kwarg_signature = True | |
158 | args_for_this_func = [] | |
159 | for p in process_params( | |
160 | list(_remove_given_params( | |
161 | arguments, | |
162 | signature.get_param_names(resolve_stars=False) | |
163 | )), new_star_count): | |
164 | if p.get_kind() == Parameter.VAR_KEYWORD: | |
165 | kwarg_names.append(p) | |
166 | elif p.get_kind() == Parameter.VAR_POSITIONAL: | |
167 | arg_names.append(p) | |
168 | elif p.get_kind() == Parameter.KEYWORD_ONLY: | |
169 | kw_only_names.append(p) | |
170 | else: | |
171 | args_for_this_func.append(p) | |
172 | if len(args_for_this_func) > len(longest_param_names): | |
173 | longest_param_names = args_for_this_func | |
174 | ||
175 | for p in longest_param_names: | |
176 | if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL: | |
177 | yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY) | |
178 | else: | |
179 | if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD: | |
180 | used_names.add(p.string_name) | |
181 | yield p | |
182 | ||
183 | if not found_arg_signature and original_arg_name is not None: | |
184 | yield original_arg_name | |
185 | elif arg_names: | |
186 | yield arg_names[0] | |
187 | ||
188 | # Then process **kwargs | |
189 | for func, arguments in kwarg_callables: | |
190 | for signature in func.get_signatures(): | |
191 | found_kwarg_signature = True | |
192 | for p in process_params( | |
193 | list(_remove_given_params( | |
194 | arguments, | |
195 | signature.get_param_names(resolve_stars=False) | |
196 | )), star_count=2): | |
197 | if p.get_kind() == Parameter.VAR_KEYWORD: | |
198 | kwarg_names.append(p) | |
199 | elif p.get_kind() == Parameter.KEYWORD_ONLY: | |
200 | kw_only_names.append(p) | |
201 | ||
202 | for p in kw_only_names: | |
203 | if p.string_name in used_names: | |
204 | continue | |
205 | yield p | |
206 | used_names.add(p.string_name) | |
207 | ||
208 | if not found_kwarg_signature and original_kwarg_name is not None: | |
209 | yield original_kwarg_name | |
210 | elif kwarg_names: | |
211 | yield kwarg_names[0] | |
212 | ||
213 | ||
214 | class ParamNameFixedKind(ParamNameWrapper): | |
215 | def __init__(self, param_name, new_kind): | |
216 | super().__init__(param_name) | |
217 | self._new_kind = new_kind | |
218 | ||
219 | def get_kind(self): | |
220 | return self._new_kind |