[pypy-commit] pypy py3.5: hg merge py3k

arigo pypy.commits at gmail.com
Sat Aug 20 17:17:18 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.5
Changeset: r86364:7d8da1e4bd2c
Date: 2016-08-20 23:11 +0200
http://bitbucket.org/pypy/pypy/changeset/7d8da1e4bd2c/

Log:	hg merge py3k

diff --git a/pypy/interpreter/eval.py b/pypy/interpreter/eval.py
--- a/pypy/interpreter/eval.py
+++ b/pypy/interpreter/eval.py
@@ -40,9 +40,6 @@
         and possibly more locals."""
         return self.signature().getallvarnames()
 
-    def getformalargcount(self):
-        return self.signature().scope_length()
-
     def getdocstring(self, space):
         return space.w_None
 
diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py
--- a/pypy/interpreter/function.py
+++ b/pypy/interpreter/function.py
@@ -38,7 +38,9 @@
                           'name?',
                           'w_kw_defs?']
 
-    def __init__(self, space, code, w_globals=None, defs_w=[], w_kw_defs=None,
+    w_kw_defs = None
+
+    def __init__(self, space, code, w_globals=None, defs_w=[], kw_defs_w=None,
                  closure=None, w_ann=None, forcename=None, qualname=None):
         self.space = space
         self.name = forcename or code.co_name
@@ -48,10 +50,12 @@
         self.w_func_globals = w_globals  # the globals dictionary
         self.closure = closure    # normally, list of Cell instances or None
         self.defs_w = defs_w
-        self.w_kw_defs = w_kw_defs
         self.w_func_dict = None # filled out below if needed
         self.w_module = None
         self.w_ann = w_ann
+        #
+        if kw_defs_w is not None:
+            self.init_kwdefaults_dict(kw_defs_w)
 
     def __repr__(self):
         # return "function %s.%s" % (self.space, self.name)
@@ -379,14 +383,29 @@
 
     def fset_func_kwdefaults(self, space, w_new):
         if space.is_w(w_new, space.w_None):
-            w_new = None
-        elif not space.isinstance_w(w_new, space.w_dict):
-            raise oefmt(space.w_TypeError, "__kwdefaults__ must be a dict")
-        self.w_kw_defs = w_new
+            self.w_kw_defs = None
+        else:
+            if not space.isinstance_w(w_new, space.w_dict):
+                raise oefmt(space.w_TypeError, "__kwdefaults__ must be a dict")
+            w_instance = self.init_kwdefaults_dict()
+            w_instance.setdict(space, w_new)
+            self.w_kw_defs = w_instance.getdict(space)
 
     def fdel_func_kwdefaults(self, space):
         self.w_kw_defs = None
 
+    def init_kwdefaults_dict(self, kw_defs_w=[]):
+        # use the mapdict logic to get at least not-too-bad JIT code
+        # from function calls with default values of kwonly arguments
+        space = self.space
+        w_class = space.fromcache(KwDefsClassCache).w_class
+        w_instance = space.call_function(w_class)
+        for w_name, w_value in kw_defs_w:
+            attr = space.unicode_w(w_name).encode('utf-8')
+            w_instance.setdictvalue(space, attr, w_value)
+        self.w_kw_defs = w_instance.getdict(space)
+        return w_instance
+
     def fget_func_doc(self, space):
         if self.w_doc is None:
             self.w_doc = self.code.getdocstring(space)
@@ -663,10 +682,12 @@
     def __init__(self, func):
         assert isinstance(func, Function)
         Function.__init__(self, func.space, func.code, func.w_func_globals,
-                          func.defs_w, None, func.closure, None, func.name)
+                          func.defs_w, None, func.closure,
+                          None, func.name)
         self.w_doc = func.w_doc
         self.w_func_dict = func.w_func_dict
         self.w_module = func.w_module
+        self.w_kw_defs = func.w_kw_defs
 
     def descr_builtinfunction__new__(space, w_subtype):
         raise oefmt(space.w_TypeError,
@@ -684,3 +705,12 @@
     else:
         code = None
     return isinstance(code, BuiltinCode)
+
+
+class KwDefsClassCache:
+    def __init__(self, space):
+        self.w_class = space.appexec([], """():
+            class KwDefs:
+                pass
+            return KwDefs
+        """)
diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py
--- a/pypy/interpreter/gateway.py
+++ b/pypy/interpreter/gateway.py
@@ -28,6 +28,8 @@
 from rpython.rlib.rarithmetic import r_longlong, r_int, r_ulonglong, r_uint
 from rpython.tool.sourcetools import func_with_new_name, compile2
 
+NO_DEFAULT = object()
+
 
 # internal non-translatable parts:
 class SignatureBuilder(object):
@@ -44,12 +46,21 @@
         self.argnames = argnames
         self.varargname = varargname
         self.kwargname = kwargname
+        self.kwonlyargnames = None
 
     def append(self, argname):
-        self.argnames.append(argname)
+        if self.kwonlyargnames is None:
+            self.argnames.append(argname)
+        else:
+            self.kwonlyargnames.append(argname)
+
+    def marker_kwonly(self):
+        assert self.kwonlyargnames is None
+        self.kwonlyargnames = []
 
     def signature(self):
-        return Signature(self.argnames, self.varargname, self.kwargname)
+        return Signature(self.argnames, self.varargname, self.kwargname,
+                         self.kwonlyargnames)
 
 #________________________________________________________________
 
@@ -66,13 +77,6 @@
         """NOT_RPYTHON"""
         raise NotImplementedError
 
-def kwonly(arg_unwrapper):
-    """Mark argument as keyword-only.
-
-    XXX: has no actual effect for now.
-    """
-    return arg_unwrapper
-
 
 class UnwrapSpecRecipe(object):
     "NOT_RPYTHON"
@@ -229,6 +233,11 @@
         name = int_unwrapping_space_method(typ)
         self.checked_space_method(name, app_sig)
 
+    def visit_kwonly(self, _, app_sig):
+        argname = self.orig_arg()
+        assert argname == '__kwonly__'
+        app_sig.marker_kwonly()
+
 
 class UnwrapSpec_EmitRun(UnwrapSpecEmit):
 
@@ -316,6 +325,9 @@
     def visit_truncatedint_w(self, typ):
         self.run_args.append("space.truncatedint_w(%s)" % (self.scopenext(),))
 
+    def visit_kwonly(self, typ):
+        self.run_args.append("None")
+
     def _make_unwrap_activation_class(self, unwrap_spec, cache={}):
         try:
             key = tuple(unwrap_spec)
@@ -468,6 +480,9 @@
     def visit_truncatedint_w(self, typ):
         self.unwrap.append("space.truncatedint_w(%s)" % (self.nextarg(),))
 
+    def visit_kwonly(self, typ):
+        raise FastFuncNotSupported
+
     def make_fastfunc(unwrap_spec, func):
         unwrap_info = UnwrapSpec_FastFunc_Unwrap()
         unwrap_info.apply_over(unwrap_spec)
@@ -563,6 +578,8 @@
                 unwrap_spec.append('args_w')
             elif argname.startswith('w_'):
                 unwrap_spec.append(W_Root)
+            elif argname == '__kwonly__':
+                unwrap_spec.append('kwonly')
             else:
                 unwrap_spec.append(None)
 
@@ -616,6 +633,8 @@
         argnames = sig.argnames
         varargname = sig.varargname
         kwargname = sig.kwargname
+        if sig.kwonlyargnames:
+            import pdb; pdb.set_trace()
         self._argnames = argnames
 
         if unwrap_spec is None:
@@ -950,64 +969,71 @@
         self.name = app_name
         self.as_classmethod = as_classmethod
 
-        if not f.func_defaults:
-            self._staticdefs = []
-        else:
-            argnames = self._code._argnames
-            defaults = f.func_defaults
-            self._staticdefs = zip(argnames[-len(defaults):], defaults)
+        argnames = self._code._argnames
+        defaults = f.func_defaults or ()
+        self._staticdefs = dict(zip(
+            argnames[len(argnames) - len(defaults):], defaults))
+
         return self
 
     def _getdefaults(self, space):
         "NOT_RPYTHON"
-        defs_w = []
-        unwrap_spec = self._code._unwrap_spec[-len(self._staticdefs):]
-        for i, (name, defaultval) in enumerate(self._staticdefs):
+        alldefs_w = {}
+        assert len(self._code._argnames) == len(self._code._unwrap_spec)
+        for name, spec in zip(self._code._argnames, self._code._unwrap_spec):
+            if name == '__kwonly__':
+                continue
+
+            defaultval = self._staticdefs.get(name, NO_DEFAULT)
+            w_def = Ellipsis
             if name.startswith('w_'):
-                assert defaultval is None, (
+                assert defaultval in (NO_DEFAULT, None), (
                     "%s: default value for '%s' can only be None, got %r; "
                     "use unwrap_spec(...=WrappedDefault(default))" % (
                     self._code.identifier, name, defaultval))
-                defs_w.append(None)
-            elif name != '__args__' and name != 'args_w':
-                spec = unwrap_spec[i]
-                if isinstance(defaultval, str) and spec not in [str]:
-                    defs_w.append(space.newbytes(defaultval))
-                else:
-                    defs_w.append(space.wrap(defaultval))
-        if self._code._unwrap_spec:
-            UNDEFINED = object()
-            alldefs_w = [UNDEFINED] * len(self._code.sig.argnames)
-            if defs_w:
-                alldefs_w[-len(defs_w):] = defs_w
-            code = self._code
-            assert isinstance(code._unwrap_spec, (list, tuple))
-            assert isinstance(code._argnames, list)
-            assert len(code._unwrap_spec) == len(code._argnames)
-            for i in range(len(code._unwrap_spec)-1, -1, -1):
-                spec = code._unwrap_spec[i]
-                argname = code._argnames[i]
-                if isinstance(spec, tuple) and spec[0] is W_Root:
-                    assert False, "use WrappedDefault"
-                if isinstance(spec, WrappedDefault):
-                    default_value = spec.default_value
-                    if isinstance(default_value, str):
-                        w_default = space.newbytes(default_value)
-                    else:
-                        w_default = space.wrap(default_value)
-                    assert isinstance(w_default, W_Root)
-                    assert argname.startswith('w_')
-                    argname = argname[2:]
-                    j = self._code.sig.argnames.index(argname)
-                    assert alldefs_w[j] in (UNDEFINED, None)
-                    alldefs_w[j] = w_default
-            first_defined = 0
-            while (first_defined < len(alldefs_w) and
-                   alldefs_w[first_defined] is UNDEFINED):
-                first_defined += 1
-            defs_w = alldefs_w[first_defined:]
-            assert UNDEFINED not in defs_w
-        return defs_w
+                if defaultval is None:
+                    w_def = None
+
+            if isinstance(spec, tuple) and spec[0] is W_Root:
+                assert False, "use WrappedDefault"
+            elif isinstance(spec, WrappedDefault):
+                assert name.startswith('w_')
+                defaultval = spec.default_value
+                w_def = Ellipsis
+
+            if defaultval is not NO_DEFAULT:
+                if name != '__args__' and name != 'args_w':
+                    if w_def is Ellipsis:
+                        if isinstance(defaultval, str) and spec not in [str]:
+                            w_def = space.newbytes(defaultval)
+                        else:
+                            w_def = space.wrap(defaultval)
+                    if name.startswith('w_'):
+                        name = name[2:]
+                    alldefs_w[name] = w_def
+        #
+        # Here, 'alldefs_w' maps some argnames to their wrapped default
+        # value.  We return two lists:
+        #  - a list of defaults for positional arguments, which covers
+        #    some suffix of the sig.argnames list
+        #  - a list of pairs (w_name, w_def) for kwonly arguments
+        #
+        sig = self._code.sig
+        first_defined = 0
+        while (first_defined < len(sig.argnames) and
+               sig.argnames[first_defined] not in alldefs_w):
+            first_defined += 1
+        defs_w = [alldefs_w.pop(name) for name in sig.argnames[first_defined:]]
+
+        kw_defs_w = None
+        if alldefs_w:
+            kw_defs_w = []
+            for name, w_def in sorted(alldefs_w.items()):
+                assert name in sig.kwonlyargnames
+                w_name = space.newunicode(name.decode('utf-8'))
+                kw_defs_w.append((w_name, w_def))
+
+        return defs_w, kw_defs_w
 
     # lazy binding to space
 
@@ -1027,9 +1053,10 @@
     def build(cache, gateway):
         "NOT_RPYTHON"
         space = cache.space
-        defs = gateway._getdefaults(space) # needs to be implemented by subclass
+        defs_w, kw_defs_w = gateway._getdefaults(space)
         code = gateway._code
-        fn = FunctionWithFixedCode(space, code, None, defs, forcename=gateway.name)
+        fn = FunctionWithFixedCode(space, code, None, defs_w, kw_defs_w,
+                                   forcename=gateway.name)
         if not space.config.translating:
             fn.add_to_table()
         if gateway.as_classmethod:
@@ -1167,15 +1194,15 @@
             source = source[source.find('\n') + 1:].lstrip()
         assert source.startswith("def "), "can only transform functions"
         source = source[4:]
-        import __future__
-        if flags & __future__.CO_FUTURE_DIVISION:
-            prefix += "from __future__ import division\n"
-        if flags & __future__.CO_FUTURE_ABSOLUTE_IMPORT:
-            prefix += "from __future__ import absolute_import\n"
-        if flags & __future__.CO_FUTURE_PRINT_FUNCTION:
-            prefix += "from __future__ import print_function\n"
-        if flags & __future__.CO_FUTURE_UNICODE_LITERALS:
-            prefix += "from __future__ import unicode_literals\n"
+        # The following flags have no effect any more in app-level code
+        # (i.e. they are always on anyway), and have been removed:
+        #    CO_FUTURE_DIVISION
+        #    CO_FUTURE_ABSOLUTE_IMPORT
+        #    CO_FUTURE_PRINT_FUNCTION
+        #    CO_FUTURE_UNICODE_LITERALS
+        # Original code was, for each of these flags:
+        #    if flags & __future__.CO_xxx:
+        #        prefix += "from __future__ import yyy\n"
     p = source.find('(')
     assert p >= 0
     funcname = source[:p].strip()
diff --git a/pypy/interpreter/mixedmodule.py b/pypy/interpreter/mixedmodule.py
--- a/pypy/interpreter/mixedmodule.py
+++ b/pypy/interpreter/mixedmodule.py
@@ -19,6 +19,7 @@
         """ NOT_RPYTHON """
         Module.__init__(self, space, w_name)
         self.lazy = True
+        self.lazy_initial_values_w = {}
         self.__class__.buildloaders()
         self.loaders = self.loaders.copy()    # copy from the class to the inst
         self.submodules_w = []
@@ -57,22 +58,11 @@
             if not self.lazy and self.w_initialdict is None:
                 self.save_module_content_for_future_reload()
 
-    def save_module_content_for_future_reload(self, save_all=False):
-        # Because setdictvalue is unable to immediately load all attributes
-        # (due to an importlib bootstrapping problem), this method needs to be
-        # able to support saving the content of a module's dict without
-        # requiring that the entire dict already be loaded. To support that
-        # properly, when updating the dict, we must be careful to never
-        # overwrite the value of a key already in w_initialdict. (So as to avoid
-        # overriding the builtin value with a user-provided value)
-        if self.space.is_none(self.w_initialdict) or save_all:
-            self.w_initialdict = self.space.call_method(self.w_dict, 'copy')
-        else:
-            w_items = self.space.call_method(self.w_dict, 'items')
-            for w_item in self.space.iteriterable(w_items):
-                w_key, w_value = self.space.fixedview(w_item, expected_length=2)
-                if not self.space.contains_w(self.w_initialdict, w_key):
-                    self.space.setitem(self.w_initialdict, w_key, w_value)
+    def save_module_content_for_future_reload(self):
+        # Save the current dictionary in w_initialdict, for future
+        # reloads.  This forces the dictionary if needed.
+        w_dict = self.getdict(self.space)
+        self.w_initialdict = self.space.call_method(w_dict, 'copy')
 
     @classmethod
     def get_applevel_name(cls):
@@ -101,9 +91,13 @@
         return w_value
 
     def setdictvalue(self, space, attr, w_value):
-        if self.lazy:
-            self._load_lazily(space, attr)
-            self.save_module_content_for_future_reload()
+        if self.lazy and attr not in self.lazy_initial_values_w:
+            # in lazy mode, the first time an attribute changes,
+            # we save away the old (initial) value.  This allows
+            # a future getdict() call to build the correct
+            # self.w_initialdict, containing the initial value.
+            w_initial_value = self._load_lazily(space, attr)
+            self.lazy_initial_values_w[attr] = w_initial_value
         space.setitem_str(self.w_dict, attr, w_value)
         return True
 
@@ -137,13 +131,29 @@
 
     def getdict(self, space):
         if self.lazy:
-            for name in self.loaders:
-                w_value = self.get(name)
-                space.setitem(self.w_dict, space.new_interned_str(name), w_value)
-            self.lazy = False
-            self.save_module_content_for_future_reload()
+            self._force_lazy_dict_now()
         return self.w_dict
 
+    def _force_lazy_dict_now(self):
+        # Force the dictionary by calling all lazy loaders now.
+        # This also saves in self.w_initialdict a copy of all the
+        # initial values, including if they have already been
+        # modified by setdictvalue().
+        space = self.space
+        for name in self.loaders:
+            w_value = self.get(name)
+            space.setitem(self.w_dict, space.new_interned_str(name), w_value)
+        self.lazy = False
+        self.save_module_content_for_future_reload()
+        for key, w_initial_value in self.lazy_initial_values_w.items():
+            w_key = space.new_interned_str(key)
+            if w_initial_value is not None:
+                space.setitem(self.w_initialdict, w_key, w_initial_value)
+            else:
+                if space.finditem(self.w_initialdict, w_key) is not None:
+                    space.delitem(self.w_initialdict, w_key)
+        del self.lazy_initial_values_w
+
     def _cleanup_(self):
         self.getdict(self.space)
         self.w_initialdict = None
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -1288,15 +1288,15 @@
             for i in range(len(names_w) - 1, -1, -1):
                 space.setitem(w_ann, names_w[i], self.popvalue())
         defaultarguments = self.popvalues(posdefaults)
-        w_kw_defs = None
+        kw_defs_w = None
         if kwdefaults:
-            w_kw_defs = space.newdict(strdict=True)
-            for i in range(kwdefaults - 1, -1, -1):
-                w_name = self.popvalue()
-                w_def = self.popvalue()
-                space.setitem(w_kw_defs, w_def, w_name)
+            kw_defs_w = []
+            for i in range(kwdefaults):
+                w_defvalue = self.popvalue()
+                w_defname = self.popvalue()
+                kw_defs_w.append((w_defname, w_defvalue))
         fn = function.Function(space, codeobj, self.get_w_globals(), defaultarguments,
-                               w_kw_defs, freevars, w_ann, qualname=qualname)
+                               kw_defs_w, freevars, w_ann, qualname=qualname)
         self.pushvalue(space.wrap(fn))
 
     def MAKE_FUNCTION(self, oparg, next_instr):
diff --git a/pypy/interpreter/signature.py b/pypy/interpreter/signature.py
--- a/pypy/interpreter/signature.py
+++ b/pypy/interpreter/signature.py
@@ -39,6 +39,7 @@
 
     def scope_length(self):
         scopelen = len(self.argnames)
+        scopelen += len(self.kwonlyargnames)
         scopelen += self.has_vararg()
         scopelen += self.has_kwarg()
         return scopelen
diff --git a/pypy/interpreter/test/test_argument.py b/pypy/interpreter/test/test_argument.py
--- a/pypy/interpreter/test/test_argument.py
+++ b/pypy/interpreter/test/test_argument.py
@@ -30,7 +30,7 @@
         assert sig.num_argnames() == 3
         assert sig.has_vararg()
         assert sig.has_kwarg()
-        assert sig.scope_length() == 5
+        assert sig.scope_length() == 6
         assert sig.getallvarnames() == ["a", "b", "c", "d", "kwonly", "c"]
 
     def test_eq(self):
diff --git a/pypy/interpreter/test/test_gateway.py b/pypy/interpreter/test/test_gateway.py
--- a/pypy/interpreter/test/test_gateway.py
+++ b/pypy/interpreter/test/test_gateway.py
@@ -47,6 +47,12 @@
         code = gateway.BuiltinCode(f, unwrap_spec=[gateway.ObjSpace, "index"])
         assert code.signature() == Signature(["index"], None, None)
 
+        def f(space, __kwonly__, w_x):
+            pass
+        code = gateway.BuiltinCode(f, unwrap_spec=[gateway.ObjSpace,
+                                                   "kwonly", W_Root])
+        assert code.signature() == Signature([], kwonlyargnames=['x'])
+
 
     def test_call(self):
         def c(space, w_x, w_y, hello_w):
@@ -753,7 +759,7 @@
         @gateway.unwrap_spec(w_x = WrappedDefault(42), y=int)
         def g(space, w_x, y):
             never_called
-        py.test.raises(AssertionError, space.wrap, gateway.interp2app_temp(g))
+        py.test.raises(KeyError, space.wrap, gateway.interp2app_temp(g))
 
     def test_unwrap_spec_default_applevel_bug2(self):
         space = self.space
@@ -803,6 +809,80 @@
         w_res = space.call_args(w_g, args)
         assert space.eq_w(w_res, space.newbytes('foo'))
 
+    def test_unwrap_spec_kwonly(self):
+        space = self.space
+        def g(space, w_x, __kwonly__, w_y):
+            return space.sub(w_x, w_y)
+        w_g = space.wrap(gateway.interp2app_temp(g))
+        w = space.wrap
+        w1 = w(1)
+
+        for i in range(4):
+            a = argument.Arguments(space, [w1, w1, w1])
+            py.test.raises(gateway.OperationError, space.call_args, w_g, a)
+            py.test.raises(gateway.OperationError, space.call_function, w_g,
+                           *(i * (w1,)))
+
+        args = argument.Arguments(space, [w(1)],
+                                  w_starstararg = w({'y': 10}))
+        assert space.eq_w(space.call_args(w_g, args), w(-9))
+        args = argument.Arguments(space, [],
+                                  w_starstararg = w({'x': 2, 'y': 10}))
+        assert space.eq_w(space.call_args(w_g, args), w(-8))
+
+    def test_unwrap_spec_kwonly_default(self):
+        space = self.space
+        @gateway.unwrap_spec(w_x2=WrappedDefault(50), y2=int)
+        def g(space, w_x1, w_x2, __kwonly__, w_y1, y2=200):
+            return space.sub(space.sub(w_x1, w_x2),
+                             space.sub(w_y1, w(y2)))
+        w_g = space.wrap(gateway.interp2app_temp(g))
+        w = space.wrap
+        w1 = w(1)
+
+        for i in range(6):
+            py.test.raises(gateway.OperationError, space.call_function, w_g,
+                           *(i * (w1,)))
+
+        def expected(x1, x2=50, y1="missing", y2=200):
+            return (x1 - x2) - (y1 - y2)
+
+        def check(*args, **kwds):
+            a = argument.Arguments(space, [], w_stararg = w(args),
+                                          w_starstararg = w(kwds))
+            w_res = space.call_args(w_g, a)
+            assert space.eq_w(w_res, w(expected(*args, **kwds)))
+
+            del kwds['y1']
+            a = argument.Arguments(space, [], w_stararg = w(args),
+                                          w_starstararg = w(kwds))
+            py.test.raises(gateway.OperationError, space.call_args, w_g, a)
+
+            args += (1234,)
+            a = argument.Arguments(space, [], w_stararg = w(args),
+                                          w_starstararg = w(kwds))
+            py.test.raises(gateway.OperationError, space.call_args, w_g, a)
+
+        check(5,       y1=1234)
+        check(5, 1,    y1=1234)
+        check(5, x2=1, y1=1234)
+        check(5,       y1=1234, y2=343)
+        check(5, 1,    y1=1234, y2=343)
+        check(5, x2=1, y1=1234, y2=343)
+        check(x1=5,       y1=1234,       )
+        check(x1=5, x2=1, y1=1234,       )
+        check(x1=5,       y1=1234, y2=343)
+        check(x1=5, x2=1, y1=1234, y2=343)
+
+    def test_unwrap_spec_kwonly_default_2(self):
+        space = self.space
+        @gateway.unwrap_spec(w_x2=WrappedDefault(50))
+        def g(space, w_x2=None):
+            return w_x2
+        w_g = space.wrap(gateway.interp2app_temp(g))
+        w_res = space.call_function(w_g)
+        assert space.eq_w(w_res, space.wrap(50))
+
 
 class AppTestPyTestMark:
     @py.test.mark.unlikely_to_exist
diff --git a/pypy/interpreter/test/test_interpreter.py b/pypy/interpreter/test/test_interpreter.py
--- a/pypy/interpreter/test/test_interpreter.py
+++ b/pypy/interpreter/test/test_interpreter.py
@@ -435,6 +435,20 @@
         assert X().f() == 42
         """
 
