Skip to content

Commit 7726ac9

Browse files
committed
#Issue 20456: Several improvements and bugfixes for Argument Clinic,
including correctly generating code for Clinic blocks inside C preprocessor conditional blocks.
1 parent 04edd2e commit 7726ac9

11 files changed

Lines changed: 755 additions & 405 deletions

File tree

Doc/howto/clinic.rst

Lines changed: 100 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,8 @@ in ``Lib/inspect.py``.
561561
to allow full expressions like ``CONSTANT - 1``.)
562562

563563

564-
Renaming the C functions generated by Argument Clinic
565-
-----------------------------------------------------
564+
Renaming the C functions and variables generated by Argument Clinic
565+
-------------------------------------------------------------------
566566

567567
Argument Clinic automatically names the functions it generates for you.
568568
Occasionally this may cause a problem, if the generated name collides with
@@ -584,6 +584,25 @@ The base function would now be named ``pickler_dumper()``,
584584
and the impl function would now be named ``pickler_dumper_impl()``.
585585

586586

587+
Similarly, you may have a problem where you want to give a parameter
588+
a specific Python name, but that name may be inconvenient in C. Argument
589+
Clinic allows you to give a parameter different names in Python and in C,
590+
using the same ``"as"`` syntax::
591+
592+
/*[clinic input]
593+
pickle.Pickler.dump
594+
595+
obj: object
596+
file as file_obj: object
597+
protocol: object = NULL
598+
*
599+
fix_imports: bool = True
600+
601+
Here, the name used in Python (in the signature and the ``keywords``
602+
array) would be ``file``, but the C variable would be named ``file_obj``.
603+
604+
You can use this to rename the ``self`` parameter too!
605+
587606

588607
Converting functions using PyArg_UnpackTuple
589608
--------------------------------------------
@@ -1308,74 +1327,6 @@ them ``__new__`` or ``__init__`` as appropriate. Notes:
13081327
(If your function doesn't support keywords, the parsing function
13091328
generated will throw an exception if it receives any.)
13101329

1311-
The #ifdef trick
1312-
----------------------------------------------
1313-
1314-
If you're converting a function that isn't available on all platforms,
1315-
there's a trick you can use to make life a little easier. The existing
1316-
code probably looks like this::
1317-
1318-
#ifdef HAVE_FUNCTIONNAME
1319-
static module_functionname(...)
1320-
{
1321-
...
1322-
}
1323-
#endif /* HAVE_FUNCTIONNAME */
1324-
1325-
And then in the ``PyMethodDef`` structure at the bottom you'll have::
1326-
1327-
#ifdef HAVE_FUNCTIONNAME
1328-
{'functionname', ... },
1329-
#endif /* HAVE_FUNCTIONNAME */
1330-
1331-
In this scenario, you should change the code to look like the following::
1332-
1333-
#ifdef HAVE_FUNCTIONNAME
1334-
/*[clinic input]
1335-
module.functionname
1336-
...
1337-
[clinic start generated code]*/
1338-
static module_functionname(...)
1339-
{
1340-
...
1341-
}
1342-
#endif /* HAVE_FUNCTIONNAME */
1343-
1344-
Run Argument Clinic on the code in this state, then refresh the file in
1345-
your editor. Now you'll have the generated code, including the #define
1346-
for the ``PyMethodDef``, like so::
1347-
1348-
#ifdef HAVE_FUNCTIONNAME
1349-
/*[clinic input]
1350-
...
1351-
[clinic start generated code]*/
1352-
...
1353-
#define MODULE_FUNCTIONNAME \
1354-
{'functionname', ... },
1355-
...
1356-
/*[clinic end generated code: checksum=...]*/
1357-
static module_functionname(...)
1358-
{
1359-
...
1360-
}
1361-
#endif /* HAVE_FUNCTIONNAME */
1362-
1363-
Change the #endif at the bottom as follows::
1364-
1365-
#else
1366-
#define MODULE_FUNCTIONNAME
1367-
#endif /* HAVE_FUNCTIONNAME */
1368-
1369-
Now you can remove the #ifdefs around the ``PyMethodDef`` structure
1370-
at the end, and replace those three lines with ``MODULE_FUNCTIONNAME``.
1371-
If the function is available, the macro turns into the ``PyMethodDef``
1372-
static value, including the trailing comma; if the function isn't
1373-
available, the macro turns into nothing. Perfect!
1374-
1375-
(This is the preferred approach for optional functions; in the future,
1376-
Argument Clinic may generate the entire ``PyMethodDef`` structure.)
1377-
1378-
13791330
Changing and redirecting Clinic's output
13801331
----------------------------------------
13811332

