]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-108494: Argument Clinic: inline parsing code for positional-only parameters in...
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 3 Sep 2023 14:28:14 +0000 (17:28 +0300)
committerGitHub <noreply@github.com>
Sun, 3 Sep 2023 14:28:14 +0000 (17:28 +0300)
13 files changed:
Modules/_io/clinic/bufferedio.c.h
Modules/_io/clinic/bytesio.c.h
Modules/_io/clinic/fileio.c.h
Modules/_io/clinic/winconsoleio.c.h
Modules/_multiprocessing/multiprocessing.c
Modules/_posixsubprocess.c
Modules/_struct.c
Modules/clinic/_testclinic_limited.c.h
Modules/overlapped.c
Modules/resource.c
PC/msvcrtmodule.c
PC/winreg.c
Tools/clinic/clinic.py

index b2c52cf2a95a44523bdf6174943480ec55dd695c..7577bdec5c3b2054c169cb21dac91a3989b05a7c 100644 (file)
@@ -26,7 +26,6 @@ _io__BufferedIOBase_readinto(PyObject *self, PyObject *arg)
     Py_buffer buffer = {NULL, NULL};
 
     if (PyObject_GetBuffer(arg, &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto", "argument", "read-write bytes-like object", arg);
         goto exit;
     }
@@ -63,7 +62,6 @@ _io__BufferedIOBase_readinto1(PyObject *self, PyObject *arg)
     Py_buffer buffer = {NULL, NULL};
 
     if (PyObject_GetBuffer(arg, &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto1", "argument", "read-write bytes-like object", arg);
         goto exit;
     }
@@ -590,7 +588,6 @@ _io__Buffered_readinto(buffered *self, PyObject *arg)
     Py_buffer buffer = {NULL, NULL};
 
     if (PyObject_GetBuffer(arg, &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto", "argument", "read-write bytes-like object", arg);
         goto exit;
     }
@@ -627,7 +624,6 @@ _io__Buffered_readinto1(buffered *self, PyObject *arg)
     Py_buffer buffer = {NULL, NULL};
 
     if (PyObject_GetBuffer(arg, &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto1", "argument", "read-write bytes-like object", arg);
         goto exit;
     }
@@ -1098,4 +1094,4 @@ skip_optional_pos:
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=9e09091995ae02b0 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=f940cea085f0bf91 input=a9049054013a1b77]*/
index d73647792008275aadedfa05ed5302998209c2b1..d42ab48cef2859c79288942d65e1500457bd2aff 100644 (file)
@@ -328,7 +328,6 @@ _io_BytesIO_readinto(bytesio *self, PyObject *arg)
     Py_buffer buffer = {NULL, NULL};
 
     if (PyObject_GetBuffer(arg, &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto", "argument", "read-write bytes-like object", arg);
         goto exit;
     }
@@ -538,4 +537,4 @@ skip_optional_pos:
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=8ab65edc03edbfe0 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b753fdf1ba36c461 input=a9049054013a1b77]*/
index 29f8cf6aa9a85c5a459b89e02844a6e9816170b7..deb99fa9d99bd02dcddc6c7f6a6fc1010d6ebcc5 100644 (file)
@@ -245,7 +245,6 @@ _io_FileIO_readinto(fileio *self, PyTypeObject *cls, PyObject *const *args, Py_s
         goto exit;
     }
     if (PyObject_GetBuffer(args[0], &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto", "argument 1", "read-write bytes-like object", args[0]);
         goto exit;
     }
@@ -536,4 +535,4 @@ _io_FileIO_isatty(fileio *self, PyObject *Py_UNUSED(ignored))
 #ifndef _IO_FILEIO_TRUNCATE_METHODDEF
     #define _IO_FILEIO_TRUNCATE_METHODDEF
 #endif /* !defined(_IO_FILEIO_TRUNCATE_METHODDEF) */
-/*[clinic end generated code: output=238dd48819076434 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=2ce6ce923ccef86e input=a9049054013a1b77]*/
index 0683eecdfebb37c18535fce1945602805f65311d..ecc71e552c23f43d1a578181c7deceffd70fa5df 100644 (file)
@@ -243,7 +243,6 @@ _io__WindowsConsoleIO_readinto(winconsoleio *self, PyTypeObject *cls, PyObject *
         goto exit;
     }
     if (PyObject_GetBuffer(args[0], &buffer, PyBUF_WRITABLE) < 0) {
-        PyErr_Clear();
         _PyArg_BadArgument("readinto", "argument 1", "read-write bytes-like object", args[0]);
         goto exit;
     }
@@ -465,4 +464,4 @@ _io__WindowsConsoleIO_isatty(winconsoleio *self, PyObject *Py_UNUSED(ignored))
 #ifndef _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
     #define _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
 #endif /* !defined(_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF) */
-/*[clinic end generated code: output=7be51d48ddb7c8c8 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=37febc4c96732b3b input=a9049054013a1b77]*/
index 16b5cb5dd9ec7a6491c8d420aea2b0f4f75ca5c1..2e6d8eb68c02437ef9a1f5551ec22581137bddbc 100644 (file)
@@ -14,16 +14,17 @@ class HANDLE_converter(CConverter):
     type = "HANDLE"
     format_unit = '"F_HANDLE"'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = PyLong_AsVoidPtr({argname});
             if (!{paramname} && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=3e537d244034affb]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=3cf0318efc6a8772]*/
 
 /*[clinic input]
 module _multiprocessing
index ef76d26282e1b324bd03b18e5766c435359062e7..2898eedc3e3a8f59d64e76e78aff0cf74815d5ab 100644 (file)
@@ -87,15 +87,16 @@ class pid_t_converter(CConverter):
     type = 'pid_t'
     format_unit = '" _Py_PARSE_PID "'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = PyLong_AsPid({argname});
             if ({paramname} == -1 && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=5af1c116d56cbb5a]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=c94349aa1aad151d]*/
 
 #include "clinic/_posixsubprocess.c.h"
 
index 4ae21cce74f609ba64453ef3d0fb5d23da7c2b9b..be4c23af384bb980effd76ef5810078fb069390b 100644 (file)
@@ -110,18 +110,20 @@ class cache_struct_converter(CConverter):
     c_default = "NULL"
     broken_limited_capi = True
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        assert not limited_capi
+        return self.format_code("""
             if (!{converter}(module, {argname}, &{paramname})) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.name,
-                       converter=self.converter)
+            """,
+            argname=argname,
+            converter=self.converter)
 
     def cleanup(self):
         return "Py_XDECREF(%s);\n" % self.name
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=14e83804f599ed8f]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=c33b27d6b06006c6]*/
 
 static int cache_struct_converter(PyObject *, PyObject *, PyStructObject **);
 
index 93e7d7f98769cc496a69dfc9469098ead159273d..eff72cee24975c229a0df2cd0646590d96ae1c43 100644 (file)
@@ -37,7 +37,8 @@ my_int_func(PyObject *module, PyObject *arg_)
     int arg;
     int _return_value;
 
-    if (!PyArg_Parse(arg_, "i:my_int_func", &arg)) {
+    arg = PyLong_AsInt(arg_);
+    if (arg == -1 && PyErr_Occurred()) {
         goto exit;
     }
     _return_value = my_int_func_impl(module, arg);
@@ -56,22 +57,31 @@ PyDoc_STRVAR(my_int_sum__doc__,
 "\n");
 
 #define MY_INT_SUM_METHODDEF    \
-    {"my_int_sum", (PyCFunction)my_int_sum, METH_VARARGS, my_int_sum__doc__},
+    {"my_int_sum", (PyCFunction)(void(*)(void))my_int_sum, METH_FASTCALL, my_int_sum__doc__},
 
 static int
 my_int_sum_impl(PyObject *module, int x, int y);
 
 static PyObject *
-my_int_sum(PyObject *module, PyObject *args)
+my_int_sum(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
 {
     PyObject *return_value = NULL;
     int x;
     int y;
     int _return_value;
 
-    if (!PyArg_ParseTuple(args, "ii:my_int_sum",
-        &x, &y))
+    if (nargs != 2) {
+        PyErr_Format(PyExc_TypeError, "my_int_sum expected 2 arguments, got %zd", nargs);
         goto exit;
+    }
+    x = PyLong_AsInt(args[0]);
+    if (x == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    y = PyLong_AsInt(args[1]);
+    if (y == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
     _return_value = my_int_sum_impl(module, x, y);
     if ((_return_value == -1) && PyErr_Occurred()) {
         goto exit;
@@ -81,4 +91,4 @@ my_int_sum(PyObject *module, PyObject *args)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=dcd5203d0d29df3a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=5cf64baf978d2288 input=a9049054013a1b77]*/
index c682e6adccc765da794ddb633754b879981ed18d..e23db22dadb18bb8505d6ad0c1368200740f4a30 100644 (file)
 class pointer_converter(CConverter):
     format_unit = '"F_POINTER"'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = PyLong_AsVoidPtr({argname});
             if (!{paramname} && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 
 class OVERLAPPED_converter(pointer_converter):
     type = 'OVERLAPPED *'
@@ -55,13 +56,14 @@ class HANDLE_converter(pointer_converter):
 class ULONG_PTR_converter(pointer_converter):
     type = 'ULONG_PTR'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = (uintptr_t)PyLong_AsVoidPtr({argname});
             if (!{paramname} && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 
 class DWORD_converter(unsigned_long_converter):
     type = 'DWORD'
@@ -69,7 +71,7 @@ class DWORD_converter(unsigned_long_converter):
 class BOOL_converter(int_converter):
     type = 'BOOL'
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=8a07ea3018f4cec8]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=436f4440630a304c]*/
 
 /*[clinic input]
 module _overlapped
index 9e302a3a1ed962adebf517d56034f41d9c5169b3..d65509ec3436a20101b287b12f9951e46dbbf547 100644 (file)
@@ -23,15 +23,16 @@ class pid_t_converter(CConverter):
     type = 'pid_t'
     format_unit = '" _Py_PARSE_PID "'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = PyLong_AsPid({argname});
             if ({paramname} == -1 && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=5af1c116d56cbb5a]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=c94349aa1aad151d]*/
 
 #include "clinic/resource.c.h"
 
index afc810adcf7499071f263c8f45837ab636f04a22..d7c21766d709a61216764c2fe03ad256d0ba9620 100644 (file)
@@ -38,13 +38,14 @@ class HANDLE_converter(CConverter):
     type = 'void *'
     format_unit = '"_Py_PARSE_UINTPTR"'
 
-    def parse_arg(self, argname, displayname):
-        return """
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        return self.format_code("""
             {paramname} = PyLong_AsVoidPtr({argname});
             if (!{paramname} && PyErr_Occurred()) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.parser_name)
+            """,
+            argname=argname)
 
 class HANDLE_return_converter(CReturnConverter):
     type = 'void *'
@@ -74,7 +75,7 @@ class wchar_t_return_converter(CReturnConverter):
         data.return_conversion.append(
             'return_value = PyUnicode_FromOrdinal(_return_value);\n')
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=1e8e9fa3538ec08f]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=ff031be44ab3250d]*/
 
 /*[clinic input]
 module msvcrt
index 767127d34cab1b1504b66882cf4b898a10eeb9cf..77b80217ac0ab1b44854bd7773ce36c75cb6ef6f 100644 (file)
@@ -222,13 +222,15 @@ class HKEY_converter(CConverter):
     converter = 'clinic_HKEY_converter'
     broken_limited_capi = True
 
-    def parse_arg(self, argname, displayname):
-        return """
-        if (!{converter}(_PyModule_GetState(module), {argname}, &{paramname})) {{{{
-            goto exit;
-        }}}}
-        """.format(argname=argname, paramname=self.parser_name,
-                   converter=self.converter)
+    def parse_arg(self, argname, displayname, *, limited_capi):
+        assert not limited_capi
+        return self.format_code("""
+            if (!{converter}(_PyModule_GetState(module), {argname}, &{paramname})) {{{{
+                goto exit;
+            }}}}
+            """,
+            argname=argname,
+            converter=self.converter)
 
 class HKEY_return_converter(CReturnConverter):
     type = 'HKEY'
@@ -250,7 +252,7 @@ class self_return_converter(CReturnConverter):
         data.return_conversion.append(
             'return_value = (PyObject *)_return_value;\n')
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=f8cb7034338aeaba]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=4979f33998ffb6f8]*/
 
 #include "clinic/winreg.c.h"
 
index b744ca7e5c8145b43702bb46778ff12fa1afbda7..eca4747a3e4d572ce602fad569eb397450c3a044 100755 (executable)
@@ -990,6 +990,7 @@ class CLanguage(Language):
             params: dict[int, Parameter],
             argname_fmt: str | None,
             *,
+            fastcall: bool,
             limited_capi: bool,
             clinic: Clinic,
     ) -> str:
@@ -1003,26 +1004,30 @@ class CLanguage(Language):
             if p.is_optional():
                 if argname_fmt:
                     conditions.append(f"nargs < {i+1} && {argname_fmt % i}")
-                elif func.kind.new_or_init or limited_capi:
-                    conditions.append(f"nargs < {i+1} && PyDict_Contains(kwargs, &_Py_ID({p.name}))")
-                    containscheck = "PyDict_Contains"
-                    clinic.add_include('pycore_runtime.h', '_Py_ID()')
-                else:
+                elif fastcall:
                     conditions.append(f"nargs < {i+1} && PySequence_Contains(kwnames, &_Py_ID({p.name}))")
                     containscheck = "PySequence_Contains"
                     clinic.add_include('pycore_runtime.h', '_Py_ID()')
+                else:
+                    conditions.append(f"nargs < {i+1} && PyDict_Contains(kwargs, &_Py_ID({p.name}))")
+                    containscheck = "PyDict_Contains"
+                    clinic.add_include('pycore_runtime.h', '_Py_ID()')
             else:
                 conditions = [f"nargs < {i+1}"]
         condition = ") || (".join(conditions)
         if len(conditions) > 1:
             condition = f"(({condition}))"
         if last_param.is_optional():
-            if limited_capi:
-                condition = f"kwargs && PyDict_Size(kwargs) && {condition}"
-            elif func.kind.new_or_init:
-                condition = f"kwargs && PyDict_GET_SIZE(kwargs) && {condition}"
+            if fastcall:
+                if limited_capi:
+                    condition = f"kwnames && PyTuple_Size(kwnames) && {condition}"
+                else:
+                    condition = f"kwnames && PyTuple_GET_SIZE(kwnames) && {condition}"
             else:
-                condition = f"kwnames && PyTuple_GET_SIZE(kwnames) && {condition}"
+                if limited_capi:
+                    condition = f"kwargs && PyDict_Size(kwargs) && {condition}"
+                else:
+                    condition = f"kwargs && PyDict_GET_SIZE(kwargs) && {condition}"
         names = [repr(p.name) for p in params.values()]
         pstr = pprint_words(names)
         pl = 's' if len(params) != 1 else ''
@@ -1193,6 +1198,7 @@ class CLanguage(Language):
                 add(field)
             return linear_format(output(), parser_declarations=declarations)
 
+        fastcall = not new_or_init
         limited_capi = clinic.limited_capi
         if limited_capi and (requires_defining_class or pseudo_args or
                 (any(p.is_optional() for p in parameters) and
@@ -1210,7 +1216,7 @@ class CLanguage(Language):
                 parser_prototype = self.PARSER_PROTOTYPE_NOARGS
                 parser_code = []
             else:
-                assert not new_or_init
+                assert fastcall
 
                 flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS"
                 parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
@@ -1261,11 +1267,8 @@ class CLanguage(Language):
                     {c_basename}({self_type}{self_name}, PyObject *%s)
                     """ % argname)
 
-                if limited_capi:
-                    parsearg = None
-                else:
-                    displayname = parameters[0].get_displayname(0)
-                    parsearg = converters[0].parse_arg(argname, displayname)
+                displayname = parameters[0].get_displayname(0)
+                parsearg = converters[0].parse_arg(argname, displayname, limited_capi=limited_capi)
                 if parsearg is None:
                     parsearg = """
                         if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{
@@ -1284,27 +1287,8 @@ class CLanguage(Language):
             parser_prototype = self.PARSER_PROTOTYPE_VARARGS
             parser_definition = parser_body(parser_prototype, '    {option_group_parsing}')
 
-        elif (not requires_defining_class and pos_only == len(parameters) and
-              not pseudo_args and limited_capi):
-            # positional-only for the limited C API
-            flags = "METH_VARARGS"
-            parser_prototype = self.PARSER_PROTOTYPE_VARARGS
-            if all(isinstance(c, object_converter) and c.format_unit == 'O' for c in converters):
-                parser_code = [normalize_snippet("""
-                    if (!PyArg_UnpackTuple(args, "{name}", %d, %d,
-                        {parse_arguments}))
-                        goto exit;
-                """ % (min_pos, max_pos), indent=4)]
-            else:
-                parser_code = [normalize_snippet("""
-                    if (!PyArg_ParseTuple(args, "{format_units}:{name}",
-                        {parse_arguments}))
-                        goto exit;
-                """, indent=4)]
-            parser_definition = parser_body(parser_prototype, *parser_code)
-
         elif not requires_defining_class and pos_only == len(parameters) - pseudo_args:
-            if not new_or_init:
+            if fastcall:
                 # positional-only, but no option groups
                 # we only need one call to _PyArg_ParseStack
 
@@ -1318,24 +1302,59 @@ class CLanguage(Language):
 
                 flags = "METH_VARARGS"
                 parser_prototype = self.PARSER_PROTOTYPE_VARARGS
-                nargs = 'PyTuple_GET_SIZE(args)'
-                argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
+                if limited_capi:
+                    nargs = 'PyTuple_Size(args)'
+                    argname_fmt = 'PyTuple_GetItem(args, %d)'
+                else:
+                    nargs = 'PyTuple_GET_SIZE(args)'
+                    argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
 
             left_args = f"{nargs} - {max_pos}"
             max_args = NO_VARARG if (vararg != NO_VARARG) else max_pos
-            parser_code = [normalize_snippet("""
-                if (!_PyArg_CheckPositional("{name}", %s, %d, %s)) {{
-                    goto exit;
-                }}
-                """ % (nargs, min_pos, max_args), indent=4)]
+            if limited_capi:
+                parser_code = []
+                if nargs != 'nargs':
+                    parser_code.append(normalize_snippet(f'Py_ssize_t nargs = {nargs};', indent=4))
+                    nargs = 'nargs'
+                if min_pos == max_args:
+                    pl = '' if min_pos == 1 else 's'
+                    parser_code.append(normalize_snippet(f"""
+                        if ({nargs} != {min_pos}) {{{{
+                            PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs});
+                            goto exit;
+                        }}}}
+                        """,
+                    indent=4))
+                else:
+                    if min_pos:
+                        pl = '' if min_pos == 1 else 's'
+                        parser_code.append(normalize_snippet(f"""
+                            if ({nargs} < {min_pos}) {{{{
+                                PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs});
+                                goto exit;
+                            }}}}
+                            """,
+                            indent=4))
+                    if max_args != NO_VARARG:
+                        pl = '' if max_args == 1 else 's'
+                        parser_code.append(normalize_snippet(f"""
+                            if ({nargs} > {max_args}) {{{{
+                                PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
+                                goto exit;
+                            }}}}
+                            """,
+                        indent=4))
+            else:
+                parser_code = [normalize_snippet(f"""
+                    if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{
+                        goto exit;
+                    }}}}
+                    """, indent=4)]
 
             has_optional = False
             for i, p in enumerate(parameters):
-                displayname = p.get_displayname(i+1)
-                argname = argname_fmt % i
-
                 if p.is_vararg():
-                    if not new_or_init:
+                    if fastcall:
                         parser_code.append(normalize_snippet("""
                             %s = PyTuple_New(%s);
                             if (!%s) {{
@@ -1361,7 +1380,9 @@ class CLanguage(Language):
                             ), indent=4))
                     continue
 