+    def test_kwonlyarg_required(self):
+        """
+        def f(*, a=5, b):
+            return (a, b)
+        assert f(b=10) == (5, 10)
+        assert f(a=7, b=12) == (7, 12)
+        raises(TypeError, f)
+        raises(TypeError, f, 1)
+        raises(TypeError, f, 1, 1)
+        raises(TypeError, f, a=1)
+        raises(TypeError, f, 1, a=1)
+        raises(TypeError, f, 1, b=1)
+        """
+
     def test_extended_unpacking_short(self):
         """
         class Seq:
diff --git a/pypy/module/__pypy__/interp_magic.py b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -131,7 +131,7 @@
 
 @unwrap_spec(w_module=MixedModule)
 def save_module_content_for_future_reload(space, w_module):
-    w_module.save_module_content_for_future_reload(save_all=True)
+    w_module.save_module_content_for_future_reload()
 
 def set_code_callback(space, w_callable):
     cache = space.fromcache(CodeHookCache)
diff --git a/pypy/module/_frozen_importlib/__init__.py b/pypy/module/_frozen_importlib/__init__.py
--- a/pypy/module/_frozen_importlib/__init__.py
+++ b/pypy/module/_frozen_importlib/__init__.py
@@ -16,11 +16,10 @@
     @staticmethod
     def _compile_bootstrap_module(space, name, w_name, w_dict):
         """NOT_RPYTHON"""
-        ec = space.getexecutioncontext()
         with open(os.path.join(lib_python, 'importlib', name + '.py')) as fp:
             source = fp.read()
         pathname = "<frozen importlib.%s>" % name
-        code_w = ec.compiler.compile(source, pathname, 'exec', 0)
+        code_w = Module._cached_compile(space, source, pathname, 'exec', 0)
         space.setitem(w_dict, space.wrap('__name__'), w_name)
         space.setitem(w_dict, space.wrap('__builtins__'),
                       space.wrap(space.builtin))
@@ -43,6 +42,31 @@
 
         self.w_import = space.wrap(interp_import.import_with_frames_removed)
 
+    @staticmethod
+    def _cached_compile(space, source, *args):
+        from rpython.config.translationoption import CACHE_DIR
+        from pypy.module.marshal import interp_marshal
+
+        cachename = os.path.join(CACHE_DIR, 'frozen_importlib_bootstrap')
+        try:
+            if space.config.translating:
+                raise IOError("don't use the cache when translating pypy")
+            with open(cachename, 'rb') as f:
+                previous = f.read(len(source) + 1)
+                if previous != source + '\x00':
+                    raise IOError("source changed")
+                w_bin = space.newbytes(f.read())
+                code_w = interp_marshal.loads(space, w_bin)
+        except IOError:
+            # must (re)compile the source
+            ec = space.getexecutioncontext()
+            code_w = ec.compiler.compile(source, *args)
+            w_bin = interp_marshal.dumps(space, code_w, space.wrap(2))
+            content = source + '\x00' + space.bytes_w(w_bin)
+            with open(cachename, 'wb') as f:
+                f.write(content)
+        return code_w
+
     def startup(self, space):
         """Copy our __import__ to builtins."""
         w_install = self.getdictvalue(space, '_install')
diff --git a/pypy/module/_pypyjson/interp_decoder.py b/pypy/module/_pypyjson/interp_decoder.py
--- a/pypy/module/_pypyjson/interp_decoder.py
+++ b/pypy/module/_pypyjson/interp_decoder.py
@@ -327,7 +327,8 @@
             i += 1
             if ch == '"':
                 content_utf8 = builder.build()
-                content_unicode = unicodehelper.decode_utf8(self.space, content_utf8)
+                content_unicode = unicodehelper.decode_utf8(
+                    self.space, content_utf8, allow_surrogates=True)
                 self.last_type = TYPE_STRING
                 self.pos = i
                 return self.space.wrap(content_unicode)
@@ -374,7 +375,8 @@
                    # this point
         #
         uchr = runicode.code_to_unichr(val)     # may be a surrogate pair again
-        utf8_ch = unicodehelper.encode_utf8(self.space, uchr)
+        utf8_ch = unicodehelper.encode_utf8(
+            self.space, uchr, allow_surrogates=True)
         builder.append(utf8_ch)
         return i
 
diff --git a/pypy/module/_pypyjson/test/test__pypyjson.py b/pypy/module/_pypyjson/test/test__pypyjson.py
--- a/pypy/module/_pypyjson/test/test__pypyjson.py
+++ b/pypy/module/_pypyjson/test/test__pypyjson.py
@@ -10,10 +10,10 @@
     assert dec.skip_whitespace(8) == len(s)
     dec.close()
 
-    
+
 
 class AppTest(object):
-    spaceconfig = {"objspace.usemodules._pypyjson": True}
+    spaceconfig = {"usemodules": ['_pypyjson']}
 
     def test_raise_on_bytes(self):
         import _pypyjson
@@ -40,7 +40,7 @@
         raises(ValueError, _pypyjson.loads, 'fa')
         raises(ValueError, _pypyjson.loads, 'f')
         raises(ValueError, _pypyjson.loads, 'falXX')
-        
+
 
     def test_decode_string(self):
         import _pypyjson
@@ -69,7 +69,7 @@
         import _pypyjson
         assert _pypyjson.loads(r'"\\"') == '\\'
         assert _pypyjson.loads(r'"\""') == '"'
-        assert _pypyjson.loads(r'"\/"') == '/'       
+        assert _pypyjson.loads(r'"\/"') == '/'
         assert _pypyjson.loads(r'"\b"') == '\b'
         assert _pypyjson.loads(r'"\f"') == '\f'
         assert _pypyjson.loads(r'"\n"') == '\n'
@@ -85,7 +85,7 @@
         import _pypyjson
         s = r'"hello\nworld' # missing the trailing "
         raises(ValueError, "_pypyjson.loads(s)")
-        
+
     def test_escape_sequence_unicode(self):
         import _pypyjson
         s = r'"\u1234"'
@@ -166,7 +166,7 @@
     def test_decode_object_nonstring_key(self):
         import _pypyjson
         raises(ValueError, "_pypyjson.loads('{42: 43}')")
-        
+
     def test_decode_array(self):
         import _pypyjson
         assert _pypyjson.loads('[]') == []
@@ -183,7 +183,7 @@
         res = _pypyjson.loads('"z\\ud834\\udd20x"')
         assert res == expected
 
-    def test_surrogate_pair(self):
+    def test_lone_surrogate(self):
         import _pypyjson
         json = '{"a":"\\uD83D"}'
         res = _pypyjson.loads(json)
diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -15,8 +15,7 @@
 from rpython.rlib.unroll import unrolling_iterable
 from rpython.tool.sourcetools import func_with_new_name
 
-from pypy.interpreter.gateway import (
-    unwrap_spec, WrappedDefault, Unwrapper, kwonly)
+from pypy.interpreter.gateway import unwrap_spec, WrappedDefault, Unwrapper
 from pypy.interpreter.error import (
     OperationError, oefmt, wrap_oserror, wrap_oserror2, strerror as _strerror)
 from pypy.interpreter.executioncontext import ExecutionContext
@@ -211,7 +210,8 @@
             "%s: %s unavailable on this platform", funcname, arg)
 
 @unwrap_spec(flags=c_int, mode=c_int, dir_fd=DirFD(rposix.HAVE_OPENAT))
-def open(space, w_path, flags, mode=0777, dir_fd=DEFAULT_DIR_FD):
+def open(space, w_path, flags, mode=0777,
+         __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
     """open(path, flags, mode=0o777, *, dir_fd=None)
 
 Open a file for low level IO.  Returns a file handle (integer).