@@ -1491,8 +1442,9 @@ previous configuration.
14911442
``output preset`` sets Clinic's output to one of several built-in
14921443
preset configurations, as follows:
14931444

1494-
``original``
1495-
Clinic's starting configuration.
1445+
``block``
1446+
Clinic's original starting configuration. Writes everything
1447+
immediately after the input block.
14961448

14971449
Suppress the ``parser_prototype``
14981450
and ``docstring_prototype``, write everything else to ``block``.
@@ -1640,6 +1592,82 @@ it in a Clinic block lets Clinic use its existing checksum functionality to ensu
16401592
the file was not modified by hand before it gets overwritten.
16411593

16421594

1595+
The #ifdef trick
1596+
----------------------------------------------
1597+
1598+
If you're converting a function that isn't available on all platforms,
1599+
there's a trick you can use to make life a little easier. The existing
1600+
code probably looks like this::
1601+
1602+
#ifdef HAVE_FUNCTIONNAME
1603+
static module_functionname(...)
1604+
{
1605+
...
1606+
}
1607+
#endif /* HAVE_FUNCTIONNAME */
1608+
1609+
And then in the ``PyMethodDef`` structure at the bottom the existing code
1610+
will have::
1611+
1612+
#ifdef HAVE_FUNCTIONNAME
1613+
{'functionname', ... },
1614+
#endif /* HAVE_FUNCTIONNAME */
1615+
1616+
In this scenario, you should enclose the body of your impl function inside the ``#ifdef``,
1617+
like so::
1618+
1619+
#ifdef HAVE_FUNCTIONNAME
1620+
/*[clinic input]
1621+
module.functionname
1622+
...
1623+
[clinic start generated code]*/
1624+
static module_functionname(...)
1625+
{
1626+
...
1627+
}
1628+
#endif /* HAVE_FUNCTIONNAME */
1629+
1630+
Then, remove those three lines from the ``PyMethodDef`` structure,
1631+
replacing them with the macro Argument Clinic generated::
1632+
1633+
MODULE_FUNCTIONNAME_METHODDEF
1634+
1635+
(You can find the real name for this macro inside the generated code.
1636+
Or you can calculate it yourself: it's the name of your function as defined
1637+
on the first line of your block, but with periods changed to underscores,
1638+
uppercased, and ``"_METHODDEF"`` added to the end.)
1639+
1640+
Perhaps you're wondering: what if ``HAVE_FUNCTIONNAME`` isn't defined?
1641+
The ``MODULE_FUNCTIONNAME_METHODDEF`` macro won't be defined either!
1642+
1643+
Here's where Argument Clinic gets very clever. It actually detects that the
1644+
Argument Clinic block might be deactivated by the ``#ifdef``. When that
1645+
happens, it generates a little extra code that looks like this::
1646+
1647+
#ifndef MODULE_FUNCTIONNAME_METHODDEF
1648+
#define MODULE_FUNCTIONNAME_METHODDEF
1649+
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */
1650+
1651+
That means the macro always works. If the function is defined, this turns
1652+
into the correct structure, including the trailing comma. If the function is
1653+
undefined, this turns into nothing.
1654+
1655+
However, this causes one ticklish problem: where should Argument Clinic put this
1656+
extra code when using the "block" output preset? It can't go in the output block,
1657+
because that could be decativated by the ``#ifdef``. (That's the whole point!)
1658+
1659+
In this situation, Argument Clinic writes the extra code to the "buffer" destination.
1660+
This may mean that you get a complaint from Argument Clinic::
1661+
1662+
Warning in file "Modules/posixmodule.c" on line 12357:
1663+
Destination buffer 'buffer' not empty at end of file, emptying.
1664+
1665+
When this happens, just open your file, find the ``dump buffer`` block that
1666+
Argument Clinic added to your file (it'll be at the very bottom), then
1667+
move it above the ``PyMethodDef`` structure where that macro is used.
1668+
1669+
1670+
16431671
Using Argument Clinic in Python files
16441672
-------------------------------------
16451673

Misc/NEWS

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ Tests
9595
Tools/Demos
9696
-----------
9797

98+
- #Issue 20456: Argument Clinic now observes the C preprocessor conditional
99+
compilation statements of the C files it parses. When a Clinic block is
100+
inside a conditional code, it adjusts its output to match, including
101+
automatically generating an empty methoddef macro.
102+
103+
- #Issue 20456: Cloned functions in Argument Clinic now use the correct
104+
name, not the name of the function they were cloned from, for text
105+
strings inside generated code.
106+
107+
- #Issue 20456: Fixed Argument Clinic's test suite and "--converters" feature.
108+
109+
- #Issue 20456: Argument Clinic now allows specifying different names
110+
for a parameter in Python and C, using "as" on the parameter line.
111+
98112
- Issue #20326: Argument Clinic now uses a simple, unique signature to
99113
annotate text signatures in docstrings, resulting in fewer false
100114
positives. "self" parameters are also explicitly marked, allowing

Modules/_cursesmodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ current settings for the window object.
584584
[clinic start generated code]*/
585585

586586
PyDoc_STRVAR(curses_window_addch__doc__,
587-
"addch(self, [x, y,] ch, [attr])\n"
587+
"addch([x, y,] ch, [attr])\n"
588588
"Paint character ch at (y, x) with attributes attr.\n"
589589
"\n"
590590
" x\n"
@@ -651,7 +651,7 @@ curses_window_addch(PyCursesWindowObject *self, PyObject *args)
651651

652652
static PyObject *
653653
curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, int x, int y, PyObject *ch, int group_right_1, long attr)
654-
/*[clinic end generated code: output=e1cdbd4f4e42fc6b input=fe7e3711d5bbf1f6]*/
654+
/*[clinic end generated code: output=43acb91a5c98f615 input=fe7e3711d5bbf1f6]*/
655655
{
656656
PyCursesWindowObject *cwself = (PyCursesWindowObject *)self;
657657
int coordinates_group = group_left_1;

Modules/_dbmmodule.c

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ static PyObject *DbmError;
5252
/*[python input]
5353
class dbmobject_converter(self_converter):
5454
type = "dbmobject *"
55-
def converter_init(self):
55+
def pre_render(self):
56+
super().pre_render()
5657
self.name = 'dp'
5758
[python start generated code]*/
58-
/*[python end generated code: output=da39a3ee5e6b4b0d input=8a69ac1827811128]*/
59+
/*[python end generated code: output=da39a3ee5e6b4b0d input=6ad536357913879a]*/
5960

6061
static PyObject *
6162
newdbmobject(const char *file, int flags, int mode)
@@ -270,61 +271,46 @@ dbm.dbm.get
270271
self: dbmobject
271272
272273
key: str(length=True)
273-
[
274-
default: object
275-
]
274+
default: object = None
276275
/
277276
278277
Return the value for key if present, otherwise default.
279278
[clinic start generated code]*/
280279

281280
PyDoc_STRVAR(dbm_dbm_get__doc__,
282-
"get(self, key, [default])\n"
281+
"sig=($self, key, default=None)\n"
283282
"Return the value for key if present, otherwise default.");
284283

285284
#define DBM_DBM_GET_METHODDEF \
286285
{"get", (PyCFunction)dbm_dbm_get, METH_VARARGS, dbm_dbm_get__doc__},
287286

288287
static PyObject *
289-
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value);
288+
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, PyObject *default_value);
290289

