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