@@ -428,8 +428,8 @@
 @unwrap_spec(
     path=path_or_fd(allow_fd=True),
     dir_fd=DirFD(rposix.HAVE_FSTATAT),
-    follow_symlinks=kwonly(bool))
-def stat(space, path, dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
+    follow_symlinks=bool)
+def stat(space, path, __kwonly__, dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
     """stat(path, *, dir_fd=None, follow_symlinks=True) -> stat result
 
 Perform a stat system call on the given path.
@@ -476,7 +476,7 @@
 @unwrap_spec(
     path=path_or_fd(allow_fd=False),
     dir_fd=DirFD(rposix.HAVE_FSTATAT))
-def lstat(space, path, dir_fd=DEFAULT_DIR_FD):
+def lstat(space, path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
     """lstat(path, *, dir_fd=None) -> stat result
 
 Like stat(), but do not follow symbolic links.
@@ -551,9 +551,9 @@
         raise wrap_oserror(space, e)
 
 @unwrap_spec(mode=c_int,
-    dir_fd=DirFD(rposix.HAVE_FACCESSAT), effective_ids=kwonly(bool),
-    follow_symlinks=kwonly(bool))
-def access(space, w_path, mode,
+    dir_fd=DirFD(rposix.HAVE_FACCESSAT), effective_ids=bool,
+    follow_symlinks=bool)
+def access(space, w_path, mode, __kwonly__,
         dir_fd=DEFAULT_DIR_FD, effective_ids=False, follow_symlinks=True):
     """\
 access(path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True)
@@ -626,7 +626,7 @@
         return space.wrap(rc)
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_UNLINKAT))
-def unlink(space, w_path, dir_fd=DEFAULT_DIR_FD):
+def unlink(space, w_path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
     """unlink(path, *, dir_fd=None)
 
 Remove a file (same as remove()).
@@ -645,7 +645,7 @@
         raise wrap_oserror2(space, e, w_path)
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_UNLINKAT))
-def remove(space, w_path, dir_fd=DEFAULT_DIR_FD):
+def remove(space, w_path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
     """remove(path, *, dir_fd=None)
 
 Remove a file (same as unlink()).
@@ -710,7 +710,7 @@
         raise wrap_oserror2(space, e, w_path)
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_MKDIRAT))
-def mkdir(space, w_path, mode=0o777, dir_fd=DEFAULT_DIR_FD):
+def mkdir(space, w_path, mode=0o777, __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
     """mkdir(path, mode=0o777, *, dir_fd=None)
 
 Create a directory.
@@ -731,7 +731,7 @@
         raise wrap_oserror2(space, e, w_path)
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_UNLINKAT))
-def rmdir(space, w_path, dir_fd=DEFAULT_DIR_FD):
+def rmdir(space, w_path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
     """rmdir(path, *, dir_fd=None)
 
 Remove a directory.
@@ -901,8 +901,9 @@
     return space.newtuple([space.wrap(fd1), space.wrap(fd2)])
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_FCHMODAT),
-             follow_symlinks=kwonly(bool))
-def chmod(space, w_path, mode, dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
+             follow_symlinks=bool)
+def chmod(space, w_path, mode, __kwonly__,
+          dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
     """chmod(path, mode, *, dir_fd=None, follow_symlinks=True)
 
 Change the access permissions of a file.
@@ -968,7 +969,7 @@
 
 @unwrap_spec(src_dir_fd=DirFD(rposix.HAVE_RENAMEAT),
         dst_dir_fd=DirFD(rposix.HAVE_RENAMEAT))
-def rename(space, w_src, w_dst,
+def rename(space, w_src, w_dst, __kwonly__,
         src_dir_fd=DEFAULT_DIR_FD, dst_dir_fd=DEFAULT_DIR_FD):
     """rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
 
@@ -992,7 +993,7 @@
 
 @unwrap_spec(src_dir_fd=DirFD(rposix.HAVE_RENAMEAT),
         dst_dir_fd=DirFD(rposix.HAVE_RENAMEAT))
-def replace(space, w_src, w_dst,
+def replace(space, w_src, w_dst, __kwonly__,
         src_dir_fd=DEFAULT_DIR_FD, dst_dir_fd=DEFAULT_DIR_FD):
     """replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
 
@@ -1015,7 +1016,7 @@
         raise wrap_oserror(space, e)
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_MKFIFOAT))
-def mkfifo(space, w_path, mode=0666, dir_fd=DEFAULT_DIR_FD):
+def mkfifo(space, w_path, mode=0666, __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
     """mkfifo(path, mode=0o666, *, dir_fd=None)
 
 Create a FIFO (a POSIX named pipe).
@@ -1034,7 +1035,8 @@
         raise wrap_oserror2(space, e, w_path)
 
 @unwrap_spec(mode=c_int, device=c_int, dir_fd=DirFD(rposix.HAVE_MKNODAT))
-def mknod(space, w_filename, mode=0600, device=0, dir_fd=DEFAULT_DIR_FD):
+def mknod(space, w_filename, mode=0600, device=0,
+          __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
     """mknod(filename, mode=0o600, device=0, *, dir_fd=None)
 
 Create a filesystem node (file, device special file or named pipe)
@@ -1096,9 +1098,9 @@
 @unwrap_spec(
     src='fsencode', dst='fsencode',
     src_dir_fd=DirFD(rposix.HAVE_LINKAT), dst_dir_fd=DirFD(rposix.HAVE_LINKAT),
-    follow_symlinks=kwonly(bool))
+    follow_symlinks=bool)
 def link(
-        space, src, dst,
+        space, src, dst, __kwonly__,
         src_dir_fd=DEFAULT_DIR_FD, dst_dir_fd=DEFAULT_DIR_FD,
         follow_symlinks=True):
     """\
@@ -1128,7 +1130,7 @@
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_SYMLINKAT))
 def symlink(space, w_src, w_dst, w_target_is_directory=None,
-        dir_fd=DEFAULT_DIR_FD):
+            __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
     """symlink(src, dst, target_is_directory=False, *, dir_fd=None)
 
 Create a symbolic link pointing to src named dst.
