]>
Commit | Line | Data |
---|---|---|
1 | from collections import defaultdict | |
2 | from inspect import Parameter | |
3 | ||
4 | from jedi import debug | |
5 | from jedi.inference.utils import PushBackIterator | |
6 | from jedi.inference import analysis | |
7 | from jedi.inference.lazy_value import LazyKnownValue, \ | |
8 | LazyTreeValue, LazyUnknownValue | |
9 | from jedi.inference.value import iterable | |
10 | from jedi.inference.names import ParamName | |
11 | ||
12 | ||
13 | def _add_argument_issue(error_name, lazy_value, message): | |
14 | if isinstance(lazy_value, LazyTreeValue): | |
15 | node = lazy_value.data | |
16 | if node.parent.type == 'argument': | |
17 | node = node.parent | |
18 | return analysis.add(lazy_value.context, error_name, node, message) | |
19 | ||
20 | ||
21 | class ExecutedParamName(ParamName): | |
22 | def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False): | |
23 | super().__init__(function_value, param_node.name, arguments=arguments) | |
24 | self._lazy_value = lazy_value | |
25 | self._is_default = is_default | |
26 | ||
27 | def infer(self): | |
28 | return self._lazy_value.infer() | |
29 | ||
30 | def matches_signature(self): | |
31 | if self._is_default: | |
32 | return True | |
33 | argument_values = self.infer().py__class__() | |
34 | if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): | |
35 | return True | |
36 | annotations = self.infer_annotation(execute_annotation=False) | |
37 | if not annotations: | |
38 | # If we cannot infer annotations - or there aren't any - pretend | |
39 | # that the signature matches. | |
40 | return True | |
41 | matches = any(c1.is_sub_class_of(c2) | |
42 | for c1 in argument_values | |
43 | for c2 in annotations.gather_annotation_classes()) | |
44 | debug.dbg("param compare %s: %s <=> %s", | |
45 | matches, argument_values, annotations, color='BLUE') | |
46 | return matches | |
47 | ||
48 | def __repr__(self): | |
49 | return '<%s: %s>' % (self.__class__.__name__, self.string_name) | |
50 | ||
51 | ||
52 | def get_executed_param_names_and_issues(function_value, arguments): | |
53 | """ | |
54 | Return a tuple of: | |
55 | - a list of `ExecutedParamName`s corresponding to the arguments of the | |
56 | function execution `function_value`, containing the inferred value of | |
57 | those arguments (whether explicit or default) | |
58 | - a list of the issues encountered while building that list | |
59 | ||
60 | For example, given: | |
61 | ``` | |
62 | def foo(a, b, c=None, d='d'): ... | |
63 | ||
64 | foo(42, c='c') | |
65 | ``` | |
66 | ||
67 | Then for the execution of `foo`, this will return a tuple containing: | |
68 | - a list with entries for each parameter a, b, c & d; the entries for a, | |
69 | c, & d will have their values (42, 'c' and 'd' respectively) included. | |
70 | - a list with a single entry about the lack of a value for `b` | |
71 | """ | |
72 | def too_many_args(argument): | |
73 | m = _error_argument_count(funcdef, len(unpacked_va)) | |
74 | # Just report an error for the first param that is not needed (like | |
75 | # cPython). | |
76 | if arguments.get_calling_nodes(): | |
77 | # There might not be a valid calling node so check for that first. | |
78 | issues.append( | |
79 | _add_argument_issue( | |
80 | 'type-error-too-many-arguments', | |
81 | argument, | |
82 | message=m | |
83 | ) | |
84 | ) | |
85 | else: | |
86 | issues.append(None) | |
87 | debug.warning('non-public warning: %s', m) | |
88 | ||
89 | issues = [] # List[Optional[analysis issue]] | |
90 | result_params = [] | |
91 | param_dict = {} | |
92 | funcdef = function_value.tree_node | |
93 | # Default params are part of the value where the function was defined. | |
94 | # This means that they might have access on class variables that the | |
95 | # function itself doesn't have. | |
96 | default_param_context = function_value.get_default_param_context() | |
97 | ||
98 | for param in funcdef.get_params(): | |
99 | param_dict[param.name.value] = param | |
100 | unpacked_va = list(arguments.unpack(funcdef)) | |
101 | var_arg_iterator = PushBackIterator(iter(unpacked_va)) | |
102 | ||
103 | non_matching_keys = defaultdict(lambda: []) | |
104 | keys_used = {} | |
105 | keys_only = False | |
106 | had_multiple_value_error = False | |
107 | for param in funcdef.get_params(): | |
108 | # The value and key can both be null. There, the defaults apply. | |
109 | # args / kwargs will just be empty arrays / dicts, respectively. | |
110 | # Wrong value count is just ignored. If you try to test cases that are | |
111 | # not allowed in Python, Jedi will maybe not show any completions. | |
112 | is_default = False | |
113 | key, argument = next(var_arg_iterator, (None, None)) | |
114 | while key is not None: | |
115 | keys_only = True | |
116 | try: | |
117 | key_param = param_dict[key] | |
118 | except KeyError: | |
119 | non_matching_keys[key] = argument | |
120 | else: | |
121 | if key in keys_used: | |
122 | had_multiple_value_error = True | |
123 | m = ("TypeError: %s() got multiple values for keyword argument '%s'." | |
124 | % (funcdef.name, key)) | |
125 | for contextualized_node in arguments.get_calling_nodes(): | |
126 | issues.append( | |
127 | analysis.add(contextualized_node.context, | |
128 | 'type-error-multiple-values', | |
129 | contextualized_node.node, message=m) | |
130 | ) | |
131 | else: | |
132 | keys_used[key] = ExecutedParamName( | |
133 | function_value, arguments, key_param, argument) | |
134 | key, argument = next(var_arg_iterator, (None, None)) | |
135 | ||
136 | try: | |
137 | result_params.append(keys_used[param.name.value]) | |
138 | continue | |
139 | except KeyError: | |
140 | pass | |
141 | ||
142 | if param.star_count == 1: | |
143 | # *args param | |
144 | lazy_value_list = [] | |
145 | if argument is not None: | |
146 | lazy_value_list.append(argument) | |
147 | for key, argument in var_arg_iterator: | |
148 | # Iterate until a key argument is found. | |
149 | if key: | |
150 | var_arg_iterator.push_back((key, argument)) | |
151 | break | |
152 | lazy_value_list.append(argument) | |
153 | seq = iterable.FakeTuple(function_value.inference_state, lazy_value_list) | |
154 | result_arg = LazyKnownValue(seq) | |
155 | elif param.star_count == 2: | |
156 | if argument is not None: | |
157 | too_many_args(argument) | |
158 | # **kwargs param | |
159 | dct = iterable.FakeDict(function_value.inference_state, dict(non_matching_keys)) | |
160 | result_arg = LazyKnownValue(dct) | |
161 | non_matching_keys = {} | |
162 | else: | |
163 | # normal param | |
164 | if argument is None: | |
165 | # No value: Return an empty container | |
166 | if param.default is None: | |
167 | result_arg = LazyUnknownValue() | |
168 | if not keys_only: | |
169 | for contextualized_node in arguments.get_calling_nodes(): | |
170 | m = _error_argument_count(funcdef, len(unpacked_va)) | |
171 | issues.append( | |
172 | analysis.add( | |
173 | contextualized_node.context, | |
174 | 'type-error-too-few-arguments', | |
175 | contextualized_node.node, | |
176 | message=m, | |
177 | ) | |
178 | ) | |
179 | else: | |
180 | result_arg = LazyTreeValue(default_param_context, param.default) | |
181 | is_default = True | |
182 | else: | |
183 | result_arg = argument | |
184 | ||
185 | result_params.append(ExecutedParamName( | |
186 | function_value, arguments, param, result_arg, is_default=is_default | |
187 | )) | |
188 | if not isinstance(result_arg, LazyUnknownValue): | |
189 | keys_used[param.name.value] = result_params[-1] | |
190 | ||
191 | if keys_only: | |
192 | # All arguments should be handed over to the next function. It's not | |
193 | # about the values inside, it's about the names. Jedi needs to now that | |
194 | # there's nothing to find for certain names. | |
195 | for k in set(param_dict) - set(keys_used): | |
196 | param = param_dict[k] | |
197 | ||
198 | if not (non_matching_keys or had_multiple_value_error | |
199 | or param.star_count or param.default): | |
200 | # add a warning only if there's not another one. | |
201 | for contextualized_node in arguments.get_calling_nodes(): | |
202 | m = _error_argument_count(funcdef, len(unpacked_va)) | |
203 | issues.append( | |
204 | analysis.add(contextualized_node.context, | |
205 | 'type-error-too-few-arguments', | |
206 | contextualized_node.node, message=m) | |
207 | ) | |
208 | ||
209 | for key, lazy_value in non_matching_keys.items(): | |
210 | m = "TypeError: %s() got an unexpected keyword argument '%s'." \ | |
211 | % (funcdef.name, key) | |
212 | issues.append( | |
213 | _add_argument_issue( | |
214 | 'type-error-keyword-argument', | |
215 | lazy_value, | |
216 | message=m | |
217 | ) | |
218 | ) | |
219 | ||
220 | remaining_arguments = list(var_arg_iterator) | |
221 | if remaining_arguments: | |
222 | first_key, lazy_value = remaining_arguments[0] | |
223 | too_many_args(lazy_value) | |
224 | return result_params, issues | |
225 | ||
226 | ||
227 | def get_executed_param_names(function_value, arguments): | |
228 | """ | |
229 | Return a list of `ExecutedParamName`s corresponding to the arguments of the | |
230 | function execution `function_value`, containing the inferred value of those | |
231 | arguments (whether explicit or default). Any issues building this list (for | |
232 | example required arguments which are missing in the invocation) are ignored. | |
233 | ||
234 | For example, given: | |
235 | ``` | |
236 | def foo(a, b, c=None, d='d'): ... | |
237 | ||
238 | foo(42, c='c') | |
239 | ``` | |
240 | ||
241 | Then for the execution of `foo`, this will return a list containing entries | |
242 | for each parameter a, b, c & d; the entries for a, c, & d will have their | |
243 | values (42, 'c' and 'd' respectively) included. | |
244 | """ | |
245 | return get_executed_param_names_and_issues(function_value, arguments)[0] | |
246 | ||
247 | ||
248 | def _error_argument_count(funcdef, actual_count): | |
249 | params = funcdef.get_params() | |
250 | default_arguments = sum(1 for p in params if p.default or p.star_count) | |
251 | ||
252 | if default_arguments == 0: | |
253 | before = 'exactly ' | |
254 | else: | |
255 | before = 'from %s to ' % (len(params) - default_arguments) | |
256 | return ('TypeError: %s() takes %s%s arguments (%s given).' | |
257 | % (funcdef.name, before, len(params), actual_count)) |