291290
static PyObject *
292291
dbm_dbm_get(dbmobject *dp, PyObject *args)
293292
{
294293
PyObject *return_value = NULL;
295294
const char *key;
296295
Py_ssize_clean_t key_length;
297-
int group_right_1 = 0;
298-
PyObject *default_value = NULL;
299-
300-
switch (PyTuple_GET_SIZE(args)) {
301-
case 1:
302-
if (!PyArg_ParseTuple(args, "s#:get", &key, &key_length))
303-
goto exit;
304-
break;
305-
case 2:
306-
if (!PyArg_ParseTuple(args, "s#O:get", &key, &key_length, &default_value))
307-
goto exit;
308-
group_right_1 = 1;
309-
break;
310-
default:
311-
PyErr_SetString(PyExc_TypeError, "dbm.dbm.get requires 1 to 2 arguments");
312-
goto exit;
313-
}
314-
return_value = dbm_dbm_get_impl(dp, key, key_length, group_right_1, default_value);
296+
PyObject *default_value = Py_None;
297+
298+
if (!PyArg_ParseTuple(args,
299+
"s#|O:get",
300+
&key, &key_length, &default_value))
301+
goto exit;
302+
return_value = dbm_dbm_get_impl(dp, key, key_length, default_value);
315303

316304
exit:
317305
return return_value;
318306
}
319307

320308
static PyObject *
321-
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value)
322-
/*[clinic end generated code: output=31d5180d6b36f1ea input=43a561dc2bd1db3b]*/
309+
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, PyObject *default_value)
310+
/*[clinic end generated code: output=2bbaf9a187f9b6bf input=aecf5efd2f2b1a3b]*/
323311
{
324312
datum dbm_key, val;
325313

326-
if (!group_right_1)
327-
default_value = Py_None;
328314
dbm_key.dptr = (char *)key;
329315
dbm_key.dsize = key_length;
330316
check_dbmobject_open(dp);

0 commit comments

Comments
 (0)