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