-                parsearg = p.converter.parse_arg(argname, displayname)
+                displayname = p.get_displayname(i+1)
+                argname = argname_fmt % i
+                parsearg = p.converter.parse_arg(argname, displayname, limited_capi=limited_capi)
                 if parsearg is None:
                     parser_code = None
                     break
@@ -1378,7 +1399,9 @@ class CLanguage(Language):
                 if has_optional:
                     parser_code.append("skip_optional:")
             else:
-                if not new_or_init:
+                if limited_capi:
+                    fastcall = False
+                if fastcall:
                     parser_code = [normalize_snippet("""
                         if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
                             {parse_arguments})) {{
@@ -1386,6 +1409,8 @@ class CLanguage(Language):
                         }}
                         """, indent=4)]
                 else:
+                    flags = "METH_VARARGS"
+                    parser_prototype = self.PARSER_PROTOTYPE_VARARGS
                     parser_code = [normalize_snippet("""
                         if (!PyArg_ParseTuple(args, "{format_units}:{name}",
                             {parse_arguments})) {{
@@ -1421,25 +1446,10 @@ class CLanguage(Language):
                 nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0"
 
             if limited_capi:
-                # positional-or-keyword arguments
-                flags = "METH_VARARGS|METH_KEYWORDS"
-
-                parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
-                parser_code = [normalize_snippet("""
-                    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
-                        {parse_arguments}))
-                        goto exit;
-                """, indent=4)]
-                declarations = "static char *_keywords[] = {{{keywords_c} NULL}};"
-                if deprecated_positionals or deprecated_keywords:
-                    declarations += "\nPy_ssize_t nargs = PyTuple_Size(args);"
-                if deprecated_keywords:
-                    code = self.deprecate_keyword_use(f, deprecated_keywords, None,
-                                                      limited_capi=limited_capi,
-                                                      clinic=clinic)
-                    parser_code.append(code)
+                parser_code = None
+                fastcall = False
 
-            elif not new_or_init:
+            elif fastcall:
                 flags = "METH_FASTCALL|METH_KEYWORDS"
                 parser_prototype = self.PARSER_PROTOTYPE_FASTCALL_KEYWORDS
                 argname_fmt = 'args[%d]'
@@ -1477,11 +1487,12 @@ class CLanguage(Language):
                 flags = 'METH_METHOD|' + flags
                 parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
 
-            if not limited_capi:
+            if parser_code is not None:
                 if deprecated_keywords:
                     code = self.deprecate_keyword_use(f, deprecated_keywords, argname_fmt,
-                                                      limited_capi=limited_capi,
-                                                      clinic=clinic)
+                                                      clinic=clinic,
+                                                      fastcall=fastcall,
+                                                      limited_capi=limited_capi)
                     parser_code.append(code)
 
                 add_label: str | None = None
@@ -1490,7 +1501,7 @@ class CLanguage(Language):
                         raise ValueError("defining_class should be the first "
                                         "parameter (after self)")
                     displayname = p.get_displayname(i+1)
-                    parsearg = p.converter.parse_arg(argname_fmt % i, displayname)
+                    parsearg = p.converter.parse_arg(argname_fmt % i, displayname, limited_capi=limited_capi)
                     if parsearg is None:
                         parser_code = None
                         break
@@ -1542,40 +1553,56 @@ class CLanguage(Language):
                                 }}
                                 """ % add_label, indent=4))
 
