Skip to content

Commit a005f32

Browse files
[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 99e2c5e)
1 parent 7ad3093 commit a005f32

File tree

18 files changed

+511
-162
lines changed

18 files changed

+511
-162
lines changed

Lib/test/clinic.test.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -530,19 +530,19 @@ test_char_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
530530
{
531531
PyObject *return_value = NULL;
532532
char a = 'A';
533-
char b = '\x07';
534-
char c = '\x08';
533+
char b = '\a';
534+
char c = '\b';
535535
char d = '\t';
536536
char e = '\n';
537-
char f = '\x0b';
538-
char g = '\x0c';
537+
char f = '\v';
538+
char g = '\f';
539539
char h = '\r';
540540
char i = '"';
541541
char j = '\'';
542542
char k = '?';
543543
char l = '\\';
544-
char m = '\x00';
545-
char n = '\xff';
544+
char m = '\0';
545+
char n = '\377';
546546

547547
if (!_PyArg_CheckPositional("test_char_converter", nargs, 0, 14)) {
548548
goto exit;
@@ -936,7 +936,7 @@ static PyObject *
936936
test_char_converter_impl(PyObject *module, char a, char b, char c, char d,
937937
char e, char f, char g, char h, char i, char j,
938938
char k, char l, char m, char n)
939-
/*[clinic end generated code: output=ff11e203248582df input=e42330417a44feac]*/
939+
/*[clinic end generated code: output=6503d15448e1d4c4 input=e42330417a44feac]*/
940940

941941

942942
/*[clinic input]
@@ -1173,14 +1173,14 @@ test_int_converter
11731173
11741174
a: int = 12
11751175
b: int(accept={int}) = 34
1176-
c: int(accept={str}) = 45
1176+
c: int(accept={str}) = '-'
11771177
d: int(type='myenum') = 67
11781178
/
11791179
11801180
[clinic start generated code]*/
11811181

11821182
PyDoc_STRVAR(test_int_converter__doc__,
1183-
"test_int_converter($module, a=12, b=34, c=45, d=67, /)\n"
1183+
"test_int_converter($module, a=12, b=34, c=\'-\', d=67, /)\n"
11841184
"--\n"
11851185
"\n");
11861186

@@ -1196,7 +1196,7 @@ test_int_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
11961196
PyObject *return_value = NULL;
11971197
int a = 12;
11981198
int b = 34;
1199-
int c = 45;
1199+
int c = '-';
12001200
myenum d = 67;
12011201

12021202
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)
12471247

12481248
static PyObject *
12491249
test_int_converter_impl(PyObject *module, int a, int b, int c, myenum d)
1250-
/*[clinic end generated code: output=fbcfb7554688663d input=d20541fc1ca0553e]*/
1250+
/*[clinic end generated code: output=d5357b563bdb8789 input=5d8f4eb5899b24de]*/
12511251

12521252

12531253
/*[clinic input]

Lib/test/test_clinic.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,187 @@ def test_param_with_continuations(self):
10441044
p = function.parameters['follow_symlinks']
10451045
self.assertEqual(True, p.default)
10461046

1047+
def test_param_default_none(self):
1048+
function = self.parse_function(r"""
1049+
module test
1050+
test.func
1051+
obj: object = None
1052+
str: str(accept={str, NoneType}) = None
1053+
buf: Py_buffer(accept={str, buffer, NoneType}) = None
1054+
""")
1055+
p = function.parameters['obj']
1056+
self.assertIs(p.default, None)
1057+
self.assertEqual(p.converter.py_default, 'None')
1058+
self.assertEqual(p.converter.c_default, 'Py_None')
1059+
1060+
p = function.parameters['str']
1061+
self.assertIs(p.default, None)
1062+
self.assertEqual(p.converter.py_default, 'None')
1063+
self.assertEqual(p.converter.c_default, 'NULL')
1064+
1065+
p = function.parameters['buf']
1066+
self.assertIs(p.default, None)
1067+
self.assertEqual(p.converter.py_default, 'None')
1068+
self.assertEqual(p.converter.c_default, '{NULL, NULL}')
1069+
1070+
def test_param_default_null(self):
1071+
function = self.parse_function(r"""
1072+
module test
1073+
test.func
1074+
obj: object = NULL
1075+
str: str = NULL
1076+
buf: Py_buffer = NULL
1077+
fsencoded: unicode_fs_encoded = NULL
1078+
fsdecoded: unicode_fs_decoded = NULL
1079+
""")
1080+
p = function.parameters['obj']
1081+
self.assertIs(p.default, NULL)
1082+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1083+
self.assertEqual(p.converter.c_default, 'NULL')
1084+
1085+
p = function.parameters['str']
1086+
self.assertIs(p.default, NULL)
1087+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1088+
self.assertEqual(p.converter.c_default, 'NULL')
1089+
1090+
p = function.parameters['buf']
1091+
self.assertIs(p.default, NULL)
1092+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1093+
self.assertEqual(p.converter.c_default, '{NULL, NULL}')
1094+
1095+
p = function.parameters['fsencoded']
1096+
self.assertIs(p.default, NULL)
1097+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1098+
self.assertEqual(p.converter.c_default, 'NULL')
1099+
1100+
p = function.parameters['fsdecoded']
1101+
self.assertIs(p.default, NULL)
1102+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1103+
self.assertEqual(p.converter.c_default, 'NULL')
1104+
1105+
def test_param_default_str_literal(self):
1106+
function = self.parse_function(r"""
1107+
module test
1108+
test.func
1109+
str: str = ' \t\n\r\v\f\xa0'
1110+
buf: Py_buffer(accept={str, buffer}) = ' \t\n\r\v\f\xa0'
1111+
""")
1112+
p = function.parameters['str']
1113+
self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
1114+
self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
1115+
self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\u00a0"')
1116+
1117+
p = function.parameters['buf']
1118+
self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
1119+
self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
1120+
self.assertEqual(p.converter.c_default,
1121+
r'{.buf = " \t\n\r\v\f\302\240", .obj = NULL, .len = 8}')
1122+
1123+
def test_param_default_bytes_literal(self):
1124+
function = self.parse_function(r"""
1125+
module test
1126+
test.func
1127+
str: str(accept={robuffer}) = b' \t\n\r\v\f\xa0'
1128+
buf: Py_buffer = b' \t\n\r\v\f\xa0'
1129+
""")
1130+
p = function.parameters['str']
1131+
self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
1132+
self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
1133+
self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\240"')
1134+
1135+
p = function.parameters['buf']
1136+
self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
1137+
self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
1138+
self.assertEqual(p.converter.c_default,
1139+
r'{.buf = " \t\n\r\v\f\240", .obj = NULL, .len = 7}')
1140+
1141+
def test_param_default_byte_literal(self):
1142+
function = self.parse_function(r"""
1143+
module test
1144+
test.func
1145+
zero: char = b'\0'
1146+
one: char = b'\1'
1147+
lf: char = b'\n'
1148+
nbsp: char = b'\xa0'
1149+
""")
1150+
p = function.parameters['zero']
1151+
self.assertEqual(p.default, b'\0')
1152+
self.assertEqual(p.converter.py_default, r"b'\x00'")
1153+
self.assertEqual(p.converter.c_default, r"'\0'")
1154+
1155+
p = function.parameters['one']
1156+
self.assertEqual(p.default, b'\1')
1157+
self.assertEqual(p.converter.py_default, r"b'\x01'")
1158+
self.assertEqual(p.converter.c_default, r"'\001'")
1159+
1160+
p = function.parameters['lf']
1161+
self.assertEqual(p.default, b'\n')
1162+
self.assertEqual(p.converter.py_default, r"b'\n'")
1163+
self.assertEqual(p.converter.c_default, r"'\n'")
1164+
1165+
p = function.parameters['nbsp']
1166+
self.assertEqual(p.default, b'\xa0')
1167+
self.assertEqual(p.converter.py_default, r"b'\xa0'")
1168+
self.assertEqual(p.converter.c_default, r"'\240'")
1169+
1170+
def test_param_default_unicode_char(self):
1171+
function = self.parse_function(r"""
1172+
module test
1173+
test.func
1174+
zero: int(accept={str}) = '\0'
1175+
one: int(accept={str}) = '\1'
1176+
lf: int(accept={str}) = '\n'
1177+
nbsp: int(accept={str}) = '\xa0'
1178+
snake: int(accept={str}) = '\U0001f40d'
1179+
""")
1180+
p = function.parameters['zero']
1181+
self.assertEqual(p.default, '\0')
1182+
self.assertEqual(p.converter.py_default, r"'\x00'")
1183+
self.assertEqual(p.converter.c_default, '0')
1184+
1185+
p = function.parameters['one']
1186+
self.assertEqual(p.default, '\1')
1187+
self.assertEqual(p.converter.py_default, r"'\x01'")
1188+
self.assertEqual(p.converter.c_default, '0x01')
1189+
1190+
p = function.parameters['lf']
1191+
self.assertEqual(p.default, '\n')
1192+
self.assertEqual(p.converter.py_default, r"'\n'")
1193+
self.assertEqual(p.converter.c_default, r"'\n'")
1194+
1195+
p = function.parameters['nbsp']
1196+
self.assertEqual(p.default, '\xa0')
1197+
self.assertEqual(p.converter.py_default, r"'\xa0'")
1198+
self.assertEqual(p.converter.c_default, '0xa0')
1199+
1200+
p = function.parameters['snake']
1201+
self.assertEqual(p.default, '\U0001f40d')
1202+
self.assertEqual(p.converter.py_default, "'\U0001f40d'")
1203+
self.assertEqual(p.converter.c_default, '0x1f40d')
1204+
1205+
def test_param_default_bool(self):
1206+
function = self.parse_function(r"""
1207+
module test
1208+
test.func
1209+
bool: bool = True
1210+
intbool: bool(accept={int}) = True
1211+
intbool2: bool(accept={int}) = 2
1212+
""")
1213+
p = function.parameters['bool']
1214+
self.assertIs(p.default, True)
1215+
self.assertEqual(p.converter.py_default, 'True')
1216+
self.assertEqual(p.converter.c_default, '1')
1217+
1218+
p = function.parameters['intbool']
1219+
self.assertIs(p.default, True)
1220+
self.assertEqual(p.converter.py_default, 'True')
1221+
self.assertEqual(p.converter.c_default, '1')
1222+
1223+
p = function.parameters['intbool2']
1224+
self.assertEqual(p.default, 2)
1225+
self.assertEqual(p.converter.py_default, '2')
1226+
self.assertEqual(p.converter.c_default, '2')
1227+
10471228
def test_param_default_expr_named_constant(self):
10481229
function = self.parse_function("""
10491230
module os
@@ -4209,6 +4390,56 @@ def test_format_escape(self):
42094390
out = libclinic.format_escape(line)
42104391
self.assertEqual(out, expected)
42114392

4393+
def test_c_bytes_repr(self):
4394+
c_bytes_repr = libclinic.c_bytes_repr
4395+
self.assertEqual(c_bytes_repr(b''), '""')
4396+
self.assertEqual(c_bytes_repr(b'abc'), '"abc"')
4397+
self.assertEqual(c_bytes_repr(b'\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
4398+
self.assertEqual(c_bytes_repr(b' \0\x7f'), r'" \000\177"')
4399+
self.assertEqual(c_bytes_repr(b'"'), r'"\""')
4400+
self.assertEqual(c_bytes_repr(b"'"), r'''"'"''')
4401+
self.assertEqual(c_bytes_repr(b'\\'), r'"\\"')
4402+
self.assertEqual(c_bytes_repr(b'??/'), r'"?\?/"')
4403+
self.assertEqual(c_bytes_repr(b'???/'), r'"?\?\?/"')
4404+
self.assertEqual(c_bytes_repr(b'/*****/ /*/ */*'), r'"/\*****\/ /\*\/ *\/\*"')
4405+
self.assertEqual(c_bytes_repr(b'\xa0'), r'"\240"')
4406+
self.assertEqual(c_bytes_repr(b'\xff'), r'"\377"')
4407+
4408+
def test_c_str_repr(self):
4409+
c_str_repr = libclinic.c_str_repr
4410+
self.assertEqual(c_str_repr(''), '""')
4411+
self.assertEqual(c_str_repr('abc'), '"abc"')
4412+
self.assertEqual(c_str_repr('\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
4413+
self.assertEqual(c_str_repr(' \0\x7f'), r'" \000\177"')
4414+
self.assertEqual(c_str_repr('"'), r'"\""')
4415+
self.assertEqual(c_str_repr("'"), r'''"'"''')
4416+
self.assertEqual(c_str_repr('\\'), r'"\\"')
4417+
self.assertEqual(c_str_repr('??/'), r'"?\?/"')
4418+
self.assertEqual(c_str_repr('???/'), r'"?\?\?/"')
4419+
self.assertEqual(c_str_repr('/*****/ /*/ */*'), r'"/\*****\/ /\*\/ *\/\*"')
4420+
self.assertEqual(c_str_repr('\xa0'), r'"\u00a0"')
4421+
self.assertEqual(c_str_repr('\xff'), r'"\u00ff"')
4422+
self.assertEqual(c_str_repr('\u20ac'), r'"\u20ac"')
4423+
self.assertEqual(c_str_repr('\U0001f40d'), r'"\U0001f40d"')
4424+
4425+
def test_c_unichar_repr(self):
4426+
c_unichar_repr = libclinic.c_unichar_repr
4427+
self.assertEqual(c_unichar_repr('a'), "'a'")
4428+
self.assertEqual(c_unichar_repr('\n'), r"'\n'")
4429+
self.assertEqual(c_unichar_repr('\b'), r"'\b'")
4430+
self.assertEqual(c_unichar_repr('\0'), '0')
4431+
self.assertEqual(c_unichar_repr('\1'), '0x01')
4432+
self.assertEqual(c_unichar_repr('\x7f'), '0x7f')
4433+
self.assertEqual(c_unichar_repr(' '), "' '")
4434+
self.assertEqual(c_unichar_repr('"'), """'"'""")
4435+
self.assertEqual(c_unichar_repr("'"), r"'\''")
4436+
self.assertEqual(c_unichar_repr('\\'), r"'\\'")
4437+
self.assertEqual(c_unichar_repr('?'), "'?'")
4438+
self.assertEqual(c_unichar_repr('\xa0'), '0xa0')
4439+
self.assertEqual(c_unichar_repr('\xff'), '0xff')
4440+
self.assertEqual(c_unichar_repr('\u20ac'), '0x20ac')
4441+
self.assertEqual(c_unichar_repr('\U0001f40d'), '0x1f40d')
4442+
42124443
def test_indent_all_lines(self):
42134444
# Blank lines are expected to be unchanged.
42144445
self.assertEqual(libclinic.indent_all_lines("", prefix="bar"), "")

Modules/_testclinic.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,14 @@ int_converter
334334
335335
a: int = 12
336336
b: int(accept={int}) = 34
337-
c: int(accept={str}) = 45
337+
c: int(accept={str}) = '-'
338338
/
339339
340340
[clinic start generated code]*/
341341

342342
static PyObject *
343343
int_converter_impl(PyObject *module, int a, int b, int c)
344-
/*[clinic end generated code: output=8e56b59be7d0c306 input=a1dbc6344853db7a]*/
344+
/*[clinic end generated code: output=8e56b59be7d0c306 input=9a306d4dc907e339]*/
345345
{
346346
RETURN_PACKED_ARGS(3, PyLong_FromLong, long, a, b, c);
347347
}
@@ -1360,14 +1360,15 @@ clone_f2_impl(PyObject *module, const char *path)
13601360
class custom_t_converter(CConverter):
13611361
type = 'custom_t'
13621362
converter = 'custom_converter'
1363+
c_init_default = "<placeholder>" # overridden in pre_render(()
13631364
13641365
def pre_render(self):
13651366
self.c_default = f'''{{
13661367
.name = "{self.function.name}",
13671368
}}'''
13681369
13691370
[python start generated code]*/
1370-
/*[python end generated code: output=da39a3ee5e6b4b0d input=b2fb801e99a06bf6]*/
1371+
/*[python end generated code: output=da39a3ee5e6b4b0d input=78fe84e5ecc0481b]*/
13711372

13721373

13731374
/*[clinic input]

Modules/blake2module.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -658,9 +658,9 @@ _blake2.blake2b.__new__ as py_blake2b_new
658658
data as data_obj: object(c_default="NULL") = b''
659659
*
660660
digest_size: int(c_default="HACL_HASH_BLAKE2B_OUT_BYTES") = _blake2.blake2b.MAX_DIGEST_SIZE
661-
key: Py_buffer(c_default="NULL", py_default="b''") = None
662-
salt: Py_buffer(c_default="NULL", py_default="b''") = None
663-
person: Py_buffer(c_default="NULL", py_default="b''") = None
661+
key: Py_buffer = b''
662+
salt: Py_buffer = b''
663+
person: Py_buffer = b''
664664
fanout: int = 1
665665
depth: int = 1
666666
leaf_size: unsigned_long = 0
@@ -681,7 +681,7 @@ py_blake2b_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
681681
unsigned long long node_offset, int node_depth,
682682
int inner_size, int last_node, int usedforsecurity,
683683
PyObject *string)
684-
/*[clinic end generated code: output=de64bd850606b6a0 input=78cf60a2922d2f90]*/
684+
/*[clinic end generated code: output=de64bd850606b6a0 input=32832fb37d13c03d]*/
685685
{
686686
PyObject *data;
687687
if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
@@ -696,9 +696,9 @@ _blake2.blake2s.__new__ as py_blake2s_new
696696
data as data_obj: object(c_default="NULL") = b''
697697
*
698698
digest_size: int(c_default="HACL_HASH_BLAKE2S_OUT_BYTES") = _blake2.blake2s.MAX_DIGEST_SIZE
699-
key: Py_buffer(c_default="NULL", py_default="b''") = None
700-
salt: Py_buffer(c_default="NULL", py_default="b''") = None
701-
person: Py_buffer(c_default="NULL", py_default="b''") = None
699+
key: Py_buffer = b''
700+
salt: Py_buffer = b''
701+
person: Py_buffer = b''
702702
fanout: int = 1
703703
depth: int = 1
704704
leaf_size: unsigned_long = 0
@@ -719,7 +719,7 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
719719
unsigned long long node_offset, int node_depth,
720720
int inner_size, int last_node, int usedforsecurity,
721721
PyObject *string)
722-
/*[clinic end generated code: output=582a0c4295cc3a3c input=6843d6332eefd295]*/
722+
/*[clinic end generated code: output=582a0c4295cc3a3c input=da467fc9dae646bb]*/
723723
{
724724
PyObject *data;
725725
if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {

0 commit comments

Comments
 (0)