PageRenderTime 21ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/pypy/interpreter/argument.py

https://bitbucket.org/pypy/pypy/
Python | 528 lines | 489 code | 3 blank | 36 comment | 8 complexity | ad0704cd6a3e7648ef83350fd39aa827 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. """
  2. Arguments objects.
  3. """
  4. from rpython.rlib.debug import make_sure_not_resized
  5. from rpython.rlib import jit
  6. from pypy.interpreter.error import OperationError, oefmt
  7. class Arguments(object):
  8. """
  9. Collects the arguments of a function call.
  10. Instances should be considered immutable.
  11. Some parts of this class are written in a slightly convoluted style to help
  12. the JIT. It is really crucial to get this right, because Python's argument
  13. semantics are complex, but calls occur everywhere.
  14. """
  15. ### Construction ###
  16. def __init__(self, space, args_w, keywords=None, keywords_w=None,
  17. w_stararg=None, w_starstararg=None, keyword_names_w=None):
  18. self.space = space
  19. assert isinstance(args_w, list)
  20. self.arguments_w = args_w
  21. self.keywords = keywords
  22. self.keywords_w = keywords_w
  23. self.keyword_names_w = keyword_names_w # matches the tail of .keywords
  24. if keywords is not None:
  25. assert keywords_w is not None
  26. assert len(keywords_w) == len(keywords)
  27. assert (keyword_names_w is None or
  28. len(keyword_names_w) <= len(keywords))
  29. make_sure_not_resized(self.keywords)
  30. make_sure_not_resized(self.keywords_w)
  31. make_sure_not_resized(self.arguments_w)
  32. self._combine_wrapped(w_stararg, w_starstararg)
  33. # a flag that specifies whether the JIT can unroll loops that operate
  34. # on the keywords
  35. self._jit_few_keywords = self.keywords is None or jit.isconstant(len(self.keywords))
  36. def __repr__(self):
  37. """ NOT_RPYTHON """
  38. name = self.__class__.__name__
  39. if not self.keywords:
  40. return '%s(%s)' % (name, self.arguments_w,)
  41. else:
  42. return '%s(%s, %s, %s)' % (name, self.arguments_w,
  43. self.keywords, self.keywords_w)
  44. ### Manipulation ###
  45. @jit.look_inside_iff(lambda self: self._jit_few_keywords)
  46. def unpack(self): # slowish
  47. "Return a ([w1,w2...], {'kw':w3...}) pair."
  48. kwds_w = {}
  49. if self.keywords:
  50. for i in range(len(self.keywords)):
  51. kwds_w[self.keywords[i]] = self.keywords_w[i]
  52. return self.arguments_w, kwds_w
  53. def replace_arguments(self, args_w):
  54. "Return a new Arguments with a args_w as positional arguments."
  55. return Arguments(self.space, args_w, self.keywords, self.keywords_w,
  56. keyword_names_w = self.keyword_names_w)
  57. def prepend(self, w_firstarg):
  58. "Return a new Arguments with a new argument inserted first."
  59. return self.replace_arguments([w_firstarg] + self.arguments_w)
  60. def _combine_wrapped(self, w_stararg, w_starstararg):
  61. "unpack the *arg and **kwd into arguments_w and keywords_w"
  62. if w_stararg is not None:
  63. self._combine_starargs_wrapped(w_stararg)
  64. if w_starstararg is not None:
  65. self._combine_starstarargs_wrapped(w_starstararg)
  66. def _combine_starargs_wrapped(self, w_stararg):
  67. # unpack the * arguments
  68. space = self.space
  69. try:
  70. args_w = space.fixedview(w_stararg)
  71. except OperationError as e:
  72. if e.match(space, space.w_TypeError):
  73. raise oefmt(space.w_TypeError,
  74. "argument after * must be a sequence, not %T",
  75. w_stararg)
  76. raise
  77. self.arguments_w = self.arguments_w + args_w
  78. def _combine_starstarargs_wrapped(self, w_starstararg):
  79. # unpack the ** arguments
  80. space = self.space
  81. keywords, values_w = space.view_as_kwargs(w_starstararg)
  82. if keywords is not None: # this path also taken for empty dicts
  83. if self.keywords is None:
  84. self.keywords = keywords
  85. self.keywords_w = values_w
  86. else:
  87. _check_not_duplicate_kwargs(
  88. self.space, self.keywords, keywords, values_w)
  89. self.keywords = self.keywords + keywords
  90. self.keywords_w = self.keywords_w + values_w
  91. return
  92. if space.isinstance_w(w_starstararg, space.w_dict):
  93. keys_w = space.unpackiterable(w_starstararg)
  94. else:
  95. try:
  96. w_keys = space.call_method(w_starstararg, "keys")
  97. except OperationError as e:
  98. if e.match(space, space.w_AttributeError):
  99. raise oefmt(space.w_TypeError,
  100. "argument after ** must be a mapping, not %T",
  101. w_starstararg)
  102. raise
  103. keys_w = space.unpackiterable(w_keys)
  104. keywords_w = [None] * len(keys_w)
  105. keywords = [None] * len(keys_w)
  106. _do_combine_starstarargs_wrapped(space, keys_w, w_starstararg, keywords, keywords_w, self.keywords)
  107. self.keyword_names_w = keys_w
  108. if self.keywords is None:
  109. self.keywords = keywords
  110. self.keywords_w = keywords_w
  111. else:
  112. self.keywords = self.keywords + keywords
  113. self.keywords_w = self.keywords_w + keywords_w
  114. def fixedunpack(self, argcount):
  115. """The simplest argument parsing: get the 'argcount' arguments,
  116. or raise a real ValueError if the length is wrong."""
  117. if self.keywords:
  118. raise ValueError("no keyword arguments expected")
  119. if len(self.arguments_w) > argcount:
  120. raise ValueError("too many arguments (%d expected)" % argcount)
  121. elif len(self.arguments_w) < argcount:
  122. raise ValueError("not enough arguments (%d expected)" % argcount)
  123. return self.arguments_w
  124. def firstarg(self):
  125. "Return the first argument for inspection."
  126. if self.arguments_w:
  127. return self.arguments_w[0]
  128. return None
  129. ### Parsing for function calls ###
  130. @jit.unroll_safe
  131. def _match_signature(self, w_firstarg, scope_w, signature, defaults_w=None,
  132. blindargs=0):
  133. """Parse args and kwargs according to the signature of a code object,
  134. or raise an ArgErr in case of failure.
  135. """
  136. # w_firstarg = a first argument to be inserted (e.g. self) or None
  137. # args_w = list of the normal actual parameters, wrapped
  138. # scope_w = resulting list of wrapped values
  139. #
  140. # some comments about the JIT: it assumes that signature is a constant,
  141. # so all values coming from there can be assumed constant. It assumes
  142. # that the length of the defaults_w does not vary too much.
  143. co_argcount = signature.num_argnames() # expected formal arguments, without */**
  144. # put the special w_firstarg into the scope, if it exists
  145. if w_firstarg is not None:
  146. upfront = 1
  147. if co_argcount > 0:
  148. scope_w[0] = w_firstarg
  149. else:
  150. upfront = 0
  151. args_w = self.arguments_w
  152. num_args = len(args_w)
  153. avail = num_args + upfront
  154. keywords = self.keywords
  155. num_kwds = 0
  156. if keywords is not None:
  157. num_kwds = len(keywords)
  158. # put as many positional input arguments into place as available
  159. input_argcount = upfront
  160. if input_argcount < co_argcount:
  161. take = min(num_args, co_argcount - upfront)
  162. # letting the JIT unroll this loop is safe, because take is always
  163. # smaller than co_argcount
  164. for i in range(take):
  165. scope_w[i + input_argcount] = args_w[i]
  166. input_argcount += take
  167. # collect extra positional arguments into the *vararg
  168. if signature.has_vararg():
  169. args_left = co_argcount - upfront
  170. if args_left < 0: # check required by rpython
  171. starargs_w = [w_firstarg]
  172. if num_args:
  173. starargs_w = starargs_w + args_w
  174. elif num_args > args_left:
  175. starargs_w = args_w[args_left:]
  176. else:
  177. starargs_w = []
  178. scope_w[co_argcount] = self.space.newtuple(starargs_w)
  179. elif avail > co_argcount:
  180. raise ArgErrCount(avail, num_kwds, signature, defaults_w, 0)
  181. # if a **kwargs argument is needed, create the dict
  182. w_kwds = None
  183. if signature.has_kwarg():
  184. w_kwds = self.space.newdict(kwargs=True)
  185. scope_w[co_argcount + signature.has_vararg()] = w_kwds
  186. # handle keyword arguments
  187. num_remainingkwds = 0
  188. keywords_w = self.keywords_w
  189. kwds_mapping = None
  190. if num_kwds:
  191. # kwds_mapping maps target indexes in the scope (minus input_argcount)
  192. # to positions in the keywords_w list
  193. kwds_mapping = [0] * (co_argcount - input_argcount)
  194. # initialize manually, for the JIT :-(
  195. for i in range(len(kwds_mapping)):
  196. kwds_mapping[i] = -1
  197. # match the keywords given at the call site to the argument names
  198. # the called function takes
  199. # this function must not take a scope_w, to make the scope not
  200. # escape
  201. num_remainingkwds = _match_keywords(
  202. signature, blindargs, input_argcount, keywords,
  203. kwds_mapping, self._jit_few_keywords)
  204. if num_remainingkwds:
  205. if w_kwds is not None:
  206. # collect extra keyword arguments into the **kwarg
  207. _collect_keyword_args(
  208. self.space, keywords, keywords_w, w_kwds,
  209. kwds_mapping, self.keyword_names_w, self._jit_few_keywords)
  210. else:
  211. if co_argcount == 0:
  212. raise ArgErrCount(avail, num_kwds, signature, defaults_w, 0)
  213. raise ArgErrUnknownKwds(self.space, num_remainingkwds, keywords,
  214. kwds_mapping, self.keyword_names_w)
  215. # check for missing arguments and fill them from the kwds,
  216. # or with defaults, if available
  217. missing = 0
  218. if input_argcount < co_argcount:
  219. def_first = co_argcount - (0 if defaults_w is None else len(defaults_w))
  220. j = 0
  221. kwds_index = -1
  222. for i in range(input_argcount, co_argcount):
  223. if kwds_mapping is not None:
  224. kwds_index = kwds_mapping[j]
  225. j += 1
  226. if kwds_index >= 0:
  227. scope_w[i] = keywords_w[kwds_index]
  228. continue
  229. defnum = i - def_first
  230. if defnum >= 0:
  231. scope_w[i] = defaults_w[defnum]
  232. else:
  233. missing += 1
  234. if missing:
  235. raise ArgErrCount(avail, num_kwds, signature, defaults_w, missing)
  236. def parse_into_scope(self, w_firstarg,
  237. scope_w, fnname, signature, defaults_w=None):
  238. """Parse args and kwargs to initialize a frame
  239. according to the signature of code object.
  240. Store the argumentvalues into scope_w.
  241. scope_w must be big enough for signature.
  242. """
  243. try:
  244. self._match_signature(w_firstarg,
  245. scope_w, signature, defaults_w, 0)
  246. except ArgErr as e:
  247. raise oefmt(self.space.w_TypeError, "%s() %s", fnname, e.getmsg())
  248. return signature.scope_length()
  249. def _parse(self, w_firstarg, signature, defaults_w, blindargs=0):
  250. """Parse args and kwargs according to the signature of a code object,
  251. or raise an ArgErr in case of failure.
  252. """
  253. scopelen = signature.scope_length()
  254. scope_w = [None] * scopelen
  255. self._match_signature(w_firstarg, scope_w, signature, defaults_w,
  256. blindargs)
  257. return scope_w
  258. def parse_obj(self, w_firstarg,
  259. fnname, signature, defaults_w=None, blindargs=0):
  260. """Parse args and kwargs to initialize a frame
  261. according to the signature of code object.
  262. """
  263. try:
  264. return self._parse(w_firstarg, signature, defaults_w, blindargs)
  265. except ArgErr as e:
  266. raise oefmt(self.space.w_TypeError, "%s() %s", fnname, e.getmsg())
  267. @staticmethod
  268. def frompacked(space, w_args=None, w_kwds=None):
  269. """Convenience static method to build an Arguments
  270. from a wrapped sequence and a wrapped dictionary."""
  271. return Arguments(space, [], w_stararg=w_args, w_starstararg=w_kwds)
  272. def topacked(self):
  273. """Express the Argument object as a pair of wrapped w_args, w_kwds."""
  274. space = self.space
  275. w_args = space.newtuple(self.arguments_w)
  276. w_kwds = space.newdict()
  277. if self.keywords is not None:
  278. limit = len(self.keywords)
  279. if self.keyword_names_w is not None:
  280. limit -= len(self.keyword_names_w)
  281. for i in range(len(self.keywords)):
  282. if i < limit:
  283. key = self.keywords[i]
  284. space.setitem_str(w_kwds, key, self.keywords_w[i])
  285. else:
  286. w_key = self.keyword_names_w[i - limit]
  287. space.setitem(w_kwds, w_key, self.keywords_w[i])
  288. return w_args, w_kwds
  289. # JIT helper functions
  290. # these functions contain functionality that the JIT is not always supposed to
  291. # look at. They should not get a self arguments, which makes the amount of
  292. # arguments annoying :-(
  293. @jit.look_inside_iff(lambda space, existingkeywords, keywords, keywords_w:
  294. jit.isconstant(len(keywords) and
  295. jit.isconstant(existingkeywords)))
  296. def _check_not_duplicate_kwargs(space, existingkeywords, keywords, keywords_w):
  297. # looks quadratic, but the JIT should remove all of it nicely.
  298. # Also, all the lists should be small
  299. for key in keywords:
  300. for otherkey in existingkeywords:
  301. if otherkey == key:
  302. raise oefmt(space.w_TypeError,
  303. "got multiple values for keyword argument '%s'",
  304. key)
  305. def _do_combine_starstarargs_wrapped(space, keys_w, w_starstararg, keywords,
  306. keywords_w, existingkeywords):
  307. i = 0
  308. for w_key in keys_w:
  309. try:
  310. key = space.str_w(w_key)
  311. except OperationError as e:
  312. if e.match(space, space.w_TypeError):
  313. raise oefmt(space.w_TypeError, "keywords must be strings")
  314. if e.match(space, space.w_UnicodeEncodeError):
  315. # Allow this to pass through
  316. key = None
  317. else:
  318. raise
  319. else:
  320. if existingkeywords and key in existingkeywords:
  321. raise oefmt(space.w_TypeError,
  322. "got multiple values for keyword argument '%s'",
  323. key)
  324. keywords[i] = key
  325. keywords_w[i] = space.getitem(w_starstararg, w_key)
  326. i += 1
  327. @jit.look_inside_iff(
  328. lambda signature, blindargs, input_argcount,
  329. keywords, kwds_mapping, jiton: jiton)
  330. def _match_keywords(signature, blindargs, input_argcount,
  331. keywords, kwds_mapping, _):
  332. # letting JIT unroll the loop is *only* safe if the callsite didn't
  333. # use **args because num_kwds can be arbitrarily large otherwise.
  334. num_kwds = num_remainingkwds = len(keywords)
  335. for i in range(num_kwds):
  336. name = keywords[i]
  337. # If name was not encoded as a string, it could be None. In that
  338. # case, it's definitely not going to be in the signature.
  339. if name is None:
  340. continue
  341. j = signature.find_argname(name)
  342. # if j == -1 nothing happens, because j < input_argcount and
  343. # blindargs > j
  344. if j < input_argcount:
  345. # check that no keyword argument conflicts with these. note
  346. # that for this purpose we ignore the first blindargs,
  347. # which were put into place by prepend(). This way,
  348. # keywords do not conflict with the hidden extra argument
  349. # bound by methods.
  350. if blindargs <= j:
  351. raise ArgErrMultipleValues(name)
  352. else:
  353. kwds_mapping[j - input_argcount] = i # map to the right index
  354. num_remainingkwds -= 1
  355. return num_remainingkwds
  356. @jit.look_inside_iff(
  357. lambda space, keywords, keywords_w, w_kwds, kwds_mapping,
  358. keyword_names_w, jiton: jiton)
  359. def _collect_keyword_args(space, keywords, keywords_w, w_kwds, kwds_mapping,
  360. keyword_names_w, _):
  361. limit = len(keywords)
  362. if keyword_names_w is not None:
  363. limit -= len(keyword_names_w)
  364. for i in range(len(keywords)):
  365. # again a dangerous-looking loop that either the JIT unrolls
  366. # or that is not too bad, because len(kwds_mapping) is small
  367. for j in kwds_mapping:
  368. if i == j:
  369. break
  370. else:
  371. if i < limit:
  372. space.setitem_str(w_kwds, keywords[i], keywords_w[i])
  373. else:
  374. w_key = keyword_names_w[i - limit]
  375. space.setitem(w_kwds, w_key, keywords_w[i])
  376. #
  377. # ArgErr family of exceptions raised in case of argument mismatch.
  378. # We try to give error messages following CPython's, which are very informative.
  379. #
  380. class ArgErr(Exception):
  381. def getmsg(self):
  382. raise NotImplementedError
  383. class ArgErrCount(ArgErr):
  384. def __init__(self, got_nargs, nkwds, signature,
  385. defaults_w, missing_args):
  386. self.signature = signature
  387. self.num_defaults = 0 if defaults_w is None else len(defaults_w)
  388. self.missing_args = missing_args
  389. self.num_args = got_nargs
  390. self.num_kwds = nkwds
  391. def getmsg(self):
  392. n = self.signature.num_argnames()
  393. if n == 0:
  394. msg = "takes no arguments (%d given)" % (
  395. self.num_args + self.num_kwds)
  396. else:
  397. defcount = self.num_defaults
  398. has_kwarg = self.signature.has_kwarg()
  399. num_args = self.num_args
  400. num_kwds = self.num_kwds
  401. if defcount == 0 and not self.signature.has_vararg():
  402. msg1 = "exactly"
  403. if not has_kwarg:
  404. num_args += num_kwds
  405. num_kwds = 0
  406. elif not self.missing_args:
  407. msg1 = "at most"
  408. else:
  409. msg1 = "at least"
  410. has_kwarg = False
  411. n -= defcount
  412. if n == 1:
  413. plural = ""
  414. else:
  415. plural = "s"
  416. if has_kwarg or num_kwds > 0:
  417. msg2 = " non-keyword"
  418. else:
  419. msg2 = ""
  420. msg = "takes %s %d%s argument%s (%d given)" % (
  421. msg1,
  422. n,
  423. msg2,
  424. plural,
  425. num_args)
  426. return msg
  427. class ArgErrMultipleValues(ArgErr):
  428. def __init__(self, argname):
  429. self.argname = argname
  430. def getmsg(self):
  431. msg = "got multiple values for keyword argument '%s'" % (
  432. self.argname)
  433. return msg
  434. class ArgErrUnknownKwds(ArgErr):
  435. def __init__(self, space, num_remainingkwds, keywords, kwds_mapping,
  436. keyword_names_w):
  437. name = ''
  438. self.num_kwds = num_remainingkwds
  439. if num_remainingkwds == 1:
  440. for i in range(len(keywords)):
  441. if i not in kwds_mapping:
  442. name = keywords[i]
  443. if name is None:
  444. # We'll assume it's unicode. Encode it.
  445. # Careful, I *think* it should not be possible to
  446. # get an IndexError here but you never know.
  447. try:
  448. if keyword_names_w is None:
  449. raise IndexError
  450. # note: negative-based indexing from the end
  451. w_name = keyword_names_w[i - len(keywords)]
  452. except IndexError:
  453. name = '?'
  454. else:
  455. w_enc = space.wrap(space.sys.defaultencoding)
  456. w_err = space.wrap("replace")
  457. w_name = space.call_method(w_name, "encode", w_enc,
  458. w_err)
  459. name = space.str_w(w_name)
  460. break
  461. self.kwd_name = name
  462. def getmsg(self):
  463. if self.num_kwds == 1:
  464. msg = "got an unexpected keyword argument '%s'" % (
  465. self.kwd_name)
  466. else:
  467. msg = "got %d unexpected keyword arguments" % (
  468. self.num_kwds)
  469. return msg