[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 99e2c5eccd)
This commit is contained in:
Serhiy Storchaka
2026-03-17 12:55:15 +02:00
committed by GitHub
parent 7ad3093d76
commit a005f323b7
18 changed files with 512 additions and 163 deletions
+11 -11
View File
@@ -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 @@ exit:
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]
+231
View File
@@ -1044,6 +1044,187 @@ class ClinicParserTest(TestCase):
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 @@ class FormatHelperTests(unittest.TestCase):
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"), "")
+4 -3
View File
@@ -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]
+8 -8
View File
@@ -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) {
+9 -9
View File
@@ -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 @@ exit:
}
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]*/
+7 -7
View File
@@ -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]*/
+2 -2
View File
@@ -205,7 +205,7 @@ exit:
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 @@ exit:
#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]*/
+10 -8
View File
@@ -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]
+2 -2
View File
@@ -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) {
+4 -6
View File
@@ -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 ------------------------------------------------------------
+1
View File
@@ -333,6 +333,7 @@ MAX_SIZES = {
_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),
+8 -4
View File
@@ -7,7 +7,9 @@ from .errors import (
)
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 .identifiers import (
from .utils import (
FormatCounterFormatter,
NULL,
Null,
NullType,
Sentinels,
VersionTuple,
compute_checksum,
@@ -45,7 +47,9 @@ __all__ = [
# 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 @@ __all__ = [
# Utility functions
"FormatCounterFormatter",
"NULL",
"Null",
"NullType",
"Sentinels",
"VersionTuple",
"compute_checksum",
+1 -1
View File
@@ -101,7 +101,7 @@ class CLanguage(Language):
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)
+66 -21
View File
@@ -6,7 +6,7 @@ from collections.abc import Callable
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,21 +189,6 @@ class CConverter(metaclass=CConverterAutoRegister):
self.unused = unused
self._includes: list[Include] = []
if default is not unspecified:
if (self.default_type
and default is not unknown
and not isinstance(default, self.default_type)
):
if isinstance(self.default_type, type):
types_str = self.default_type.__name__
else:
names = [cls.__name__ for cls in self.default_type]
types_str = ', '.join(names)
cls_name = self.__class__.__name__
fail(f"{cls_name}: default value {default!r} for field "
f"{name!r} is not of type {types_str!r}")
self.default = default
if c_default:
self.c_default = c_default
if py_default:
@@ -210,6 +202,56 @@ class CConverter(metaclass=CConverterAutoRegister):
# 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 == ():
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):
types_str = self.default_type.__name__
else:
names = [cls.__name__ for cls in self.default_type]
types_str = ', '.join(names)
cls_name = self.__class__.__name__
fail(f"{cls_name}: default value {default!r} for field "
f"{name!r} is not of type {types_str!r}")
self.default = default
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}.")
self.function = function
# Add a custom __getattr__ method to improve the error message
@@ -233,6 +275,9 @@ class CConverter(metaclass=CConverterAutoRegister):
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 @@ class CConverter(metaclass=CConverterAutoRegister):
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 @@ class CConverter(metaclass=CConverterAutoRegister):
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)
+78 -43
View File
@@ -4,7 +4,7 @@ import sys
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 @@ TypeSet = set[bltns.type[object]]
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 @@ class defining_class_converter(CConverter):
class char_converter(CConverter):
type = 'char'
default_type = (bytes, bytearray)
default_type = bytes
format_unit = 'c'
c_ignored_default = "'\0'"
@@ -132,9 +137,18 @@ class char_converter(CConverter):
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 @@ class char_converter(CConverter):
@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 @@ class short_converter(CConverter):
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 @@ class int_converter(CConverter):
) -> 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 @@ class int_converter(CConverter):
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 @@ class long_converter(CConverter):
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 @@ class long_long_converter(CConverter):
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 @@ class unsigned_long_long_converter(BaseUnsignedIntConverter):
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 @@ class Py_ssize_t_converter(CConverter):
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 @@ class slice_index_converter(CConverter):
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 @@ class Py_complex_converter(CConverter):
class object_converter(CConverter):
type = 'PyObject *'
format_unit = 'O'
c_ignored_default = 'NULL'
def converter_init(
self, *,
@@ -696,6 +713,10 @@ class object_converter(CConverter):
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 @@ str_converter_argument_map: dict[StrConverterKeyType, str] = {}
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 @@ class str_converter(CConverter):
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 @@ class PyByteArrayObject_converter(CConverter):
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 @@ class unicode_converter(CConverter):
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 @@ class Py_UNICODE_converter(CConverter):
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 @@ class Py_UNICODE_converter(CConverter):
@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 @@ class self_converter(CConverter):
# 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 @@ class VarPosCConverter(CConverter):
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 @@ class varpos_tuple_converter(VarPosCConverter):
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:
+17 -32
View File
@@ -7,7 +7,7 @@ import re
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 "
+51 -4
View File
@@ -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:
+2 -2
View File
@@ -85,9 +85,9 @@ unknown: Final = Sentinels.unknown
# 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()