gh-144545: Improve handling of default values in Argument Clinic (GH-… · python/cpython@99e2c5e · GitHub
Skip to content

Commit 99e2c5e

Browse files
gh-144545: Improve handling of default values in Argument Clinic (GH-146016)
* 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.
1 parent 4f5e798 commit 99e2c5e

22 files changed

Lines changed: 528 additions & 181 deletions

Lib/test/clinic.test.c

Lines changed: 11 additions & 11 deletions

Lib/test/test_clinic.py

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

1084+
def test_param_default_none(self):
1085+
function = self.parse_function(r"""
1086+
module test
1087+
test.func
1088+
obj: object = None
1089+
str: str(accept={str, NoneType}) = None
1090+
buf: Py_buffer(accept={str, buffer, NoneType}) = None
1091+
""")
1092+
p = function.parameters['obj']
1093+
self.assertIs(p.default, None)
1094+
self.assertEqual(p.converter.py_default, 'None')
1095+
self.assertEqual(p.converter.c_default, 'Py_None')
1096+
1097+
p = function.parameters['str']
1098+
self.assertIs(p.default, None)
1099+
self.assertEqual(p.converter.py_default, 'None')
1100+
self.assertEqual(p.converter.c_default, 'NULL')
1101+
1102+
p = function.parameters['buf']
1103+
self.assertIs(p.default, None)
1104+
self.assertEqual(p.converter.py_default, 'None')
1105+
self.assertEqual(p.converter.c_default, '{NULL, NULL}')
1106+
1107+
def test_param_default_null(self):
1108+
function = self.parse_function(r"""
1109+
module test
1110+
test.func
1111+
obj: object = NULL
1112+
str: str = NULL
1113+
buf: Py_buffer = NULL
1114+
fsencoded: unicode_fs_encoded = NULL
1115+
fsdecoded: unicode_fs_decoded = NULL
1116+
""")
1117+
p = function.parameters['obj']
1118+
self.assertIs(p.default, NULL)
1119+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1120+
self.assertEqual(p.converter.c_default, 'NULL')
1121+
1122+
p = function.parameters['str']
1123+
self.assertIs(p.default, NULL)
1124+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1125+
self.assertEqual(p.converter.c_default, 'NULL')
1126+
1127+
p = function.parameters['buf']
1128+
self.assertIs(p.default, NULL)
1129+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1130+
self.assertEqual(p.converter.c_default, '{NULL, NULL}')
1131+
1132+
p = function.parameters['fsencoded']
1133+
self.assertIs(p.default, NULL)
1134+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1135+
self.assertEqual(p.converter.c_default, 'NULL')
1136+
1137+
p = function.parameters['fsdecoded']
1138+
self.assertIs(p.default, NULL)
1139+
self.assertEqual(p.converter.py_default, '<unrepresentable>')
1140+
self.assertEqual(p.converter.c_default, 'NULL')
1141+
1142+
def test_param_default_str_literal(self):
1143+
function = self.parse_function(r"""
1144+
module test
1145+
test.func
1146+
str: str = ' \t\n\r\v\f\xa0'
1147+
buf: Py_buffer(accept={str, buffer}) = ' \t\n\r\v\f\xa0'
1148+
""")
1149+
p = function.parameters['str']
1150+
self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
1151+
self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
1152+
self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\u00a0"')
1153+
1154+
p = function.parameters['buf']
1155+
self.assertEqual(p.default, ' \t\n\r\v\f\xa0')
1156+
self.assertEqual(p.converter.py_default, r"' \t\n\r\x0b\x0c\xa0'")
1157+
self.assertEqual(p.converter.c_default,
1158+
r'{.buf = " \t\n\r\v\f\302\240", .obj = NULL, .len = 8}')
1159+
1160+
def test_param_default_bytes_literal(self):
1161+
function = self.parse_function(r"""
1162+
module test
1163+
test.func
1164+
str: str(accept={robuffer}) = b' \t\n\r\v\f\xa0'
1165+
buf: Py_buffer = b' \t\n\r\v\f\xa0'
1166+
""")
1167+
p = function.parameters['str']
1168+
self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
1169+
self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
1170+
self.assertEqual(p.converter.c_default, r'" \t\n\r\v\f\240"')
1171+
1172+
p = function.parameters['buf']
1173+
self.assertEqual(p.default, b' \t\n\r\v\f\xa0')
1174+
self.assertEqual(p.converter.py_default, r"b' \t\n\r\x0b\x0c\xa0'")
1175+
self.assertEqual(p.converter.c_default,
1176+
r'{.buf = " \t\n\r\v\f\240", .obj = NULL, .len = 7}')
1177+
1178+
def test_param_default_byte_literal(self):
1179+
function = self.parse_function(r"""
1180+
module test
1181+
test.func
1182+
zero: char = b'\0'
1183+
one: char = b'\1'
1184+
lf: char = b'\n'
1185+
nbsp: char = b'\xa0'
1186+
""")
1187+
p = function.parameters['zero']
1188+
self.assertEqual(p.default, b'\0')
1189+
self.assertEqual(p.converter.py_default, r"b'\x00'")
1190+
self.assertEqual(p.converter.c_default, r"'\0'")
1191+
1192+
p = function.parameters['one']
1193+
self.assertEqual(p.default, b'\1')
1194+
self.assertEqual(p.converter.py_default, r"b'\x01'")
1195+
self.assertEqual(p.converter.c_default, r"'\001'")
1196+
1197+
p = function.parameters['lf']
1198+
self.assertEqual(p.default, b'\n')
1199+
self.assertEqual(p.converter.py_default, r"b'\n'")
1200+
self.assertEqual(p.converter.c_default, r"'\n'")
1201+
1202+
p = function.parameters['nbsp']
1203+
self.assertEqual(p.default, b'\xa0')
1204+
self.assertEqual(p.converter.py_default, r"b'\xa0'")
1205+
self.assertEqual(p.converter.c_default, r"'\240'")
1206+
1207+
def test_param_default_unicode_char(self):
1208+
function = self.parse_function(r"""
1209+
module test
1210+
test.func
1211+
zero: int(accept={str}) = '\0'
1212+
one: int(accept={str}) = '\1'
1213+
lf: int(accept={str}) = '\n'
1214+
nbsp: int(accept={str}) = '\xa0'
1215+
snake: int(accept={str}) = '\U0001f40d'
1216+
""")
1217+
p = function.parameters['zero']
1218+
self.assertEqual(p.default, '\0')
1219+
self.assertEqual(p.converter.py_default, r"'\x00'")
1220+
self.assertEqual(p.converter.c_default, '0')
1221+
1222+
p = function.parameters['one']
1223+
self.assertEqual(p.default, '\1')
1224+
self.assertEqual(p.converter.py_default, r"'\x01'")
1225+
self.assertEqual(p.converter.c_default, '0x01')
1226+
1227+
p = function.parameters['lf']
1228+
self.assertEqual(p.default, '\n')
1229+
self.assertEqual(p.converter.py_default, r"'\n'")
1230+
self.assertEqual(p.converter.c_default, r"'\n'")
1231+
1232+
p = function.parameters['nbsp']
1233+
self.assertEqual(p.default, '\xa0')
1234+
self.assertEqual(p.converter.py_default, r"'\xa0'")
1235+
self.assertEqual(p.converter.c_default, '0xa0')
1236+
1237+
p = function.parameters['snake']
1238+
self.assertEqual(p.default, '\U0001f40d')
1239+
self.assertEqual(p.converter.py_default, "'\U0001f40d'")
1240+
self.assertEqual(p.converter.c_default, '0x1f40d')
1241+
1242+
def test_param_default_bool(self):
1243+
function = self.parse_function(r"""
1244+
module test
1245+
test.func
1246+
bool: bool = True
1247+
intbool: bool(accept={int}) = True
1248+
intbool2: bool(accept={int}) = 2
1249+
""")
1250+
p = function.parameters['bool']
1251+
self.assertIs(p.default, True)
1252+
self.assertEqual(p.converter.py_default, 'True')
1253+
self.assertEqual(p.converter.c_default, '1')
1254+
1255+
p = function.parameters['intbool']
1256+
self.assertIs(p.default, True)
1257+
self.assertEqual(p.converter.py_default, 'True')
1258+
self.assertEqual(p.converter.c_default, '1')
1259+
1260+
p = function.parameters['intbool2']
1261+
self.assertEqual(p.default, 2)
1262+
self.assertEqual(p.converter.py_default, '2')
1263+
self.assertEqual(p.converter.c_default, '2')
1264+
10841265
def test_param_default_expr_named_constant(self):
10851266
function = self.parse_function("""
10861267
module os
@@ -4432,6 +4613,56 @@ def test_format_escape(self):
44324613
out = libclinic.format_escape(line)
44334614
self.assertEqual(out, expected)
44344615

4616+
def test_c_bytes_repr(self):
4617+
c_bytes_repr = libclinic.c_bytes_repr
4618+
self.assertEqual(c_bytes_repr(b''), '""')
4619+
self.assertEqual(c_bytes_repr(b'abc'), '"abc"')
4620+
self.assertEqual(c_bytes_repr(b'\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
4621+
self.assertEqual(c_bytes_repr(b' \0\x7f'), r'" \000\177"')
4622+
self.assertEqual(c_bytes_repr(b'"'), r'"\""')
4623+
self.assertEqual(c_bytes_repr(b"'"), r'''"'"''')
4624+
self.assertEqual(c_bytes_repr(b'\\'), r'"\\"')
4625+
self.assertEqual(c_bytes_repr(b'??/'), r'"?\?/"')
4626+
self.assertEqual(c_bytes_repr(b'???/'), r'"?\?\?/"')
4627+
self.assertEqual(c_bytes_repr(b'/*****/ /*/ */*'), r'"/\*****\/ /\*\/ *\/\*"')
4628+
self.assertEqual(c_bytes_repr(b'\xa0'), r'"\240"')
4629+
self.assertEqual(c_bytes_repr(b'\xff'), r'"\377"')
4630+
4631+
def test_c_str_repr(self):
4632+
c_str_repr = libclinic.c_str_repr
4633+
self.assertEqual(c_str_repr(''), '""')
4634+
self.assertEqual(c_str_repr('abc'), '"abc"')
4635+
self.assertEqual(c_str_repr('\a\b\f\n\r\t\v'), r'"\a\b\f\n\r\t\v"')
4636+
self.assertEqual(c_str_repr(' \0\x7f'), r'" \000\177"')
4637+
self.assertEqual(c_str_repr('"'), r'"\""')
4638+
self.assertEqual(c_str_repr("'"), r'''"'"''')
4639+
self.assertEqual(c_str_repr('\\'), r'"\\"')
4640+
self.assertEqual(c_str_repr('??/'), r'"?\?/"')
4641+
self.assertEqual(c_str_repr('???/'), r'"?\?\?/"')
4642+
self.assertEqual(c_str_repr('/*****/ /*/ */*'), r'"/\*****\/ /\*\/ *\/\*"')
4643+
self.assertEqual(c_str_repr('\xa0'), r'"\u00a0"')
4644+
self.assertEqual(c_str_repr('\xff'), r'"\u00ff"')
4645+
self.assertEqual(c_str_repr('\u20ac'), r'"\u20ac"')
4646+
self.assertEqual(c_str_repr('\U0001f40d'), r'"\U0001f40d"')
4647+
4648+
def test_c_unichar_repr(self):
4649+
c_unichar_repr = libclinic.c_unichar_repr
4650+
self.assertEqual(c_unichar_repr('a'), "'a'")
4651+
self.assertEqual(c_unichar_repr('\n'), r"'\n'")
4652+
self.assertEqual(c_unichar_repr('\b'), r"'\b'")
4653+
self.assertEqual(c_unichar_repr('\0'), '0')
4654+
self.assertEqual(c_unichar_repr('\1'), '0x01')
4655+
self.assertEqual(c_unichar_repr('\x7f'), '0x7f')
4656+
self.assertEqual(c_unichar_repr(' '), "' '")
4657+
self.assertEqual(c_unichar_repr('"'), """'"'""")
4658+
self.assertEqual(c_unichar_repr("'"), r"'\''")
4659+
self.assertEqual(c_unichar_repr('\\'), r"'\\'")
4660+
self.assertEqual(c_unichar_repr('?'), "'?'")
4661+
self.assertEqual(c_unichar_repr('\xa0'), '0xa0')
4662+
self.assertEqual(c_unichar_repr('\xff'), '0xff')
4663+
self.assertEqual(c_unichar_repr('\u20ac'), '0x20ac')
4664+
self.assertEqual(c_unichar_repr('\U0001f40d'), '0x1f40d')
4665+
44354666
def test_indent_all_lines(self):
44364667
# Blank lines are expected to be unchanged.
44374668
self.assertEqual(libclinic.indent_all_lines("", prefix="bar"), "")

Modules/_datetimemodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6395,7 +6395,7 @@ datetime_str(PyObject *op)
63956395
/*[clinic input]
63966396
datetime.datetime.isoformat
63976397
6398-
sep: int(accept={str}, c_default="'T'", py_default="'T'") = ord('T')
6398+
sep: int(accept={str}) = 'T'
63996399
timespec: str(c_default="NULL") = 'auto'
64006400
64016401
Return the time formatted according to ISO.
@@ -6417,7 +6417,7 @@ terms of the time to include. Valid options are 'auto', 'hours',
64176417
static PyObject *
64186418
datetime_datetime_isoformat_impl(PyDateTime_DateTime *self, int sep,
64196419
const char *timespec)
6420-
/*[clinic end generated code: output=9b6ce1383189b0bf input=2fa2512172ccf5d5]*/
6420+
/*[clinic end generated code: output=9b6ce1383189b0bf input=db935a57fa697c5e]*/
64216421
{
64226422
char buffer[100];
64236423

Modules/_testclinic.c

Lines changed: 4 additions & 3 deletions

0 commit comments

Comments
 (0)