-                if parser_code is not None:
-                    if add_label:
-                        parser_code.append("%s:" % add_label)
+            if parser_code is not None:
+                if add_label:
+                    parser_code.append("%s:" % add_label)
+            else:
+                declarations = declare_parser(f, clinic=clinic,
+                                              hasformat=True,
+                                              limited_capi=limited_capi)
+                if limited_capi:
+                    # positional-or-keyword arguments
+                    assert not fastcall
+                    flags = "METH_VARARGS|METH_KEYWORDS"
+                    parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
+                    parser_code = [normalize_snippet("""
+                        if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
+                            {parse_arguments}))
+                            goto exit;
+                    """, indent=4)]
+                    declarations = "static char *_keywords[] = {{{keywords_c} NULL}};"
+                    if deprecated_positionals or deprecated_keywords:
+                        declarations += "\nPy_ssize_t nargs = PyTuple_Size(args);"
+
+                elif fastcall:
+                    parser_code = [normalize_snippet("""
+                        if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
+                            {parse_arguments})) {{
+                            goto exit;
+                        }}
+                        """, indent=4)]
                 else:
-                    declarations = declare_parser(f, clinic=clinic,
-                                                  hasformat=True,
-                                                  limited_capi=clinic.limited_capi)
-                    if not new_or_init:
-                        parser_code = [normalize_snippet("""
-                            if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
-                                {parse_arguments})) {{
-                                goto exit;
-                            }}
-                            """, indent=4)]
-                    else:
-                        parser_code = [normalize_snippet("""
-                            if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
-                                {parse_arguments})) {{
-                                goto exit;
-                            }}
-                            """, indent=4)]
-                        if deprecated_positionals or deprecated_keywords:
-                            declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
-                    if deprecated_keywords:
-                        code = self.deprecate_keyword_use(f, deprecated_keywords, None,
-                                                          limited_capi=limited_capi,
-                                                          clinic=clinic)
-                        parser_code.append(code)
+                    parser_code = [normalize_snippet("""
+                        if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
+                            {parse_arguments})) {{
+                            goto exit;
+                        }}
+                        """, indent=4)]
+                    if deprecated_positionals or deprecated_keywords:
+                        declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
+                if deprecated_keywords:
+                    code = self.deprecate_keyword_use(f, deprecated_keywords, None,
+                                                      clinic=clinic,
+                                                      fastcall=fastcall,
+                                                      limited_capi=limited_capi)
+                    parser_code.append(code)
 
             if deprecated_positionals:
                 code = self.deprecate_positional_use(f, deprecated_positionals)
                 # Insert the deprecation code before parameter parsing.
                 parser_code.insert(0, code)
 
