https://github.com/python/cpython/commit/a005f323b7c8a7c9cd06b74d02a2d3bd7134841c
commit: a005f323b7c8a7c9cd06b74d02a2d3bd7134841c
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-03-17T10:55:15Z
summary:

[3.14] gh-144545: Improve handling of default values in Argument Clinic 
(GH-146016) (GH-146052)

* Add the c_init_default attribute which is used to initialize the C variable
  if the default is not explicitly provided.
* Add the c_default_init() method which is used to derive c_default from
  default if c_default is not explicitly provided.
* Explicit c_default and py_default are now almost always have precedence
  over the generated value.
* Add support for bytes literals as default values.
* Improve support for str literals as default values (support non-ASCII
  and non-printable characters and special characters like backslash or quotes).
* Fix support for str and bytes literals containing trigraphs, "/*" and "*/".
* Improve support for default values in converters "char" and 
"int(accept={str})".
* Converter "int(accept={str})" now requires 1-character string instead of
  integer as default value.
* Add support for non-None default values in converter "Py_buffer": NULL,
  str and bytes literals.
* Improve error handling for invalid default values.
* Rename Null to NullType for consistency.
(cherry picked from commit 99e2c5eccd2b83ac955125522a952a4ff5c7eb43)

files:
M Lib/test/clinic.test.c
M Lib/test/test_clinic.py
M Modules/_testclinic.c
M Modules/blake2module.c
M Modules/clinic/_testclinic.c.h
M Modules/clinic/blake2module.c.h
M Modules/clinic/zlibmodule.c.h
M Modules/posixmodule.c
M Modules/zlibmodule.c
M Objects/unicodeobject.c
M Tools/c-analyzer/cpython/_parser.py
M Tools/clinic/libclinic/__init__.py
M Tools/clinic/libclinic/clanguage.py
M Tools/clinic/libclinic/converter.py
M Tools/clinic/libclinic/converters.py
M Tools/clinic/libclinic/dsl_parser.py
M Tools/clinic/libclinic/formatting.py
M Tools/clinic/libclinic/utils.py

diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c
index 4a67fcd2c3e9b3..4cec427dbaa885 100644
--- a/Lib/test/clinic.test.c
+++ b/Lib/test/clinic.test.c
@@ -530,19 +530,19 @@ test_char_converter(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs)
 {
     PyObject *return_value = NULL;
     char a = 'A';
-    char b = '\x07';
-    char c = '\x08';
+    char b = '\a';
+    char c = '\b';
     char d = '\t';
     char e = '\n';
-    char f = '\x0b';
-    char g = '\x0c';
+    char f = '\v';
+    char g = '\f';
     char h = '\r';
     char i = '"';
     char j = '\'';
     char k = '?';
     char l = '\\';
-    char m = '\x00';
-    char n = '\xff';
+    char m = '\0';
+    char n = '\377';
 
     if (!_PyArg_CheckPositional("test_char_converter", nargs, 0, 14)) {
         goto exit;
@@ -936,7 +936,7 @@ static PyObject *
 test_char_converter_impl(PyObject *module, char a, char b, char c, char d,
                          char e, char f, char g, char h, char i, char j,
                          char k, char l, char m, char n)
-/*[clinic end generated code: output=ff11e203248582df input=e42330417a44feac]*/
+/*[clinic end generated code: output=6503d15448e1d4c4 input=e42330417a44feac]*/
 
 
 /*[clinic input]
@@ -1173,14 +1173,14 @@ test_int_converter
 
     a: int = 12
     b: int(accept={int}) = 34
-    c: int(accept={str}) = 45
+    c: int(accept={str}) = '-'
     d: int(type='myenum') = 67
     /
 
 [clinic start generated code]*/
 
 PyDoc_STRVAR(test_int_converter__doc__,
-"test_int_converter($module, a=12, b=34, c=45, d=67, /)\n"
+"test_int_converter($module, a=12, b=34, c=\'-\', d=67, /)\n"
 "--\n"
 "\n");
 
@@ -1196,7 +1196,7 @@ test_int_converter(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs)
     PyObject *return_value = NULL;
     int a = 12;
     int b = 34;
-    int c = 45;
+    int c = '-';
     myenum d = 67;
 
     if (!_PyArg_CheckPositional("test_int_converter", nargs, 0, 4)) {
@@ -1247,7 +1247,7 @@ test_int_converter(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs)
 
 static PyObject *
 test_int_converter_impl(PyObject *module, int a, int b, int c, myenum d)
-/*[clinic end generated code: output=fbcfb7554688663d input=d20541fc1ca0553e]*/
+/*[clinic end generated code: output=d5357b563bdb8789 input=5d8f4eb5899b24de]*/
 
 
 /*[clinic input]
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index b25108e0ff7903..73bb942af7c0a1 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -1044,6 +1044,187 @@ def test_param_with_continuations(self):
         p = function.parameters['follow_symlinks']
         self.assertEqual(True, p.default)
 
+    def test_param_default_none(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                obj: object = None
+                str: str(accept={str, NoneType}) = None
+                buf: Py_buffer(accept={str, buffer, NoneType}) = None
+            """)
+        p = function.parameters['obj']
+        self.assertIs(p.default, None)
+        self.assertEqual(p.converter.py_default, 'None')
+        self.assertEqual(p.converter.c_default, 'Py_None')
+
+        p = function.parameters['str']
+        self.assertIs(p.default, None)
+        self.assertEqual(p.converter.py_default, 'None')
+        self.assertEqual(p.converter.c_default, 'NULL')
+
+        p = function.parameters['buf']
+        self.assertIs(p.default, None)
+        self.assertEqual(p.converter.py_default, 'None')
+        self.assertEqual(p.converter.c_default, '{NULL, NULL}')
+
+    def test_param_default_null(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                obj: object = NULL
+                str: str = NULL
+                buf: Py_buffer = NULL
+                fsencoded: unicode_fs_encoded = NULL
+                fsdecoded: unicode_fs_decoded = NULL
+            """)
+        p = function.parameters['obj']
+        self.assertIs(p.default, NULL)
+        self.assertEqual(p.converter.py_default, '<unrepresentable>')
+        self.assertEqual(p.converter.c_default, 'NULL')
+
+        p = function.parameters['str']
+        self.assertIs(p.default, NULL)
+        self.assertEqual(p.converter.py_default, '<unrepresentable>')
+        self.assertEqual(p.converter.c_default, 'NULL')
+
+        p = function.parameters['buf']
+        self.assertIs(p.default, NULL)
+        self.assertEqual(p.converter.py_default, '<unrepresentable>')
+        self.assertEqual(p.converter.c_default, '{NULL, NULL}')
+
+        p = function.parameters['fsencoded']
+        self.assertIs(p.default, NULL)
+        self.assertEqual(p.converter.py_default, '<unrepresentable>')
+        self.assertEqual(p.converter.c_default, 'NULL')
+
+        p = function.parameters['fsdecoded']
+        self.assertIs(p.default, NULL)
+        self.assertEqual(p.converter.py_default, '<unrepresentable>')
+        self.assertEqual(p.converter.c_default, 'NULL')
+
+    def test_param_default_str_literal(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                str: str = ' \t\n\r\v\f\xa0'
+                buf: Py_buffer(accept={str, buffer}) = ' \t\n\r\v\f\xa0'
+            """)
+        p = function.parameters['str']
+        self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
+        self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
+        self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\u00a0"')
+
+        p = function.parameters['buf']
+        self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
+        self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
+        self.assertEqual(p.converter.c_default,
+                         r'{.buf = " \t\n\r\v\f\302\240", .obj = NULL, .len = 
8}')
+
+    def test_param_default_bytes_literal(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                str: str(accept={robuffer}) = b' \t\n\r\v\f\xa0'
+                buf: Py_buffer = b' \t\n\r\v\f\xa0'
+            """)
+        p = function.parameters['str']
+        self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
+        self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
+        self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\240"')
+
+        p = function.parameters['buf']
+        self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
+        self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
+        self.assertEqual(p.converter.c_default,
+                         r'{.buf = " \t\n\r\v\f\240", .obj = NULL, .len = 7}')
+
+    def test_param_default_byte_literal(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                zero: char = b'\0'
+                one: char = b'\1'
+                lf: char = b'\n'
+                nbsp: char = b'\xa0'
+            """)
+        p = function.parameters['zero']
+        self.assertEqual(p.default, b'\0')
+        self.assertEqual(p.converter.py_default, r"b'\x00'")
+        self.assertEqual(p.converter.c_default, r"'\0'")
+
+        p = function.parameters['one']
+        self.assertEqual(p.default, b'\1')
+        self.assertEqual(p.converter.py_default, r"b'\x01'")
+        self.assertEqual(p.converter.c_default, r"'\001'")
+
+        p = function.parameters['lf']
+        self.assertEqual(p.default, b'\n')
+        self.assertEqual(p.converter.py_default, r"b'\n'")
+        self.assertEqual(p.converter.c_default, r"'\n'")
+
+        p = function.parameters['nbsp']
+        self.assertEqual(p.default, b'\xa0')
+        self.assertEqual(p.converter.py_default, r"b'\xa0'")
+        self.assertEqual(p.converter.c_default, r"'\240'")
+
+    def test_param_default_unicode_char(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                zero: int(accept={str}) = '\0'
+                one: int(accept={str}) = '\1'
+                lf: int(accept={str}) = '\n'
+                nbsp: int(accept={str}) = '\xa0'
+                snake: int(accept={str}) = '\U0001f40d'
+            """)
+        p = function.parameters['zero']
+        self.assertEqual(p.default, '\0')
+        self.assertEqual(p.converter.py_default, r"'\x00'")
+        self.assertEqual(p.converter.c_default, '0')
+
+        p = function.parameters['one']
+        self.assertEqual(p.default, '\1')
+        self.assertEqual(p.converter.py_default, r"'\x01'")
+        self.assertEqual(p.converter.c_default, '0x01')
+
+        p = function.parameters['lf']
+        self.assertEqual(p.default, '\n')
+        self.assertEqual(p.converter.py_default, r"'\n'")
+        self.assertEqual(p.converter.c_default, r"'\n'")
+
+        p = function.parameters['nbsp']
+        self.assertEqual(p.default, '\xa0')
+        self.assertEqual(p.converter.py_default, r"'\xa0'")
+        self.assertEqual(p.converter.c_default, '0xa0')
+
+        p = function.parameters['snake']
+        self.assertEqual(p.default, '\U0001f40d')
+        self.assertEqual(p.converter.py_default, "'\U0001f40d'")
+        self.assertEqual(p.converter.c_default, '0x1f40d')
+
+    def test_param_default_bool(self):
+        function = self.parse_function(r"""
+            module test
+            test.func
+                bool: bool = True
+                intbool: bool(accept={int}) = True
+                intbool2: bool(accept={int}) = 2
+            """)
+        p = function.parameters['bool']
+        self.assertIs(p.default, True)
+        self.assertEqual(p.converter.py_default, 'True')
+        self.assertEqual(p.converter.c_default, '1')
+
+        p = function.parameters['intbool']
+        self.assertIs(p.default, True)
+        self.assertEqual(p.converter.py_default, 'True')
+        self.assertEqual(p.converter.c_default, '1')
+
+        p = function.parameters['intbool2']
+        self.assertEqual(p.default, 2)
+        self.assertEqual(p.converter.py_default, '2')
+        self.assertEqual(p.converter.c_default, '2')
+
     def test_param_default_expr_named_constant(self):
         function = self.parse_function("""
             module os
@@ -4209,6 +4390,56 @@ def test_format_escape(self):
         out = libclinic.format_escape(line)
         self.assertEqual(out, expected)
 
+    def test_c_bytes_repr(self):
+        c_bytes_repr = libclinic.c_bytes_repr
+        self.assertEqual(c_bytes_repr(b''), '""')
+        self.assertEqual(c_bytes_repr(b'abc'), '"abc"')
+        self.assertEqual(c_bytes_repr(b'\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
+        self.assertEqual(c_bytes_repr(b' \0\x7f'), r'" \000\177"')
+        self.assertEqual(c_bytes_repr(b'"'), r'"\""')
+        self.assertEqual(c_bytes_repr(b"'"), r'''"'"''')
+        self.assertEqual(c_bytes_repr(b'\\'), r'"\\"')
+        self.assertEqual(c_bytes_repr(b'??/'), r'"?\?/"')
+        self.assertEqual(c_bytes_repr(b'???/'), r'"?\?\?/"')
+        self.assertEqual(c_bytes_repr(b'/*****/ /*/ */*'), r'"/\*****\/ /\*\/ 
*\/\*"')
+        self.assertEqual(c_bytes_repr(b'\xa0'), r'"\240"')
+        self.assertEqual(c_bytes_repr(b'\xff'), r'"\377"')
+
+    def test_c_str_repr(self):
+        c_str_repr = libclinic.c_str_repr
+        self.assertEqual(c_str_repr(''), '""')
+        self.assertEqual(c_str_repr('abc'), '"abc"')
+        self.assertEqual(c_str_repr('\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
+        self.assertEqual(c_str_repr(' \0\x7f'), r'" \000\177"')
+        self.assertEqual(c_str_repr('"'), r'"\""')
+        self.assertEqual(c_str_repr("'"), r'''"'"''')
+        self.assertEqual(c_str_repr('\\'), r'"\\"')
+        self.assertEqual(c_str_repr('??/'), r'"?\?/"')
+        self.assertEqual(c_str_repr('???/'), r'"?\?\?/"')
+        self.assertEqual(c_str_repr('/*****/ /*/ */*'), r'"/\*****\/ /\*\/ 
*\/\*"')
+        self.assertEqual(c_str_repr('\xa0'), r'"\u00a0"')
+        self.assertEqual(c_str_repr('\xff'), r'"\u00ff"')
+        self.assertEqual(c_str_repr('\u20ac'), r'"\u20ac"')
+        self.assertEqual(c_str_repr('\U0001f40d'), r'"\U0001f40d"')
+
+    def test_c_unichar_repr(self):
+        c_unichar_repr = libclinic.c_unichar_repr
+        self.assertEqual(c_unichar_repr('a'), "'a'")
+        self.assertEqual(c_unichar_repr('\n'), r"'\n'")
+        self.assertEqual(c_unichar_repr('\b'), r"'\b'")
+        self.assertEqual(c_unichar_repr('\0'), '0')
+        self.assertEqual(c_unichar_repr('\1'), '0x01')
+        self.assertEqual(c_unichar_repr('\x7f'), '0x7f')
+        self.assertEqual(c_unichar_repr(' '), "' '")
+        self.assertEqual(c_unichar_repr('"'), """'"'""")
+        self.assertEqual(c_unichar_repr("'"), r"'\''")
+        self.assertEqual(c_unichar_repr('\\'), r"'\\'")
+        self.assertEqual(c_unichar_repr('?'), "'?'")
+        self.assertEqual(c_unichar_repr('\xa0'), '0xa0')
+        self.assertEqual(c_unichar_repr('\xff'), '0xff')
+        self.assertEqual(c_unichar_repr('\u20ac'), '0x20ac')
+        self.assertEqual(c_unichar_repr('\U0001f40d'), '0x1f40d')
+
     def test_indent_all_lines(self):
         # Blank lines are expected to be unchanged.
         self.assertEqual(libclinic.indent_all_lines("", prefix="bar"), "")
diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c
index 3e903b6d87d89f..1d23198dac52b2 100644
--- a/Modules/_testclinic.c
+++ b/Modules/_testclinic.c
@@ -334,14 +334,14 @@ int_converter
 
     a: int = 12
     b: int(accept={int}) = 34
-    c: int(accept={str}) = 45
+    c: int(accept={str}) = '-'
     /
 
 [clinic start generated code]*/
 
 static PyObject *
 int_converter_impl(PyObject *module, int a, int b, int c)
-/*[clinic end generated code: output=8e56b59be7d0c306 input=a1dbc6344853db7a]*/
+/*[clinic end generated code: output=8e56b59be7d0c306 input=9a306d4dc907e339]*/
 {
     RETURN_PACKED_ARGS(3, PyLong_FromLong, long, a, b, c);
 }
@@ -1360,6 +1360,7 @@ clone_f2_impl(PyObject *module, const char *path)
 class custom_t_converter(CConverter):
     type = 'custom_t'
     converter = 'custom_converter'
+    c_init_default = "<placeholder>"  # overridden in pre_render(()
 
     def pre_render(self):
         self.c_default = f'''{{
@@ -1367,7 +1368,7 @@ class custom_t_converter(CConverter):
         }}'''
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=b2fb801e99a06bf6]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=78fe84e5ecc0481b]*/
 
 
 /*[clinic input]
diff --git a/Modules/blake2module.c b/Modules/blake2module.c
index ae37e2d3383f9b..e31fa8131f1ecf 100644
--- a/Modules/blake2module.c
+++ b/Modules/blake2module.c
@@ -658,9 +658,9 @@ _blake2.blake2b.__new__ as py_blake2b_new
     data as data_obj: object(c_default="NULL") = b''
     *
     digest_size: int(c_default="HACL_HASH_BLAKE2B_OUT_BYTES") = 
_blake2.blake2b.MAX_DIGEST_SIZE
-    key: Py_buffer(c_default="NULL", py_default="b''") = None
-    salt: Py_buffer(c_default="NULL", py_default="b''") = None
-    person: Py_buffer(c_default="NULL", py_default="b''") = None
+    key: Py_buffer = b''
+    salt: Py_buffer = b''
+    person: Py_buffer = b''
     fanout: int = 1
     depth: int = 1
     leaf_size: unsigned_long = 0
@@ -681,7 +681,7 @@ py_blake2b_new_impl(PyTypeObject *type, PyObject *data_obj, 
int digest_size,
                     unsigned long long node_offset, int node_depth,
                     int inner_size, int last_node, int usedforsecurity,
                     PyObject *string)
-/*[clinic end generated code: output=de64bd850606b6a0 input=78cf60a2922d2f90]*/
+/*[clinic end generated code: output=de64bd850606b6a0 input=32832fb37d13c03d]*/
 {
     PyObject *data;
     if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
@@ -696,9 +696,9 @@ _blake2.blake2s.__new__ as py_blake2s_new
     data as data_obj: object(c_default="NULL") = b''
     *
     digest_size: int(c_default="HACL_HASH_BLAKE2S_OUT_BYTES") = 
_blake2.blake2s.MAX_DIGEST_SIZE
-    key: Py_buffer(c_default="NULL", py_default="b''") = None
-    salt: Py_buffer(c_default="NULL", py_default="b''") = None
-    person: Py_buffer(c_default="NULL", py_default="b''") = None
+    key: Py_buffer = b''
+    salt: Py_buffer = b''
+    person: Py_buffer = b''
     fanout: int = 1
     depth: int = 1
     leaf_size: unsigned_long = 0
@@ -719,7 +719,7 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data_obj, 
int digest_size,
                     unsigned long long node_offset, int node_depth,
                     int inner_size, int last_node, int usedforsecurity,
                     PyObject *string)
-/*[clinic end generated code: output=582a0c4295cc3a3c input=6843d6332eefd295]*/
+/*[clinic end generated code: output=582a0c4295cc3a3c input=da467fc9dae646bb]*/
 {
     PyObject *data;
     if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h
index 970528ce9ea46d..b652634892c27f 100644
--- a/Modules/clinic/_testclinic.c.h
+++ b/Modules/clinic/_testclinic.c.h
@@ -273,19 +273,19 @@ char_converter(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs)
 {
     PyObject *return_value = NULL;
     char a = 'A';
-    char b = '\x07';
-    char c = '\x08';
+    char b = '\a';
+    char c = '\b';
     char d = '\t';
     char e = '\n';
-    char f = '\x0b';
-    char g = '\x0c';
+    char f = '\v';
+    char g = '\f';
     char h = '\r';
     char i = '"';
     char j = '\'';
     char k = '?';
     char l = '\\';
-    char m = '\x00';
-    char n = '\xff';
+    char m = '\0';
+    char n = '\377';
 
     if (!_PyArg_CheckPositional("char_converter", nargs, 0, 14)) {
         goto exit;
@@ -860,7 +860,7 @@ unsigned_short_converter(PyObject *module, PyObject *const 
*args, Py_ssize_t nar
 }
 
 PyDoc_STRVAR(int_converter__doc__,
-"int_converter($module, a=12, b=34, c=45, /)\n"
+"int_converter($module, a=12, b=34, c=\'-\', /)\n"
 "--\n"
 "\n");
 
@@ -876,7 +876,7 @@ int_converter(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs)
     PyObject *return_value = NULL;
     int a = 12;
     int b = 34;
-    int c = 45;
+    int c = '-';
 
     if (!_PyArg_CheckPositional("int_converter", nargs, 0, 3)) {
         goto exit;
@@ -4481,4 +4481,4 @@ 
_testclinic_TestClass_posonly_poskw_varpos_array_no_fastcall(PyObject *type, PyO
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=84ffc31f27215baa input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8af194d826d6740d input=a9049054013a1b77]*/
diff --git a/Modules/clinic/blake2module.c.h b/Modules/clinic/blake2module.c.h
index 9e9cd56e569b24..556f344e34740b 100644
--- a/Modules/clinic/blake2module.c.h
+++ b/Modules/clinic/blake2module.c.h
@@ -63,9 +63,9 @@ py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject 
*kwargs)
     Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
     PyObject *data_obj = NULL;
     int digest_size = HACL_HASH_BLAKE2B_OUT_BYTES;
-    Py_buffer key = {NULL, NULL};
-    Py_buffer salt = {NULL, NULL};
-    Py_buffer person = {NULL, NULL};
+    Py_buffer key = {.buf = "", .obj = NULL, .len = 0};
+    Py_buffer salt = {.buf = "", .obj = NULL, .len = 0};
+    Py_buffer person = {.buf = "", .obj = NULL, .len = 0};
     int fanout = 1;
     int depth = 1;
     unsigned long leaf_size = 0;
@@ -272,9 +272,9 @@ py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject 
*kwargs)
     Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
     PyObject *data_obj = NULL;
     int digest_size = HACL_HASH_BLAKE2S_OUT_BYTES;
-    Py_buffer key = {NULL, NULL};
-    Py_buffer salt = {NULL, NULL};
-    Py_buffer person = {NULL, NULL};
+    Py_buffer key = {.buf = "", .obj = NULL, .len = 0};
+    Py_buffer salt = {.buf = "", .obj = NULL, .len = 0};
+    Py_buffer person = {.buf = "", .obj = NULL, .len = 0};
     int fanout = 1;
     int depth = 1;
     unsigned long leaf_size = 0;
@@ -502,4 +502,4 @@ _blake2_blake2b_hexdigest(PyObject *self, PyObject 
*Py_UNUSED(ignored))
 {
     return _blake2_blake2b_hexdigest_impl((Blake2Object *)self);
 }
-/*[clinic end generated code: output=eed18dcfaf6f7731 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=bf30e70c312718cb input=a9049054013a1b77]*/
diff --git a/Modules/clinic/zlibmodule.c.h b/Modules/clinic/zlibmodule.c.h
index 2710f65a840db9..64879097ac753a 100644
--- a/Modules/clinic/zlibmodule.c.h
+++ b/Modules/clinic/zlibmodule.c.h
@@ -205,7 +205,7 @@ zlib_decompress(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs, PyObj
 PyDoc_STRVAR(zlib_compressobj__doc__,
 "compressobj($module, /, level=Z_DEFAULT_COMPRESSION, method=DEFLATED,\n"
 "            wbits=MAX_WBITS, memLevel=DEF_MEM_LEVEL,\n"
-"            strategy=Z_DEFAULT_STRATEGY, zdict=None)\n"
+"            strategy=Z_DEFAULT_STRATEGY, zdict=<unrepresentable>)\n"
 "--\n"
 "\n"
 "Return a compressor object.\n"
@@ -1121,4 +1121,4 @@ zlib_crc32(PyObject *module, PyObject *const *args, 
Py_ssize_t nargs)
 #ifndef ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF
     #define ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF
 #endif /* !defined(ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF) */
-/*[clinic end generated code: output=33938c7613a8c1c7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=3611ce90fe05accb input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index bb9ef0e6da6c77..31b2d28200c4ab 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3057,25 +3057,22 @@ class path_t_converter(CConverter):
     type = "path_t"
     impl_by_reference = True
     parse_by_reference = True
+    default_type = ()
+    c_init_default = "<placeholder>"  # overridden in pre_render(()
 
     converter = 'path_converter'
 
     def converter_init(self, *, allow_fd=False, make_wide=None,
                        nonstrict=False, nullable=False,
                        suppress_value_error=False):
-        # right now path_t doesn't support default values.
-        # to support a default value, you'll need to override initialize().
-        if self.default not in (unspecified, None):
-            fail("Can't specify a default to the path_t converter!")
-
-        if self.c_default not in (None, 'Py_None'):
-            raise RuntimeError("Can't specify a c_default to the path_t 
converter!")
 
         self.nullable = nullable
         self.nonstrict = nonstrict
         self.make_wide = make_wide
         self.suppress_value_error = suppress_value_error
         self.allow_fd = allow_fd
+        if nullable:
+            self.default_type = NoneType
 
     def pre_render(self):
         def strify(value):
@@ -3110,6 +3107,8 @@ class path_t_converter(CConverter):
 
 class dir_fd_converter(CConverter):
     type = 'int'
+    default_type = NoneType
+    c_init_default = 'DEFAULT_DIR_FD'
 
     def converter_init(self, requires=None):
         if self.default in (unspecified, None):
@@ -3119,6 +3118,9 @@ class dir_fd_converter(CConverter):
         else:
             self.converter = 'dir_fd_converter'
 
+    def c_default_init(self):
+        self.c_default = 'DEFAULT_DIR_FD'
+
 class uid_t_converter(CConverter):
     type = "uid_t"
     converter = '_Py_Uid_Converter'
@@ -3199,7 +3201,7 @@ class confname_converter(CConverter):
         """, argname=argname, converter=self.converter, table=self.table)
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=d2759f2332cd39b3]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=d58f18bdf3bd3565]*/
 
 /*[clinic input]
 
diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c
index cb360f261608bd..5b6b0c5cac864a 100644
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -556,7 +556,7 @@ zlib.compressobj
     strategy: int(c_default="Z_DEFAULT_STRATEGY") = Z_DEFAULT_STRATEGY
         Used to tune the compression algorithm.  Possible values are
         Z_DEFAULT_STRATEGY, Z_FILTERED, and Z_HUFFMAN_ONLY.
-    zdict: Py_buffer = None
+    zdict: Py_buffer = NULL
         The predefined compression dictionary - a sequence of bytes
         containing subsequences that are likely to occur in the input data.
 
@@ -566,7 +566,7 @@ Return a compressor object.
 static PyObject *
 zlib_compressobj_impl(PyObject *module, int level, int method, int wbits,
                       int memLevel, int strategy, Py_buffer *zdict)
-/*[clinic end generated code: output=8b5bed9c8fc3814d input=2fa3d026f90ab8d5]*/
+/*[clinic end generated code: output=8b5bed9c8fc3814d input=1a6f61d8a8885c0d]*/
 {
     zlibstate *state = get_zlib_state(module);
     if (zdict->buf != NULL && (size_t)zdict->len > UINT_MAX) {
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 3835b8d462a10d..53f219eb185d77 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -87,14 +87,12 @@ class Py_UCS4_converter(CConverter):
     type = 'Py_UCS4'
     converter = 'convert_uc'
 
-    def converter_init(self):
-        if self.default is not unspecified:
-            self.c_default = ascii(self.default)
-            if len(self.c_default) > 4 or self.c_default[0] != "'":
-                self.c_default = hex(ord(self.default))
+    def c_default_init(self):
+        import libclinic
+        self.c_default = libclinic.c_unichar_repr(self.default)
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=88f5dd06cd8e7a61]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=22f057b68fd9a65a]*/
 
 /* --- Globals ------------------------------------------------------------
 
diff --git a/Tools/c-analyzer/cpython/_parser.py 
b/Tools/c-analyzer/cpython/_parser.py
index 2d6726faf7757a..f5dcd5c76c55f2 100644
--- a/Tools/c-analyzer/cpython/_parser.py
+++ b/Tools/c-analyzer/cpython/_parser.py
@@ -333,6 +333,7 @@ def format_tsv_lines(lines):
     _abs('Modules/_ssl_data_300.h'): (80_000, 10_000),
     _abs('Modules/_ssl_data_111.h'): (80_000, 10_000),
     _abs('Modules/cjkcodecs/mappings_*.h'): (160_000, 2_000),
+    _abs('Modules/clinic/_testclinic.c.h'): (120_000, 5_000),
     _abs('Modules/unicodedata_db.h'): (180_000, 3_000),
     _abs('Modules/unicodename_db.h'): (1_200_000, 15_000),
     _abs('Objects/unicodetype_db.h'): (240_000, 3_000),
diff --git a/Tools/clinic/libclinic/__init__.py 
b/Tools/clinic/libclinic/__init__.py
index 7c5cede2396677..742f1448146a0f 100644
--- a/Tools/clinic/libclinic/__init__.py
+++ b/Tools/clinic/libclinic/__init__.py
@@ -7,7 +7,9 @@
 )
 from .formatting import (
     SIG_END_MARKER,
-    c_repr,
+    c_str_repr,
+    c_bytes_repr,
+    c_unichar_repr,
     docstring_for_c_string,
     format_escape,
     indent_all_lines,
@@ -26,7 +28,7 @@
 from .utils import (
     FormatCounterFormatter,
     NULL,
-    Null,
+    NullType,
     Sentinels,
     VersionTuple,
     compute_checksum,
@@ -45,7 +47,9 @@
 
     # Formatting helpers
     "SIG_END_MARKER",
-    "c_repr",
+    "c_str_repr",
+    "c_bytes_repr",
+    "c_unichar_repr",
     "docstring_for_c_string",
     "format_escape",
     "indent_all_lines",
@@ -64,7 +68,7 @@
     # Utility functions
     "FormatCounterFormatter",
     "NULL",
-    "Null",
+    "NullType",
     "Sentinels",
     "VersionTuple",
     "compute_checksum",
diff --git a/Tools/clinic/libclinic/clanguage.py 
b/Tools/clinic/libclinic/clanguage.py
index 341667d2f0bff9..7f02c7790f015a 100644
--- a/Tools/clinic/libclinic/clanguage.py
+++ b/Tools/clinic/libclinic/clanguage.py
@@ -101,7 +101,7 @@ def compiler_deprecated_warning(
         code = self.COMPILER_DEPRECATION_WARNING_PROTOTYPE.format(
             major=minversion[0],
             minor=minversion[1],
-            message=libclinic.c_repr(message),
+            message=libclinic.c_str_repr(message),
         )
         return libclinic.normalize_snippet(code)
 
diff --git a/Tools/clinic/libclinic/converter.py 
b/Tools/clinic/libclinic/converter.py
index 2c93dda3541030..3d375dd3fdd70d 100644
--- a/Tools/clinic/libclinic/converter.py
+++ b/Tools/clinic/libclinic/converter.py
@@ -6,7 +6,7 @@
 
 import libclinic
 from libclinic import fail
-from libclinic import Sentinels, unspecified, unknown
+from libclinic import Sentinels, unspecified, unknown, NULL
 from libclinic.codegen import CRenderData, Include, TemplateDict
 from libclinic.function import Function, Parameter
 
@@ -83,9 +83,9 @@ class CConverter(metaclass=CConverterAutoRegister):
     # at runtime).
     default: object = unspecified
 
-    # If not None, default must be isinstance() of this type.
+    # default must be isinstance() of this type.
     # (You can also specify a tuple of types.)
-    default_type: bltns.type[object] | tuple[bltns.type[object], ...] | None = 
None
+    default_type: bltns.type[object] | tuple[bltns.type[object], ...] = object
 
     # "default" converted into a C value, as a string.
     # Or None if there is no default.
@@ -95,6 +95,13 @@ class CConverter(metaclass=CConverterAutoRegister):
     # Or None if there is no default.
     py_default: str | None = None
 
+    # The default value used to initialize the C variable when
+    # there is no default.
+    #
+    # Every non-abstract subclass with non-trivial cleanup() should supply
+    # a valid value.
+    c_init_default: str = ''
+
     # The default value used to initialize the C variable when
     # there is no default, but not specifying a default may
     # result in an "uninitialized variable" warning.  This can
@@ -105,7 +112,7 @@ class CConverter(metaclass=CConverterAutoRegister):
     #
     # This value is specified as a string.
     # Every non-abstract subclass should supply a valid value.
-    c_ignored_default: str = 'NULL'
+    c_ignored_default: str = ''
 
     # If true, wrap with Py_UNUSED.
     unused = False
@@ -182,9 +189,25 @@ def __init__(self,
         self.unused = unused
         self._includes: list[Include] = []
 
+        if c_default:
+            self.c_default = c_default
+        if py_default:
+            self.py_default = py_default
+
+        if annotation is not unspecified:
+            fail("The 'annotation' parameter is not currently permitted.")
+
+        # Make sure not to set self.function until after converter_init() has 
been called.
+        # This prevents you from caching information
+        # about the function in converter_init().
+        # (That breaks if we get cloned.)
+        self.converter_init(**kwargs)
+
         if default is not unspecified:
-            if (self.default_type
-                and default is not unknown
+            if self.default_type == ():
+                conv_name = self.__class__.__name__.removesuffix('_converter')
+                fail(f"A '{conv_name}' parameter cannot be marked optional.")
+            if (default is not unknown
                 and not isinstance(default, self.default_type)
             ):
                 if isinstance(self.default_type, type):
@@ -197,19 +220,38 @@ def __init__(self,
                      f"{name!r} is not of type {types_str!r}")
             self.default = default
 
-        if c_default:
-            self.c_default = c_default
-        if py_default:
-            self.py_default = py_default
-
-        if annotation is not unspecified:
-            fail("The 'annotation' parameter is not currently permitted.")
+        if not self.c_default:
+            if default is unspecified:
+                if self.c_init_default:
+                    self.c_default = self.c_init_default
+            elif default is NULL:
+                self.c_default = self.c_ignored_default or self.c_init_default
+                if not self.c_default:
+                    cls_name = self.__class__.__name__
+                    fail(f"{cls_name}: c_default is required for "
+                         f"default value NULL")
+            else:
+                assert default is not unknown
+                self.c_default_init()
+                if not self.c_default:
+                    if default is None:
+                        self.c_default = self.c_init_default
+                        if not self.c_default:
+                            cls_name = self.__class__.__name__
+                            fail(f"{cls_name}: c_default is required for "
+                                 f"default value None")
+                    elif isinstance(default, str):
+                        self.c_default = libclinic.c_str_repr(default)
+                    elif isinstance(default, bytes):
+                        self.c_default = libclinic.c_bytes_repr(default)
+                    elif isinstance(default, (int, float)):
+                        self.c_default = repr(default)
+                    else:
+                        cls_name = self.__class__.__name__
+                        fail(f"{cls_name}: c_default is required for "
+                             f"default value {default!r}")
+                        fail(f"Unsupported default value {default!r}.")
 
-        # Make sure not to set self.function until after converter_init() has 
been called.
-        # This prevents you from caching information
-        # about the function in converter_init().
-        # (That breaks if we get cloned.)
-        self.converter_init(**kwargs)
         self.function = function
 
     # Add a custom __getattr__ method to improve the error message
@@ -233,6 +275,9 @@ def __getattr__(self, attr):
     def converter_init(self) -> None:
         pass
 
+    def c_default_init(self) -> None:
+        return
+
     def is_optional(self) -> bool:
         return (self.default is not unspecified)
 
@@ -324,7 +369,7 @@ def parse_argument(self, args: list[str]) -> None:
             args.append(self.converter)
 
         if self.encoding:
-            args.append(libclinic.c_repr(self.encoding))
+            args.append(libclinic.c_str_repr(self.encoding))
         elif self.subclass_of:
             args.append(self.subclass_of)
 
@@ -371,7 +416,7 @@ def declaration(self, *, in_parser: bool = False) -> str:
         declaration = [self.simple_declaration(in_parser=True)]
         default = self.c_default
         if not default and self.parameter.group:
-            default = self.c_ignored_default
+            default = self.c_ignored_default or self.c_init_default
         if default:
             declaration.append(" = ")
             declaration.append(default)
diff --git a/Tools/clinic/libclinic/converters.py 
b/Tools/clinic/libclinic/converters.py
index 8c92b766ba0862..64fc1e95007516 100644
--- a/Tools/clinic/libclinic/converters.py
+++ b/Tools/clinic/libclinic/converters.py
@@ -4,7 +4,7 @@
 from types import NoneType
 from typing import Any
 
-from libclinic import fail, Null, unspecified, unknown
+from libclinic import fail, NullType, unspecified, NULL, c_bytes_repr, 
c_unichar_repr
 from libclinic.function import (
     Function, Parameter,
     CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
@@ -18,6 +18,9 @@
 
 
 class BaseUnsignedIntConverter(CConverter):
+    bitwise = False
+    default_type = int
+    c_ignored_default = '0'
 
     def use_converter(self) -> None:
         if self.converter:
@@ -74,12 +77,13 @@ class bool_converter(CConverter):
     def converter_init(self, *, accept: TypeSet = {object}) -> None:
         if accept == {int}:
             self.format_unit = 'i'
+            self.default_type = int  # type: ignore[assignment]
         elif accept != {object}:
             fail(f"bool_converter: illegal 'accept' argument {accept!r}")
-        if self.default is not unspecified and self.default is not unknown:
-            self.default = bool(self.default)
-            if self.c_default in {'Py_True', 'Py_False'}:
-                self.c_default = str(int(self.default))
+
+    def c_default_init(self) -> None:
+        assert isinstance(self.default, int)
+        self.c_default = str(int(self.default))
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'i':
@@ -107,6 +111,7 @@ class defining_class_converter(CConverter):
     this is the default converter used for the defining class.
     """
     type = 'PyTypeObject *'
+    default_type = ()
     format_unit = ''
     show_in_signature = False
     specified_type: str | None = None
@@ -123,7 +128,7 @@ def set_template_dict(self, template_dict: TemplateDict) -> 
None:
 
 class char_converter(CConverter):
     type = 'char'
-    default_type = (bytes, bytearray)
+    default_type = bytes
     format_unit = 'c'
     c_ignored_default = "'\0'"
 
@@ -132,9 +137,18 @@ def converter_init(self) -> None:
             if len(self.default) != 1:
                 fail(f"char_converter: illegal default value {self.default!r}")
 
-            self.c_default = repr(bytes(self.default))[1:]
-            if self.c_default == '"\'"':
-                self.c_default = r"'\''"
+    def c_default_init(self) -> None:
+        default = self.default
+        assert isinstance(default, bytes)
+        if default == b"'":
+            self.c_default = r"'\''"
+        elif default == b'"':
+            self.c_default = r"""'"'"""
+        elif default == b'\0':
+            self.c_default = r"'\0'"
+        else:
+            r = c_bytes_repr(default)[1:-1]
+            self.c_default = "'" + r + "'"
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'c':
@@ -174,7 +188,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 @add_legacy_c_converter('B', bitwise=True)
 class unsigned_char_converter(CConverter):
     type = 'unsigned char'
-    default_type = int
     format_unit = 'b'
     c_ignored_default = "'\0'"
 
@@ -261,8 +274,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class unsigned_short_converter(BaseUnsignedIntConverter):
     type = 'unsigned short'
-    default_type = int
-    c_ignored_default = "0"
 
     def converter_init(self, *, bitwise: bool = False) -> None:
         if bitwise:
@@ -294,11 +305,19 @@ def converter_init(
     ) -> None:
         if accept == {str}:
             self.format_unit = 'C'
+            self.default_type = str  # type: ignore[assignment]
+            if isinstance(self.default, str):
+                if len(self.default) != 1:
+                    fail(f"int_converter: illegal default value 
{self.default!r}")
         elif accept != {int}:
             fail(f"int_converter: illegal 'accept' argument {accept!r}")
         if type is not None:
             self.type = type
 
+    def c_default_init(self) -> None:
+        if isinstance(self.default, str):
+            self.c_default = c_unichar_repr(self.default)
+
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'i':
             return self.format_code("""
@@ -332,8 +351,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class unsigned_int_converter(BaseUnsignedIntConverter):
     type = 'unsigned int'
-    default_type = int
-    c_ignored_default = "0"
 
     def converter_init(self, *, bitwise: bool = False) -> None:
         if bitwise:
@@ -373,8 +390,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class unsigned_long_converter(BaseUnsignedIntConverter):
     type = 'unsigned long'
-    default_type = int
-    c_ignored_default = "0"
 
     def converter_init(self, *, bitwise: bool = False) -> None:
         if bitwise:
@@ -417,8 +432,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class unsigned_long_long_converter(BaseUnsignedIntConverter):
     type = 'unsigned long long'
-    default_type = int
-    c_ignored_default = "0"
 
     def converter_init(self, *, bitwise: bool = False) -> None:
         if bitwise:
@@ -443,12 +456,13 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class Py_ssize_t_converter(CConverter):
     type = 'Py_ssize_t'
+    default_type = (int, NoneType)
     c_ignored_default = "0"
 
     def converter_init(self, *, accept: TypeSet = {int}) -> None:
         if accept == {int}:
             self.format_unit = 'n'
-            self.default_type = int
+            self.default_type = int  # type: ignore[assignment]
         elif accept == {int, NoneType}:
             self.converter = '_Py_convert_optional_to_ssize_t'
         else:
@@ -505,10 +519,13 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class slice_index_converter(CConverter):
     type = 'Py_ssize_t'
+    default_type = (int, NoneType)
+    c_ignored_default = "0"
 
     def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None:
         if accept == {int}:
             self.converter = '_PyEval_SliceIndexNotNone'
+            self.default_type = int  # type: ignore[assignment]
             self.nullable = False
         elif accept == {int, NoneType}:
             self.converter = '_PyEval_SliceIndex'
@@ -558,7 +575,6 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 class size_t_converter(BaseUnsignedIntConverter):
     type = 'size_t'
     converter = '_PyLong_Size_t_Converter'
-    c_ignored_default = "0"
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'n':
@@ -677,6 +693,7 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 class object_converter(CConverter):
     type = 'PyObject *'
     format_unit = 'O'
+    c_ignored_default = 'NULL'
 
     def converter_init(
             self, *,
@@ -696,6 +713,10 @@ def converter_init(
         if type is not None:
             self.type = type
 
+    def c_default_init(self) -> None:
+        default = self.default
+        if default is None or isinstance(default, bool):
+            self.c_default = "Py_" + repr(default)
 
 #
 # We define three conventions for buffer types in the 'accept' argument:
@@ -725,8 +746,9 @@ def str_converter_key(
 
 class str_converter(CConverter):
     type = 'const char *'
-    default_type = (str, Null, NoneType)
+    default_type = (str, bytes, NullType, NoneType)
     format_unit = 's'
+    c_ignored_default = 'NULL'
 
     def converter_init(
             self,
@@ -744,14 +766,16 @@ def converter_init(
         self.format_unit = format_unit
         self.length = bool(zeroes)
         if encoding:
-            if self.default not in (Null, None, unspecified):
+            if self.default not in (NULL, None, unspecified):
                 fail("str_converter: Argument Clinic doesn't support default 
values for encoded strings")
             self.encoding = encoding
             self.type = 'char *'
             # sorry, clinic can't support preallocated buffers
             # for es# and et#
             self.c_default = "NULL"
-        if NoneType in accept and self.c_default == "Py_None":
+
+    def c_default_init(self) -> None:
+        if self.default is None:
             self.c_default = "NULL"
 
     def post_parsing(self) -> str:
@@ -864,6 +888,7 @@ class PyBytesObject_converter(CConverter):
     type = 'PyBytesObject *'
     format_unit = 'S'
     # accept = {bytes}
+    c_ignored_default = 'NULL'
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'S':
@@ -884,6 +909,7 @@ class PyByteArrayObject_converter(CConverter):
     type = 'PyByteArrayObject *'
     format_unit = 'Y'
     # accept = {bytearray}
+    c_ignored_default = 'NULL'
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'Y':
@@ -902,8 +928,9 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class unicode_converter(CConverter):
     type = 'PyObject *'
-    default_type = (str, Null, NoneType)
+    default_type = (str, NullType, NoneType)
     format_unit = 'U'
+    c_ignored_default = 'NULL'
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
         if self.format_unit == 'U':
@@ -922,11 +949,11 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 
 class _unicode_fs_converter_base(CConverter):
     type = 'PyObject *'
+    default_type = NullType
+    c_init_default = 'NULL'
 
-    def converter_init(self) -> None:
-        if self.default is not unspecified:
-            fail(f"{self.__class__.__name__} does not support default values")
-        self.c_default = 'NULL'
+    def c_default_init(self) -> None:
+        fail(f"{self.__class__.__name__} does not support default values")
 
     def cleanup(self) -> str:
         return f"Py_XDECREF({self.parser_name});"
@@ -946,7 +973,8 @@ class 
unicode_fs_decoded_converter(_unicode_fs_converter_base):
 @add_legacy_c_converter('Z#', accept={str, NoneType}, zeroes=True)
 class Py_UNICODE_converter(CConverter):
     type = 'const wchar_t *'
-    default_type = (str, Null, NoneType)
+    default_type = (str, NullType, NoneType)
+    c_ignored_default = 'NULL'
 
     def converter_init(
             self, *,
@@ -962,6 +990,7 @@ def converter_init(
             self.accept = accept
             if accept == {str}:
                 self.converter = '_PyUnicode_WideCharString_Converter'
+                self.default_type = (str, NullType)  # type: ignore[assignment]
             elif accept == {str, NoneType}:
                 self.converter = '_PyUnicode_WideCharString_Opt_Converter'
             else:
@@ -1017,28 +1046,34 @@ def parse_arg(self, argname: str, displayname: str, *, 
limited_capi: bool) -> st
 @add_legacy_c_converter('w*', accept={rwbuffer})
 class Py_buffer_converter(CConverter):
     type = 'Py_buffer'
+    default_type = (str, bytes, NullType, NoneType)
     format_unit = 'y*'
     impl_by_reference = True
-    c_ignored_default = "{NULL, NULL}"
+    c_init_default = "{NULL, NULL}"
 
     def converter_init(self, *, accept: TypeSet = {buffer}) -> None:
-        if self.default not in (unspecified, None):
-            fail("The only legal default value for Py_buffer is None.")
-
-        self.c_default = self.c_ignored_default
-
         if accept == {str, buffer, NoneType}:
-            format_unit = 'z*'
+            self.format_unit = 'z*'
+            self.default_type = (str, bytes, NullType, NoneType)
         elif accept == {str, buffer}:
-            format_unit = 's*'
+            self.format_unit = 's*'
+            self.default_type = (str, bytes, NullType)  # type: 
ignore[assignment]
         elif accept == {buffer}:
-            format_unit = 'y*'
+            self.format_unit = 'y*'
+            self.default_type = (bytes, NullType)  # type: ignore[assignment]
         elif accept == {rwbuffer}:
-            format_unit = 'w*'
+            self.format_unit = 'w*'
+            self.default_type = NullType  # type: ignore[assignment]
         else:
             fail("Py_buffer_converter: illegal combination of arguments")
 
-        self.format_unit = format_unit
+    def c_default_init(self) -> None:
+        default = self.default
+        if isinstance(default, bytes):
+            self.c_default = f'{{.buf = {c_bytes_repr(default)}, .obj = NULL, 
.len = {len(default)}}}'
+        elif isinstance(default, str):
+            default = default.encode()
+            self.c_default = f'{{.buf = {c_bytes_repr(default)}, .obj = NULL, 
.len = {len(default)}}}'
 
     def cleanup(self) -> str:
         name = self.name
@@ -1119,6 +1154,7 @@ class self_converter(CConverter):
     this is the default converter used for "self".
     """
     type: str | None = None
+    default_type = ()
     format_unit = ''
     specified_type: str | None = None
 
@@ -1233,6 +1269,7 @@ def use_pyobject_self(self, func: Function) -> bool:
 # Converters for var-positional parameter.
 
 class VarPosCConverter(CConverter):
+    default_type = ()
     format_unit = ''
 
     def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) 
-> str | None:
@@ -1245,8 +1282,7 @@ def parse_vararg(self, *, pos_only: int, min_pos: int, 
max_pos: int,
 
 class varpos_tuple_converter(VarPosCConverter):
     type = 'PyObject *'
-    format_unit = ''
-    c_default = 'NULL'
+    c_init_default = 'NULL'
 
     def cleanup(self) -> str:
         return f"""Py_XDECREF({self.parser_name});\n"""
@@ -1304,7 +1340,6 @@ def parse_vararg(self, *, pos_only: int, min_pos: int, 
max_pos: int,
 class varpos_array_converter(VarPosCConverter):
     type = 'PyObject * const *'
     length = True
-    c_ignored_default = ''
 
     def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int,
                      fastcall: bool, limited_capi: bool) -> str:
diff --git a/Tools/clinic/libclinic/dsl_parser.py 
b/Tools/clinic/libclinic/dsl_parser.py
index eca41531f7c8e9..6ead9bf2022833 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -7,7 +7,7 @@
 import shlex
 import sys
 from collections.abc import Callable
-from types import FunctionType, NoneType
+from types import FunctionType
 from typing import TYPE_CHECKING, Any, NamedTuple
 
 import libclinic
@@ -914,16 +914,17 @@ def parse_parameter(self, line: str) -> None:
             name = 'varpos_' + name
 
         value: object
+        has_c_default = 'c_default' in kwargs
         if not function_args.defaults:
-            if is_vararg:
-                value = NULL
-            else:
-                if self.parameter_state is ParamState.OPTIONAL:
-                    fail(f"Can't have a parameter without a default 
({parameter_name!r}) "
-                          "after a parameter with a default!")
-                value = unspecified
+            value = unspecified
+            if (not is_vararg
+                    and self.parameter_state is ParamState.OPTIONAL):
+                fail(f"Can't have a parameter without a default 
({parameter_name!r}) "
+                     "after a parameter with a default!")
             if 'py_default' in kwargs:
                 fail("You can't specify py_default without specifying a 
default value!")
+            if has_c_default:
+                fail("You can't specify c_default without specifying a default 
value!")
         else:
             expr = function_args.defaults[0]
             default = ast_input[expr.col_offset: expr.end_col_offset].strip()
@@ -932,7 +933,7 @@ def parse_parameter(self, line: str) -> None:
                 self.parameter_state = ParamState.OPTIONAL
             bad = False
             try:
-                if 'c_default' not in kwargs:
+                if not has_c_default:
                     # we can only represent very simple data values in C.
                     # detect whether default is okay, via a denylist
                     # of disallowed ast nodes.
@@ -978,18 +979,15 @@ def bad_node(self, node: ast.AST) -> None:
                     fail(f"Unsupported expression as default value: 
{default!r}")
 
                 # mild hack: explicitly support NULL as a default value
-                c_default: str | None
                 if isinstance(expr, ast.Name) and expr.id == 'NULL':
                     value = NULL
                     py_default = '<unrepresentable>'
-                    c_default = "NULL"
                 elif (isinstance(expr, ast.BinOp) or
                     (isinstance(expr, ast.UnaryOp) and
                      not (isinstance(expr.operand, ast.Constant) and
                           type(expr.operand.value) in {int, float, complex})
                     )):
-                    c_default = kwargs.get("c_default")
-                    if not (isinstance(c_default, str) and c_default):
+                    if not has_c_default:
                         fail(f"When you specify an expression ({default!r}) "
                              f"as your default value, "
                              f"you MUST specify a valid c_default.",
@@ -1008,8 +1006,7 @@ def bad_node(self, node: ast.AST) -> None:
                     a.append(n.id)
                     py_default = ".".join(reversed(a))
 
-                    c_default = kwargs.get("c_default")
-                    if not (isinstance(c_default, str) and c_default):
+                    if not has_c_default:
                         fail(f"When you specify a named constant 
({py_default!r}) "
                              "as your default value, "
                              "you MUST specify a valid c_default.")
@@ -1021,23 +1018,15 @@ def bad_node(self, node: ast.AST) -> None:
                 else:
                     value = ast.literal_eval(expr)
                     py_default = repr(value)
-                    if isinstance(value, (bool, NoneType)):
-                        c_default = "Py_" + py_default
-                    elif isinstance(value, str):
-                        c_default = libclinic.c_repr(value)
-                    else:
-                        c_default = py_default
 
             except (ValueError, AttributeError):
                 value = unknown
-                c_default = kwargs.get("c_default")
                 py_default = default
-                if not (isinstance(c_default, str) and c_default):
+                if not has_c_default:
                     fail("When you specify a named constant "
                          f"({py_default!r}) as your default value, "
                          "you MUST specify a valid c_default.")
 
-            kwargs.setdefault('c_default', c_default)
             kwargs.setdefault('py_default', py_default)
 
         dict = legacy_converters if legacy else converters
@@ -1058,12 +1047,10 @@ def bad_node(self, node: ast.AST) -> None:
 
         if isinstance(converter, self_converter):
             if len(self.function.parameters) == 1:
-                if self.parameter_state is not ParamState.REQUIRED:
-                    fail("A 'self' parameter cannot be marked optional.")
-                if value is not unspecified:
-                    fail("A 'self' parameter cannot have a default value.")
                 if self.group:
                     fail("A 'self' parameter cannot be in an optional group.")
+                assert self.parameter_state is ParamState.REQUIRED
+                assert value is unspecified
                 kind = inspect.Parameter.POSITIONAL_ONLY
                 self.parameter_state = ParamState.START
                 self.function.parameters.clear()
@@ -1074,14 +1061,12 @@ def bad_node(self, node: ast.AST) -> None:
         if isinstance(converter, defining_class_converter):
             _lp = len(self.function.parameters)
             if _lp == 1:
-                if self.parameter_state is not ParamState.REQUIRED:
-                    fail("A 'defining_class' parameter cannot be marked 
optional.")
-                if value is not unspecified:
-                    fail("A 'defining_class' parameter cannot have a default 
value.")
                 if self.group:
                     fail("A 'defining_class' parameter cannot be in an 
optional group.")
                 if self.function.cls is None:
                     fail("A 'defining_class' parameter cannot be defined at 
module level.")
+                assert self.parameter_state is ParamState.REQUIRED
+                assert value is unspecified
                 kind = inspect.Parameter.POSITIONAL_ONLY
             else:
                 fail("A 'defining_class' parameter, if specified, must either "
diff --git a/Tools/clinic/libclinic/formatting.py 
b/Tools/clinic/libclinic/formatting.py
index 873ece6210017a..264327818c1d19 100644
--- a/Tools/clinic/libclinic/formatting.py
+++ b/Tools/clinic/libclinic/formatting.py
@@ -39,8 +39,55 @@ def _quoted_for_c_string(text: str) -> str:
     return text
 
 
-def c_repr(text: str) -> str:
-    return '"' + text + '"'
+# Use octals, because \x... in C has arbitrary number of hexadecimal digits.
+_c_repr = [chr(i) if 32 <= i < 127 else fr'\{i:03o}' for i in range(256)]
+_c_repr[ord('"')] = r'\"'
+_c_repr[ord('\\')] = r'\\'
+_c_repr[ord('\a')] = r'\a'
+_c_repr[ord('\b')] = r'\b'
+_c_repr[ord('\f')] = r'\f'
+_c_repr[ord('\n')] = r'\n'
+_c_repr[ord('\r')] = r'\r'
+_c_repr[ord('\t')] = r'\t'
+_c_repr[ord('\v')] = r'\v'
+
+def _break_trigraphs(s: str) -> str:
+    # Prevent trigraphs from being interpreted inside string literals.
+    if '??' in s:
+        s = s.replace('??', r'?\?')
+        s = s.replace(r'\??', r'\?\?')
+    # Also Argument Clinic does not like comment-like sequences
+    # in string literals.
+    s = s.replace(r'/*', r'/\*')
+    s = s.replace(r'*/', r'*\/')
+    return s
+
+def c_bytes_repr(data: bytes) -> str:
+    r = ''.join(_c_repr[i] for i in data)
+    r = _break_trigraphs(r)
+    return '"' + r + '"'
+
+def c_str_repr(text: str) -> str:
+    r = ''.join(_c_repr[i] if i < 0x80
+                else fr'\u{i:04x}' if i < 0x10000
+                else fr'\U{i:08x}'
+                for i in map(ord, text))
+    r = _break_trigraphs(r)
+    return '"' + r + '"'
+
+def c_unichar_repr(char: str) -> str:
+    if char == "'":
+        return r"'\''"
+    if char == '"':
+        return """'"'"""
+    if char == '\0':
+        return '0'
+    i = ord(char)
+    if i < 0x80:
+        r = _c_repr[i]
+        if not r.startswith((r'\0', r'\1')):
+            return "'" + r + "'"
+    return f'0x{i:02x}'
 
 
 def wrapped_c_string_literal(
@@ -58,8 +105,8 @@ def wrapped_c_string_literal(
         drop_whitespace=False,
         break_on_hyphens=False,
     )
-    separator = c_repr(suffix + "\n" + subsequent_indent * " ")
-    return initial_indent * " " + c_repr(separator.join(wrapped))
+    separator = '"' + suffix + "\n" + subsequent_indent * " " + '"'
+    return initial_indent * " " + '"' + separator.join(wrapped) + '"'
 
 
 def _add_prefix_and_suffix(text: str, *, prefix: str = "", suffix: str = "") 
-> str:
diff --git a/Tools/clinic/libclinic/utils.py b/Tools/clinic/libclinic/utils.py
index 17e8f35be73bf4..3df64f270dd074 100644
--- a/Tools/clinic/libclinic/utils.py
+++ b/Tools/clinic/libclinic/utils.py
@@ -85,9 +85,9 @@ def __repr__(self) -> str:
 
 
 # This one needs to be a distinct class, unlike the other two
-class Null:
+class NullType:
     def __repr__(self) -> str:
         return '<Null>'
 
 
-NULL = Null()
+NULL = NullType()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to