1# Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com 2# 3# Part of "Nuitka", an optimizing Python compiler that is compatible and 4# integrates with CPython, but also works on its own. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18""" This module maintains the parameter specification classes. 19 20These are used for function, lambdas, generators. They are also a factory 21for the respective variable objects. One of the difficulty of Python and 22its parameter parsing is that they are allowed to be nested like this: 23 24(a,b), c 25 26Much like in assignments, which are very similar to parameters, except 27that parameters may also be assigned from a dictionary, they are no less 28flexible. 29 30""" 31 32from nuitka import Variables 33from nuitka.PythonVersions import python_version 34from nuitka.utils.InstanceCounters import ( 35 counted_del, 36 counted_init, 37 isCountingInstances, 38) 39 40 41class TooManyArguments(Exception): 42 def __init__(self, real_exception): 43 Exception.__init__(self) 44 45 self.real_exception = real_exception 46 47 def getRealException(self): 48 return self.real_exception 49 50 51class ParameterSpec(object): 52 # These got many attributes, in part duplicating name and instance of 53 # variables, pylint: disable=too-many-instance-attributes 54 55 __slots__ = ( 56 "name", 57 "owner", 58 "normal_args", 59 "normal_variables", 60 "list_star_arg", 61 "dict_star_arg", 62 "list_star_variable", 63 "dict_star_variable", 64 "default_count", 65 "kw_only_args", 66 "kw_only_variables", 67 "pos_only_args", 68 "pos_only_variables", 69 ) 70 71 @counted_init 72 def __init__( 73 self, 74 ps_name, 75 ps_normal_args, 76 ps_pos_only_args, 77 ps_kw_only_args, 78 ps_list_star_arg, 79 ps_dict_star_arg, 80 ps_default_count, 81 ): 82 if type(ps_normal_args) is str: 83 if ps_normal_args == "": 84 ps_normal_args = () 85 else: 86 ps_normal_args = ps_normal_args.split(",") 87 88 if type(ps_kw_only_args) is str: 89 if ps_kw_only_args == "": 90 ps_kw_only_args = () 91 else: 92 ps_kw_only_args = ps_kw_only_args.split(",") 93 94 assert None not in ps_normal_args 95 96 self.owner = None 97 98 self.name = ps_name 99 self.normal_args = tuple(ps_normal_args) 100 self.normal_variables = None 101 102 assert ( 103 ps_list_star_arg is None or type(ps_list_star_arg) is str 104 ), ps_list_star_arg 105 assert ( 106 ps_dict_star_arg is None or type(ps_dict_star_arg) is str 107 ), ps_dict_star_arg 108 109 self.list_star_arg = ps_list_star_arg if ps_list_star_arg else None 110 self.dict_star_arg = ps_dict_star_arg if ps_dict_star_arg else None 111 112 self.list_star_variable = None 113 self.dict_star_variable = None 114 115 self.default_count = ps_default_count 116 117 self.kw_only_args = tuple(ps_kw_only_args) 118 self.kw_only_variables = None 119 120 self.pos_only_args = tuple(ps_pos_only_args) 121 self.pos_only_variables = None 122 123 if isCountingInstances(): 124 __del__ = counted_del() 125 126 def makeClone(self): 127 return ParameterSpec( 128 ps_name=self.name, 129 ps_normal_args=self.normal_args, 130 ps_pos_only_args=self.pos_only_args, 131 ps_kw_only_args=self.kw_only_args, 132 ps_list_star_arg=self.list_star_arg, 133 ps_dict_star_arg=self.dict_star_arg, 134 ps_default_count=self.default_count, 135 ) 136 137 def getDetails(self): 138 return { 139 "ps_name": self.name, 140 "ps_normal_args": ",".join(self.normal_args), 141 "ps_pos_only_args": self.pos_only_args, 142 "ps_kw_only_args": ",".join(self.kw_only_args), 143 "ps_list_star_arg": self.list_star_arg 144 if self.list_star_arg is not None 145 else "", 146 "ps_dict_star_arg": self.dict_star_arg 147 if self.dict_star_arg is not None 148 else "", 149 "ps_default_count": self.default_count, 150 } 151 152 def checkParametersValid(self): 153 arg_names = self.getParameterNames() 154 155 # Check for duplicate arguments, could happen. 156 for arg_name in arg_names: 157 if arg_names.count(arg_name) != 1: 158 return "duplicate argument '%s' in function definition" % arg_name 159 160 return None 161 162 def __repr__(self): 163 parts = [str(normal_arg) for normal_arg in self.pos_only_args] 164 if parts: 165 parts.append("/") 166 167 parts += [str(normal_arg) for normal_arg in self.normal_args] 168 169 if self.list_star_arg is not None: 170 parts.append("*%s" % self.list_star_arg) 171 172 if self.dict_star_variable is not None: 173 parts.append("**%s" % self.dict_star_variable) 174 175 if parts: 176 return "<ParameterSpec '%s'>" % ",".join(parts) 177 else: 178 return "<NoParameters>" 179 180 def setOwner(self, owner): 181 if self.owner is not None: 182 return 183 184 self.owner = owner 185 self.normal_variables = [] 186 187 for normal_arg in self.normal_args: 188 if type(normal_arg) is str: 189 normal_variable = Variables.ParameterVariable( 190 owner=self.owner, parameter_name=normal_arg 191 ) 192 else: 193 assert False, normal_arg 194 195 self.normal_variables.append(normal_variable) 196 197 if self.list_star_arg: 198 self.list_star_variable = Variables.ParameterVariable( 199 owner=owner, parameter_name=self.list_star_arg 200 ) 201 else: 202 self.list_star_variable = None 203 204 if self.dict_star_arg: 205 self.dict_star_variable = Variables.ParameterVariable( 206 owner=owner, parameter_name=self.dict_star_arg 207 ) 208 else: 209 self.dict_star_variable = None 210 211 self.kw_only_variables = [ 212 Variables.ParameterVariable(owner=self.owner, parameter_name=kw_only_arg) 213 for kw_only_arg in self.kw_only_args 214 ] 215 216 self.pos_only_variables = [ 217 Variables.ParameterVariable(owner=self.owner, parameter_name=pos_only_arg) 218 for pos_only_arg in self.pos_only_args 219 ] 220 221 def getDefaultCount(self): 222 return self.default_count 223 224 def hasDefaultParameters(self): 225 return self.getDefaultCount() > 0 226 227 def getTopLevelVariables(self): 228 return self.pos_only_variables + self.normal_variables + self.kw_only_variables 229 230 def getAllVariables(self): 231 result = list(self.pos_only_variables) 232 result += self.normal_variables 233 result += self.kw_only_variables 234 235 if self.list_star_variable is not None: 236 result.append(self.list_star_variable) 237 238 if self.dict_star_variable is not None: 239 result.append(self.dict_star_variable) 240 241 return result 242 243 def getParameterNames(self): 244 result = list(self.pos_only_args + self.normal_args) 245 246 result += self.kw_only_args 247 248 if self.list_star_arg is not None: 249 result.append(self.list_star_arg) 250 251 if self.dict_star_arg is not None: 252 result.append(self.dict_star_arg) 253 254 return result 255 256 def getStarListArgumentName(self): 257 return self.list_star_arg 258 259 def getListStarArgVariable(self): 260 return self.list_star_variable 261 262 def getStarDictArgumentName(self): 263 return self.dict_star_arg 264 265 def getDictStarArgVariable(self): 266 return self.dict_star_variable 267 268 def getKwOnlyVariables(self): 269 return self.kw_only_variables 270 271 def allowsKeywords(self): 272 # Abstract method, pylint: disable=no-self-use 273 return True 274 275 def getKeywordRefusalText(self): 276 return "%s() takes no keyword arguments" % self.name 277 278 def getArgumentNames(self): 279 return self.pos_only_args + self.normal_args 280 281 def getArgumentCount(self): 282 return len(self.normal_args) + len(self.pos_only_args) 283 284 def getKwOnlyParameterNames(self): 285 return self.kw_only_args 286 287 def getKwOnlyParameterCount(self): 288 return len(self.kw_only_args) 289 290 def getPosOnlyParameterCount(self): 291 return len(self.pos_only_args) 292 293 294def matchCall( 295 func_name, 296 args, 297 kw_only_args, 298 star_list_arg, 299 star_dict_arg, 300 num_defaults, 301 num_posonly, 302 positional, 303 pairs, 304 improved=False, 305): 306 """Match a call arguments to a signature. 307 308 Args: 309 func_name - Name of the function being matched, used to construct exception texts. 310 args - normal argument names 311 kw_only_args - keyword only argument names (Python3) 312 star_list_arg - name of star list argument if any 313 star_dict_arg - name of star dict argument if any 314 num_defaults - amount of arguments that have default values 315 num_posonly - amount of arguments that must be given by position 316 positional - tuple of argument values given for simulated call 317 pairs - tuple of pairs arg argument name and argument values 318 improved - (bool) should we give better errors than CPython or not. 319 Returns: 320 Dictionary of argument name to value mappings 321 Notes: 322 Based loosely on "inspect.getcallargs" with corrections. 323 """ 324 325 # This is of incredible code complexity, but there really is no other way to 326 # express this with less statements, branches, or variables. 327 # pylint: disable=too-many-branches,too-many-locals,too-many-statements 328 329 assert type(positional) is tuple, positional 330 assert type(pairs) in (tuple, list), pairs 331 332 # Make a copy, we are going to modify it. 333 pairs = list(pairs) 334 335 result = {} 336 337 assigned_tuple_params = [] 338 339 def assign(arg, value): 340 if type(arg) is str: 341 # Normal case: 342 result[arg] = value 343 else: 344 # Tuple argument case: 345 346 assigned_tuple_params.append(arg) 347 value = iter(value.getIterationValues()) 348 349 for i, subarg in enumerate(arg): 350 try: 351 subvalue = next(value) 352 except StopIteration: 353 raise TooManyArguments( 354 ValueError( 355 "need more than %d %s to unpack" 356 % (i, "values" if i > 1 else "value") 357 ) 358 ) 359 360 # Recurse into tuple argument values, could be more tuples. 361 assign(subarg, subvalue) 362 363 # Check that not too many values we provided. 364 try: 365 next(value) 366 except StopIteration: 367 pass 368 else: 369 raise TooManyArguments(ValueError("too many values to unpack")) 370 371 def isAssigned(arg): 372 if type(arg) is str: 373 return arg in result 374 375 return arg in assigned_tuple_params 376 377 num_pos = len(positional) 378 num_total = num_pos + len(pairs) 379 num_args = len(args) 380 381 for arg, value in zip(args, positional): 382 assign(arg, value) 383 384 # Python3 does this check earlier. 385 if python_version >= 0x300 and not star_dict_arg: 386 for pair in pairs: 387 try: 388 arg_index = (args + kw_only_args).index(pair[0]) 389 except ValueError: 390 if improved or python_version >= 0x370: 391 message = "'%s' is an invalid keyword argument for %s()" % ( 392 pair[0], 393 func_name, 394 ) 395 else: 396 message = ( 397 "'%s' is an invalid keyword argument for this function" 398 % pair[0] 399 ) 400 401 raise TooManyArguments(TypeError(message)) 402 else: 403 if arg_index < num_posonly: 404 message = "'%s' is an invalid keyword argument for %s()" % ( 405 pair[0], 406 func_name, 407 ) 408 409 raise TooManyArguments(TypeError(message)) 410 411 if star_list_arg: 412 if num_pos > num_args: 413 assign(star_list_arg, positional[-(num_pos - num_args) :]) 414 else: 415 assign(star_list_arg, ()) 416 elif 0 < num_args < num_total: 417 # Special case for no default values. 418 if num_defaults == 0: 419 # Special cases text for one argument. 420 if num_args == 1: 421 raise TooManyArguments( 422 TypeError( 423 "%s() takes exactly one argument (%d given)" 424 % (func_name, num_total) 425 ) 426 ) 427 428 raise TooManyArguments( 429 TypeError( 430 "%s expected %d arguments, got %d" 431 % (func_name, num_args, num_total) 432 ) 433 ) 434 435 raise TooManyArguments( 436 TypeError( 437 "%s() takes at most %d %s (%d given)" 438 % ( 439 func_name, 440 num_args, 441 "argument" if num_args == 1 else "arguments", 442 num_total, 443 ) 444 ) 445 ) 446 elif num_args == 0 and num_total: 447 if star_dict_arg: 448 if num_pos: 449 # Could use num_pos, but Python also uses num_total. 450 raise TooManyArguments( 451 TypeError( 452 "%s() takes exactly 0 arguments (%d given)" 453 % (func_name, num_total) 454 ) 455 ) 456 else: 457 raise TooManyArguments( 458 TypeError("%s() takes no arguments (%d given)" % (func_name, num_total)) 459 ) 460 461 named_argument_names = [pair[0] for pair in pairs] 462 463 for arg in args + kw_only_args: 464 if type(arg) is str and arg in named_argument_names: 465 if isAssigned(arg): 466 raise TooManyArguments( 467 TypeError( 468 "%s() got multiple values for keyword argument '%s'" 469 % (func_name, arg) 470 ) 471 ) 472 473 new_pairs = [] 474 475 for pair in pairs: 476 if arg == pair[0]: 477 assign(arg, pair[1]) 478 else: 479 new_pairs.append(pair) 480 481 assert len(new_pairs) == len(pairs) - 1 482 483 pairs = new_pairs 484 485 # Fill in any missing values with the None to indicate "default". 486 if num_defaults > 0: 487 for arg in (kw_only_args + args)[-num_defaults:]: 488 if not isAssigned(arg): 489 assign(arg, None) 490 491 if star_dict_arg: 492 assign(star_dict_arg, pairs) 493 elif pairs: 494 unexpected = next(iter(dict(pairs))) 495 496 if improved: 497 message = "%s() got an unexpected keyword argument '%s'" % ( 498 func_name, 499 unexpected, 500 ) 501 else: 502 message = ( 503 "'%s' is an invalid keyword argument for this function" % unexpected 504 ) 505 506 raise TooManyArguments(TypeError(message)) 507 508 unassigned = num_args - len([arg for arg in args if isAssigned(arg)]) 509 510 if unassigned: 511 num_required = num_args - num_defaults 512 513 # Special case required arguments. 514 if num_required > 0 or improved: 515 if num_defaults == 0 and num_args != 1: 516 raise TooManyArguments( 517 TypeError( 518 "%s expected %d arguments, got %d" 519 % (func_name, num_args, num_total) 520 ) 521 ) 522 523 if num_required == 1: 524 arg_desc = "1 argument" if python_version < 0x350 else "one argument" 525 else: 526 arg_desc = "%d arguments" % num_required 527 528 raise TooManyArguments( 529 TypeError( 530 "%s() takes %s %s (%d given)" 531 % ( 532 func_name, 533 "at least" if num_defaults > 0 else "exactly", 534 arg_desc, 535 num_total, 536 ) 537 ) 538 ) 539 540 raise TooManyArguments( 541 TypeError( 542 "%s expected %s%s, got %d" 543 % ( 544 func_name, 545 ("at least " if python_version < 0x300 else "") 546 if num_defaults > 0 547 else "exactly ", 548 "%d arguments" % num_required, 549 num_total, 550 ) 551 ) 552 ) 553 554 unassigned = len(kw_only_args) - len( 555 [arg for arg in kw_only_args if isAssigned(arg)] 556 ) 557 if unassigned: 558 raise TooManyArguments( 559 TypeError( 560 "%s missing %d required keyword-only argument%s: %s" 561 % ( 562 func_name, 563 unassigned, 564 "s" if unassigned > 1 else "", 565 " and ".join( 566 "'%s'" 567 % [arg.getName() for arg in kw_only_args if not isAssigned(arg)] 568 ), 569 ) 570 ) 571 ) 572 573 return result 574