+            assert parser_prototype is not None
             parser_definition = parser_body(parser_prototype, *parser_code,
                                             declarations=declarations)
 
@@ -1619,6 +1646,9 @@ class CLanguage(Language):
         if flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'):
             methoddef_cast = "(PyCFunction)"
             methoddef_cast_end = ""
+        elif limited_capi:
+            methoddef_cast = "(PyCFunction)(void(*)(void))"
+            methoddef_cast_end = ""
         else:
             methoddef_cast = "_PyCFunction_CAST("
             methoddef_cast_end = ")"
@@ -3030,11 +3060,11 @@ class Parameter:
 
     def get_displayname(self, i: int) -> str:
         if i == 0:
-            return '"argument"'
+            return 'argument'
         if not self.is_positional_only():
-            return f'"argument {self.name!r}"'
+            return f'argument {self.name!r}'
         else:
-            return f'"argument {i}"'
+            return f'argument {i}'
 
     def render_docstring(self) -> str:
         add, out = text_accumulator()
@@ -3458,41 +3488,78 @@ class CConverter(metaclass=CConverterAutoRegister):
         """
         pass
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def bad_argument(self, displayname: str, expected: str, *, limited_capi: bool, expected_literal: bool = True) -> str:
+        assert '"' not in expected
+        if limited_capi:
+            if expected_literal:
+                return (f'PyErr_Format(PyExc_TypeError, '
+                        f'"{{{{name}}}}() {displayname} must be {expected}, not %.50s", '
+                        f'{{argname}} == Py_None ? "None" : Py_TYPE({{argname}})->tp_name);')
+            else:
+                return (f'PyErr_Format(PyExc_TypeError, '
+                        f'"{{{{name}}}}() {displayname} must be %.50s, not %.50s", '
+                        f'"{expected}", '
+                        f'{{argname}} == Py_None ? "None" : Py_TYPE({{argname}})->tp_name);')
+        else:
+            if expected_literal:
+                expected = f'"{expected}"'
+            return f'_PyArg_BadArgument("{{{{name}}}}", "{displayname}", {expected}, {{argname}});'
+
+    def format_code(self, fmt: str, *,
+                    argname: str,
+                    bad_argument: str | None = None,
+                    bad_argument2: str | None = None,
+                    **kwargs: Any) -> str:
+        if '{bad_argument}' in fmt:
+            if not bad_argument:
+                raise TypeError("required 'bad_argument' argument")
+            fmt = fmt.replace('{bad_argument}', bad_argument)
+        if '{bad_argument2}' in fmt:
+            if not bad_argument2:
+                raise TypeError("required 'bad_argument2' argument")
+            fmt = fmt.replace('{bad_argument2}', bad_argument2)
+        return fmt.format(argname=argname, paramname=self.parser_name, **kwargs)
+
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'O&':
-            return """
+            return self.format_code("""
                 if (!{converter}({argname}, &{paramname})) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           converter=self.converter)
+                """,
+                argname=argname,
+                converter=self.converter)
         if self.format_unit == 'O!':
             cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
             if self.subclass_of in type_checks:
                 typecheck, typename = type_checks[self.subclass_of]