@@ -1156,7 +1158,7 @@
 @unwrap_spec(
     path=path_or_fd(allow_fd=False),
     dir_fd=DirFD(rposix.HAVE_READLINKAT))
-def readlink(space, path, dir_fd=DEFAULT_DIR_FD):
+def readlink(space, path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
     """readlink(path, *, dir_fd=None) -> path
 
 Return a string representing the path to which the symbolic link points.
@@ -1356,9 +1358,9 @@
 
 @unwrap_spec(
     path=path_or_fd(allow_fd=rposix.HAVE_FUTIMENS or rposix.HAVE_FUTIMES),
-    w_times=WrappedDefault(None), w_ns=kwonly(WrappedDefault(None)),
-    dir_fd=DirFD(rposix.HAVE_UTIMENSAT), follow_symlinks=kwonly(bool))
-def utime(space, path, w_times, w_ns, dir_fd=DEFAULT_DIR_FD,
+    w_times=WrappedDefault(None), w_ns=WrappedDefault(None),
+    dir_fd=DirFD(rposix.HAVE_UTIMENSAT), follow_symlinks=bool)
+def utime(space, path, w_times, __kwonly__, w_ns, dir_fd=DEFAULT_DIR_FD,
           follow_symlinks=True):
     """utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)
 
@@ -1892,8 +1894,9 @@
 
 @unwrap_spec(
     uid=c_uid_t, gid=c_gid_t,
-    dir_fd=DirFD(rposix.HAVE_FCHOWNAT), follow_symlinks=kwonly(bool))
-def chown(space, w_path, uid, gid, dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
+    dir_fd=DirFD(rposix.HAVE_FCHOWNAT), follow_symlinks=bool)
+def chown(space, w_path, uid, gid, __kwonly__,
+          dir_fd=DEFAULT_DIR_FD, follow_symlinks=True):
     """chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)
 
 Change the owner and group id of path to the numeric uid and gid.
diff --git a/pypy/module/sys/__init__.py b/pypy/module/sys/__init__.py
--- a/pypy/module/sys/__init__.py
+++ b/pypy/module/sys/__init__.py
@@ -14,6 +14,13 @@
         """NOT_RPYTHON""" # because parent __init__ isn't
         if space.config.translating:
             del self.__class__.interpleveldefs['pypy_getudir']
+            del self.__class__.appleveldefs['stdin']
+            del self.__class__.appleveldefs['__stdin__']
+            del self.__class__.appleveldefs['stdout']
+            del self.__class__.appleveldefs['__stdout__']
+            del self.__class__.appleveldefs['stderr']
+            del self.__class__.appleveldefs['__stderr__']
+
         super(Module, self).__init__(space, w_name)
         self.recursionlimit = 100
         self.defaultencoding = "utf-8"
@@ -99,6 +106,15 @@
         'flags'                 : 'app.null_sysflags',
         '_xoptions'             : 'app.null__xoptions',
         'implementation'        : 'app.implementation',
+
+        # these six attributes are here only during tests;
+        # they are removed before translation
+        'stdin'                 : 'std_test.stdin',
+        '__stdin__'             : 'std_test.stdin',
+        'stdout'                : 'std_test.stdout',
+        '__stdout__'            : 'std_test.stdout',
+        'stderr'                : 'std_test.stderr',
+        '__stderr__'            : 'std_test.stderr',
     }
 
     def startup(self, space):
@@ -123,28 +139,12 @@
         space = self.space
 
         if not space.config.translating:
-            from pypy.module.sys.interp_encoding import _getfilesystemencoding
-            self.filesystemencoding = _getfilesystemencoding(space)
-
-        if not space.config.translating:
-            # Install standard streams for tests that don't call app_main.
-            # Always use line buffering, even for tests that capture
-            # standard descriptors.
-            space.appexec([], """():
-                import sys, io
-                sys.stdin = sys.__stdin__ = io.open(0, "r", encoding="ascii",
-                                                    closefd=False)
-                sys.stdin.buffer.raw.name = "<stdin>"
-                sys.stdout = sys.__stdout__ = io.open(1, "w", encoding="ascii",
-                                                      buffering=1,
-                                                      closefd=False)
-                sys.stdout.buffer.raw.name = "<stdout>"
-                sys.stderr = sys.__stderr__ = io.open(2, "w", encoding="ascii",
-                                                      errors="backslashreplace",
-                                                      buffering=1,
-                                                      closefd=False)
-                sys.stderr.buffer.raw.name = "<stderr>"
-               """)
+            ##from pypy.module.sys.interp_encoding import _getfilesystemencoding
+            ##self.filesystemencoding = _getfilesystemencoding(space)
+            # XXX the two lines above take a few seconds to run whenever
+            # we initialize the space; for tests, use a simpler version
+            from pypy.module.sys.interp_encoding import base_encoding
+            self.filesystemencoding = space.wrap(base_encoding)
 
     def flush_std_files(self, space):
         w_stdout = space.sys.getdictvalue(space, 'stdout')
diff --git a/pypy/module/sys/std_test.py b/pypy/module/sys/std_test.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/sys/std_test.py
@@ -0,0 +1,19 @@
+# Install standard streams for tests that don't call app_main.  Always
+# use line buffering, even for tests that capture standard descriptors.
+
+import io
+
+stdin = io.open(0, "r", encoding="ascii",
+                closefd=False)
+stdin.buffer.raw.name = "<stdin>"
+
+stdout = io.open(1, "w", encoding="ascii",
+                 buffering=1,
+                 closefd=False)
+stdout.buffer.raw.name = "<stdout>"
+
+stderr = io.open(2, "w", encoding="ascii",
+                 errors="backslashreplace",
+                 buffering=1,
+                 closefd=False)
+stderr.buffer.raw.name = "<stderr>"
diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py
--- a/pypy/objspace/std/mapdict.py
+++ b/pypy/objspace/std/mapdict.py
@@ -68,6 +68,7 @@
     @jit.elidable
     def find_map_attr(self, name, index):
         # attr cache
+        assert type(name) is str    # utf8-encoded
         space = self.space
         cache = space.fromcache(MapAttrCache)
         SHIFT2 = r_uint.BITS - space.config.objspace.std.methodcachesizeexp
@@ -336,7 +337,7 @@
             space = self.space
             w_dict = obj.getdict(space)
             try:
-                space.delitem(w_dict, space.wrap(name))
+                space.delitem(w_dict, space.wrap(name.decode('utf-8')))
             except OperationError as ex:
                 if not ex.match(space, space.w_KeyError):
                     raise
@@ -401,7 +402,7 @@
     def materialize_r_dict(self, space, obj, dict_w):
         new_obj = self.back.materialize_r_dict(space, obj, dict_w)
         if self.index == DICT:
-            w_attr = space.wrap(self.name)
+            w_attr = space.wrap(self.name.decode('utf-8'))
             dict_w[w_attr] = obj._mapdict_read_storage(self.storageindex)
         else:
             self._copy_attr(obj, new_obj)
@@ -809,7 +810,7 @@
             raise KeyError
         key = curr.name
         w_value = self.getitem_str(w_dict, key)
-        w_key = self.space.wrap(key)
+        w_key = self.space.wrap(key.decode('utf-8'))
         self.delitem(w_dict, w_key)
         return (w_key, w_value)
 
@@ -844,7 +845,7 @@
             if curr_map:
                 self.curr_map = curr_map.back
                 attr = curr_map.name
-                w_attr = self.space.wrap(attr)
+                w_attr = self.space.wrap(attr.decode('utf-8'))
                 return w_attr
         return None
 
@@ -885,7 +886,7 @@
             if curr_map:
                 self.curr_map = curr_map.back
                 attr = curr_map.name
-                w_attr = self.space.wrap(attr)
+                w_attr = self.space.wrap(attr.decode('utf-8'))
                 return w_attr, self.w_obj.getdictvalue(self.space, attr)
         return None, None
 
diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py
--- a/pypy/objspace/std/test/test_dictmultiobject.py
+++ b/pypy/objspace/std/test/test_dictmultiobject.py
@@ -1282,7 +1282,7 @@
             assert a == self.string2
             assert b == 2000
             if not self._str_devolves:
-                result = self.impl.getitem_str(self.string)
+                result = self.impl.getitem_str(self.string.encode('utf-8'))
             else:
                 result = self.impl.getitem(self.string)
             assert result == 1000
@@ -1293,7 +1293,7 @@
         assert self.impl.length() == 1
         assert self.impl.getitem(self.string) == 1000
         if not self._str_devolves:
-            result = self.impl.getitem_str(self.string)
+            result = self.impl.getitem_str(self.string.encode('utf-8'))
         else:
             result = self.impl.getitem(self.string)
         assert result == 1000
diff --git a/pypy/objspace/std/test/test_mapdict.py b/pypy/objspace/std/test/test_mapdict.py
--- a/pypy/objspace/std/test/test_mapdict.py
+++ b/pypy/objspace/std/test/test_mapdict.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 from pypy.objspace.std.test.test_dictmultiobject import FakeSpace, W_DictObject
 from pypy.objspace.std.mapdict import *
 
@@ -873,6 +875,15 @@
         d = x.__dict__
         assert list(__pypy__.reversed_dict(d)) == list(d.keys())[::-1]
 
+    def test_nonascii_argname(self):
+        """
+        class X:
+            pass
+        x = X()
+        x.日本 = 3
+        assert x.日本 == 3
+        assert x.__dict__ == {'日本': 3}
+        """
 
 class AppTestWithMapDictAndCounters(object):
     spaceconfig = {"objspace.std.withmethodcachecounter": True}


More information about the pypy-commit mailing list