[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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 512 additions and 163 deletions

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 @@ 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]

View file

@ -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"), "")

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]

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) {

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]*/

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]*/

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]*/

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]

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) {

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 ------------------------------------------------------------

View file

@ -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),

View file

@ -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",

View file

@ -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)

View file

@ -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,21 +189,6 @@ def __init__(self,
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 @@ def __init__(self,
# 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 @@ 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)

View file

@ -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:

View file

@ -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 "

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:

View file

@ -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()