-                return """
+                return self.format_code("""
                     if (!{typecheck}({argname})) {{{{
-                        _PyArg_BadArgument("{{name}}", {displayname}, "{typename}", {argname});
+                        {bad_argument}
                         goto exit;
                     }}}}
                     {paramname} = {cast}{argname};
-                    """.format(argname=argname, paramname=self.parser_name,
-                               displayname=displayname, typecheck=typecheck,
-                               typename=typename, cast=cast)
-            return """
+                    """,
+                    argname=argname,
+                    bad_argument=self.bad_argument(displayname, typename, limited_capi=limited_capi),
+                    typecheck=typecheck, typename=typename, cast=cast)
+            return self.format_code("""
                 if (!PyObject_TypeCheck({argname}, {subclass_of})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, ({subclass_of})->tp_name, {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = {cast}{argname};
-                """.format(argname=argname, paramname=self.parser_name,
-                           subclass_of=self.subclass_of, cast=cast,
-                           displayname=displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, '({subclass_of})->tp_name',
+                                               expected_literal=False, limited_capi=limited_capi),
+                subclass_of=self.subclass_of, cast=cast)
         if self.format_unit == 'O':
             cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
-            return """
+            return self.format_code("""
                 {paramname} = {cast}{argname};
-                """.format(argname=argname, paramname=self.parser_name, cast=cast)
+                """,
+                argname=argname, cast=cast)
         return None
 
     def set_template_dict(self, template_dict: TemplateDict) -> None:
@@ -3567,22 +3634,24 @@ class bool_converter(CConverter):
             self.default = bool(self.default)
             self.c_default = str(int(self.default))
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'i':
-            return """
+            return self.format_code("""
                 {paramname} = PyLong_AsInt({argname});
                 if ({paramname} == -1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
+                """,
+                argname=argname)
         elif self.format_unit == 'p':
-            return """
+            return self.format_code("""
                 {paramname} = PyObject_IsTrue({argname});
                 if ({paramname} < 0) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class defining_class_converter(CConverter):
     """
@@ -3618,9 +3687,9 @@ class char_converter(CConverter):
             if self.c_default == '"\'"':
                 self.c_default = r"'\''"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'c':
-            return """
+            return self.format_code("""
                 if (PyBytes_Check({argname}) && PyBytes_GET_SIZE({argname}) == 1) {{{{
                     {paramname} = PyBytes_AS_STRING({argname})[0];
                 }}}}
@@ -3628,12 +3697,14 @@ class char_converter(CConverter):
                     {paramname} = PyByteArray_AS_STRING({argname})[0];
                 }}}}
                 else {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "a byte string of length 1", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'a byte string of length 1', limited_capi=limited_capi),
+            )
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 
 @add_legacy_c_converter('B', bitwise=True)
@@ -3647,9 +3718,9 @@ class unsigned_char_converter(CConverter):
         if bitwise:
             self.format_unit = 'B'
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'b':
-            return """
+            return self.format_code("""
                 {{{{
                     long ival = PyLong_AsLong({argname});
                     if (ival == -1 && PyErr_Occurred()) {{{{
@@ -3669,9 +3740,10 @@ class unsigned_char_converter(CConverter):
                         {paramname} = (unsigned char) ival;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
+                """,
+                argname=argname)
         elif self.format_unit == 'B':
-            return """
+            return self.format_code("""
                 {{{{
                     unsigned long ival = PyLong_AsUnsignedLongMask({argname});
                     if (ival == (unsigned long)-1 && PyErr_Occurred()) {{{{
@@ -3681,8 +3753,9 @@ class unsigned_char_converter(CConverter):
                         {paramname} = (unsigned char) ival;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class byte_converter(unsigned_char_converter): pass
 
@@ -3692,9 +3765,9 @@ class short_converter(CConverter):
     format_unit = 'h'
     c_ignored_default = "0"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'h':
-            return """
+            return self.format_code("""
                 {{{{
                     long ival = PyLong_AsLong({argname});
                     if (ival == -1 && PyErr_Occurred()) {{{{
@@ -3714,8 +3787,9 @@ class short_converter(CConverter):
                         {paramname} = (short) ival;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class unsigned_short_converter(CConverter):
     type = 'unsigned short'
@@ -3730,15 +3804,33 @@ class unsigned_short_converter(CConverter):
             self.add_include('pycore_long.h',
                              '_PyLong_UnsignedShort_Converter()')
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'H':
-            return """
+            return self.format_code("""
                 {paramname} = (unsigned short)PyLong_AsUnsignedLongMask({argname});
                 if ({paramname} == (unsigned short)-1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        # NOTE: Raises OverflowError for negative integer.
+        return self.format_code("""
+            {{{{
+                unsigned long uval = PyLong_AsUnsignedLong({argname});
+                if (uval == (unsigned long)-1 && PyErr_Occurred()) {{{{
+                    goto exit;
+                }}}}
+                if (uval > USHRT_MAX) {{{{
+                    PyErr_SetString(PyExc_OverflowError,
+                                    "Python int too large for C unsigned short");
+                    goto exit;
+                }}}}
+                {paramname} = (unsigned short) uval;
+            }}}}
+            """,
+            argname=argname)
 
 @add_legacy_c_converter('C', accept={str})
 class int_converter(CConverter):
@@ -3757,28 +3849,31 @@ class int_converter(CConverter):
         if type is not None:
             self.type = type
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'i':
-            return """
+            return self.format_code("""
                 {paramname} = PyLong_AsInt({argname});
                 if ({paramname} == -1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
+                """,
+                argname=argname)
         elif self.format_unit == 'C':
-            return """
+            return self.format_code("""
                 if (!PyUnicode_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "a unicode character", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 if (PyUnicode_GET_LENGTH({argname}) != 1) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "a unicode character", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = PyUnicode_READ_CHAR({argname}, 0);
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'a unicode character', limited_capi=limited_capi),
+            )
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class unsigned_int_converter(CConverter):
     type = 'unsigned int'
@@ -3793,15 +3888,33 @@ class unsigned_int_converter(CConverter):
             self.add_include('pycore_long.h',
                              '_PyLong_UnsignedInt_Converter()')
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'I':
-            return """
+            return self.format_code("""
                 {paramname} = (unsigned int)PyLong_AsUnsignedLongMask({argname});
                 if ({paramname} == (unsigned int)-1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        # NOTE: Raises OverflowError for negative integer.
+        return self.format_code("""
+            {{{{
+                unsigned long uval = PyLong_AsUnsignedLong({argname});
+                if (uval == (unsigned long)-1 && PyErr_Occurred()) {{{{
+                    goto exit;
+                }}}}
+                if (uval > UINT_MAX) {{{{
+                    PyErr_SetString(PyExc_OverflowError,
+                                    "Python int too large for C unsigned int");
+                    goto exit;
+                }}}}
+                {paramname} = (unsigned int) uval;
+            }}}}
+            """,
+            argname=argname)
 
 class long_converter(CConverter):
     type = 'long'
@@ -3809,15 +3922,16 @@ class long_converter(CConverter):
     format_unit = 'l'
     c_ignored_default = "0"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'l':
-            return """
+            return self.format_code("""
                 {paramname} = PyLong_AsLong({argname});
                 if ({paramname} == -1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class unsigned_long_converter(CConverter):
     type = 'unsigned long'
@@ -3832,17 +3946,28 @@ class unsigned_long_converter(CConverter):
             self.add_include('pycore_long.h',
                              '_PyLong_UnsignedLong_Converter()')
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'k':
-            return """
+            return self.format_code("""
                 if (!PyLong_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "int", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = PyLong_AsUnsignedLongMask({argname});
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi),
+            )
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        # NOTE: Raises OverflowError for negative integer.
+        return self.format_code("""
+            {paramname} = PyLong_AsUnsignedLong({argname});
+            if ({paramname} == (unsigned long)-1 && PyErr_Occurred()) {{{{
+                goto exit;
+            }}}}
+            """,
+            argname=argname)
 
 class long_long_converter(CConverter):
     type = 'long long'
@@ -3850,15 +3975,16 @@ class long_long_converter(CConverter):
     format_unit = 'L'
     c_ignored_default = "0"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'L':
-            return """
+            return self.format_code("""
                 {paramname} = PyLong_AsLongLong({argname});
                 if ({paramname} == -1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class unsigned_long_long_converter(CConverter):
     type = 'unsigned long long'
@@ -3873,17 +3999,28 @@ class unsigned_long_long_converter(CConverter):
             self.add_include('pycore_long.h',
                              '_PyLong_UnsignedLongLong_Converter()')
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'K':
-            return """
+            return self.format_code("""
                 if (!PyLong_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "int", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = PyLong_AsUnsignedLongLongMask({argname});
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi),
+            )
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        # NOTE: Raises OverflowError for negative integer.
+        return self.format_code("""
+            {paramname} = PyLong_AsUnsignedLongLong({argname});
+            if ({paramname} == (unsigned long long)-1 && PyErr_Occurred()) {{{{
+                goto exit;
+            }}}}
+            """,
+            argname=argname)
 
 class Py_ssize_t_converter(CConverter):
     type = 'Py_ssize_t'
@@ -3901,12 +4038,16 @@ class Py_ssize_t_converter(CConverter):
         else:
             fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}")
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'n':
-            return """
+            if limited_capi:
+                PyNumber_Index = 'PyNumber_Index'
+            else:
+                PyNumber_Index = '_PyNumber_Index'
+            return self.format_code("""
                 {{{{
                     Py_ssize_t ival = -1;
-                    PyObject *iobj = _PyNumber_Index({argname});
+                    PyObject *iobj = {PyNumber_Index}({argname});
                     if (iobj != NULL) {{{{
                         ival = PyLong_AsSsize_t(iobj);
                         Py_DECREF(iobj);
@@ -3916,8 +4057,28 @@ class Py_ssize_t_converter(CConverter):
                     }}}}
                     {paramname} = ival;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                PyNumber_Index=PyNumber_Index)
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        return self.format_code("""
+            if ({argname} != Py_None) {{{{
+                if (PyIndex_Check({argname})) {{{{
+                    {paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError);
+                    if ({paramname} == -1 && PyErr_Occurred()) {{{{
+                        goto exit;
+                    }}}}
+                }}}}
+                else {{{{
+                    {bad_argument}
+                    goto exit;
+                }}}}
+            }}}}
+            """,
+            argname=argname,
+            bad_argument=self.bad_argument(displayname, 'integer or None', limited_capi=limited_capi),
+        )
 
 
 class slice_index_converter(CConverter):
@@ -3926,11 +4087,51 @@ class slice_index_converter(CConverter):
     def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None:
         if accept == {int}:
             self.converter = '_PyEval_SliceIndexNotNone'
+            self.nullable = False
         elif accept == {int, NoneType}:
             self.converter = '_PyEval_SliceIndex'
+            self.nullable = True
         else:
             fail(f"slice_index_converter: illegal 'accept' argument {accept!r}")
 
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        if self.nullable:
+            return self.format_code("""
+                if (!Py_IsNone({argname})) {{{{
+                    if (PyIndex_Check({argname})) {{{{
+                        {paramname} = PyNumber_AsSsize_t({argname}, NULL);
+                        if ({paramname} == -1 && PyErr_Occurred()) {{{{
+                            return 0;
+                        }}}}
+                    }}}}
+                    else {{{{
+                        PyErr_SetString(PyExc_TypeError,
+                                        "slice indices must be integers or "
+                                        "None or have an __index__ method");
+                        goto exit;
+                    }}}}
+                }}}}
+                """,
+                argname=argname)
+        else:
+            return self.format_code("""
+                if (PyIndex_Check({argname})) {{{{
+                    {paramname} = PyNumber_AsSsize_t({argname}, NULL);
+                    if ({paramname} == -1 && PyErr_Occurred()) {{{{
+                        goto exit;
+                    }}}}
+                }}}}
+                else {{{{
+                    PyErr_SetString(PyExc_TypeError,
+                                    "slice indices must be integers or "
+                                    "have an __index__ method");
+                    goto exit;
+                }}}}
+                """,
+                argname=argname)
+
 class size_t_converter(CConverter):
     type = 'size_t'
     converter = '_PyLong_Size_t_Converter'
@@ -3940,15 +4141,25 @@ class size_t_converter(CConverter):
         self.add_include('pycore_long.h',
                          '_PyLong_Size_t_Converter()')
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'n':
-            return """
+            return self.format_code("""
                 {paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError);
                 if ({paramname} == -1 && PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        if not limited_capi:
+            return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+        # NOTE: Raises OverflowError for negative integer.
+        return self.format_code("""
+            {paramname} = PyLong_AsSize_t({argname});
+            if ({paramname} == (size_t)-1 && PyErr_Occurred()) {{{{
+                goto exit;
+            }}}}
+            """,
+            argname=argname)
 
 
 class fildes_converter(CConverter):
@@ -3960,12 +4171,13 @@ class fildes_converter(CConverter):
                          '_PyLong_FileDescriptor_Converter()')
 
     def _parse_arg(self, argname: str, displayname: str) -> str | None:
-        return """
+        return self.format_code("""
             {paramname} = PyObject_AsFileDescriptor({argname});
             if ({paramname} == -1) {{{{
                 goto exit;
             }}}}
-            """.format(argname=argname, paramname=self.name)
+            """,
+            argname=argname)
 
 
 class float_converter(CConverter):
@@ -3974,9 +4186,9 @@ class float_converter(CConverter):
     format_unit = 'f'
     c_ignored_default = "0.0"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'f':
-            return """
+            return self.format_code("""
                 if (PyFloat_CheckExact({argname})) {{{{
                     {paramname} = (float) (PyFloat_AS_DOUBLE({argname}));
                 }}}}
@@ -3987,8 +4199,9 @@ class float_converter(CConverter):
                         goto exit;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class double_converter(CConverter):
     type = 'double'
@@ -3996,9 +4209,9 @@ class double_converter(CConverter):
     format_unit = 'd'
     c_ignored_default = "0.0"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'd':
-            return """
+            return self.format_code("""
                 if (PyFloat_CheckExact({argname})) {{{{
                     {paramname} = PyFloat_AS_DOUBLE({argname});
                 }}}}
@@ -4009,8 +4222,9 @@ class double_converter(CConverter):
                         goto exit;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 
 class Py_complex_converter(CConverter):
@@ -4019,15 +4233,16 @@ class Py_complex_converter(CConverter):
     format_unit = 'D'
     c_ignored_default = "{0.0, 0.0}"
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'D':
-            return """
+            return self.format_code("""
                 {paramname} = PyComplex_AsCComplex({argname});
                 if (PyErr_Occurred()) {{{{
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 
 class object_converter(CConverter):
@@ -4112,11 +4327,11 @@ class str_converter(CConverter):
         else:
             return ""
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 's':
-            return """
+            return self.format_code("""
                 if (!PyUnicode_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "str", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 Py_ssize_t {length_name};
@@ -4128,10 +4343,12 @@ class str_converter(CConverter):
                     PyErr_SetString(PyExc_ValueError, "embedded null character");
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname, length_name=self.length_name)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi),
+                length_name=self.length_name)
         if self.format_unit == 'z':
-            return """
+            return self.format_code("""
                 if ({argname} == Py_None) {{{{
                     {paramname} = NULL;
                 }}}}
@@ -4147,12 +4364,14 @@ class str_converter(CConverter):
                     }}}}
                 }}}}
                 else {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "str or None", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname, length_name=self.length_name)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'str or None', limited_capi=limited_capi),
+                length_name=self.length_name)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 #
 # This is the fourth or fifth rewrite of registering all the
@@ -4214,51 +4433,57 @@ class PyBytesObject_converter(CConverter):
     format_unit = 'S'
     # accept = {bytes}
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'S':
-            return """
+            return self.format_code("""
                 if (!PyBytes_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "bytes", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = ({type}){argname};
-                """.format(argname=argname, paramname=self.parser_name,
-                           type=self.type, displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'bytes', limited_capi=limited_capi),
+                type=self.type)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class PyByteArrayObject_converter(CConverter):
     type = 'PyByteArrayObject *'
     format_unit = 'Y'
     # accept = {bytearray}
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'Y':
-            return """
+            return self.format_code("""
                 if (!PyByteArray_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "bytearray", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = ({type}){argname};
-                """.format(argname=argname, paramname=self.parser_name,
-                           type=self.type, displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'bytearray', limited_capi=limited_capi),
+                type=self.type)
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 class unicode_converter(CConverter):
     type = 'PyObject *'
     default_type = (str, Null, NoneType)
     format_unit = 'U'
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'U':
-            return """
+            return self.format_code("""
                 if (!PyUnicode_Check({argname})) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "str", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 {paramname} = {argname};
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi),
+            )
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 @add_legacy_c_converter('u')
 @add_legacy_c_converter('u#', zeroes=True)
@@ -4292,25 +4517,26 @@ class Py_UNICODE_converter(CConverter):
         if self.length:
             return ""
         else:
-            return """\
-PyMem_Free((void *){name});
-""".format(name=self.name)
+            return f"""PyMem_Free((void *){self.parser_name});\n"""
 
-    def parse_arg(self, argname: str, argnum: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if not self.length:
             if self.accept == {str}:
-                return """
+                return self.format_code("""
                     if (!PyUnicode_Check({argname})) {{{{
-                        _PyArg_BadArgument("{{name}}", {argnum}, "str", {argname});
+                        {bad_argument}
                         goto exit;
                     }}}}
                     {paramname} = PyUnicode_AsWideCharString({argname}, NULL);
                     if ({paramname} == NULL) {{{{
                         goto exit;
                     }}}}
-                    """.format(argname=argname, paramname=self.name, argnum=argnum)
+                    """,
+                    argname=argname,
+                    bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi),
+                )
             elif self.accept == {str, NoneType}:
-                return """
+                return self.format_code("""
                     if ({argname} == Py_None) {{{{
                         {paramname} = NULL;
                     }}}}
@@ -4321,11 +4547,14 @@ PyMem_Free((void *){name});
                         }}}}
                     }}}}
                     else {{{{
-                        _PyArg_BadArgument("{{name}}", {argnum}, "str or None", {argname});
+                        {bad_argument}
                         goto exit;
                     }}}}
-                    """.format(argname=argname, paramname=self.name, argnum=argnum)
-        return super().parse_arg(argname, argnum)
+                    """,
+                    argname=argname,
+                    bad_argument=self.bad_argument(displayname, 'str or None', limited_capi=limited_capi),
+                )
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 @add_legacy_c_converter('s*', accept={str, buffer})
 @add_legacy_c_converter('z*', accept={str, buffer, NoneType})
@@ -4359,20 +4588,22 @@ class Py_buffer_converter(CConverter):
         name = self.name
         return "".join(["if (", name, ".obj) {\n   PyBuffer_Release(&", name, ");\n}\n"])
 
-    def parse_arg(self, argname: str, displayname: str) -> str | None:
+    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
         if self.format_unit == 'y*':
-            return """
+            return self.format_code("""
                 if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{
                     goto exit;
                 }}}}
                 if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi),
+            )
         elif self.format_unit == 's*':
-            return """
+            return self.format_code("""
                 if (PyUnicode_Check({argname})) {{{{
                     Py_ssize_t len;
                     const char *ptr = PyUnicode_AsUTF8AndSize({argname}, &len);
@@ -4386,26 +4617,30 @@ class Py_buffer_converter(CConverter):
                         goto exit;
                     }}}}
                     if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
-                        _PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
+                        {bad_argument}
                         goto exit;
                     }}}}
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi),
+            )
         elif self.format_unit == 'w*':
-            return """
+            return self.format_code("""
                 if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_WRITABLE) < 0) {{{{
-                    PyErr_Clear();
-                    _PyArg_BadArgument("{{name}}", {displayname}, "read-write bytes-like object", {argname});
+                    {bad_argument}
                     goto exit;
                 }}}}
                 if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
-                    _PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
+                    {bad_argument2}
                     goto exit;
                 }}}}
-                """.format(argname=argname, paramname=self.parser_name,
-                           displayname=displayname)
-        return super().parse_arg(argname, displayname)
+                """,
+                argname=argname,
+                bad_argument=self.bad_argument(displayname, 'read-write bytes-like object', limited_capi=limited_capi),
+                bad_argument2=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi),
+            )
+        return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 
 def correct_name_for_self(