Skip to content

Commit 37c2d22

Browse files
committed
gh-156870: Use a manual Stable ABI note for the exception type tables
In compact tables of many similar definitions, the generated "Part of the Stable ABI" notes are repetitive and take up a lot of vertical space. Let the c_annotations extension skip the generated note for elements inside a container with the "no-stable-abi-notes" class, and use it for the three tables of exception and warning types in the C API docs. Each table gets an "S.ABI" column with the version in which the pointer was added to the Stable ABI, and a common note before it.
1 parent 5056ac5 commit 37c2d22

2 files changed

Lines changed: 115 additions & 2 deletions

File tree

Doc/c-api/exceptions.rst

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,124 +1117,186 @@ For completeness, here are all the variables:
11171117
Exception types
11181118
---------------
11191119
1120+
These pointers are part of the :ref:`Stable ABI <stable>` since the version
1121+
in the *S.ABI* column, or since 3.2 if that column is empty.
1122+
Exceptions marked N/A are *not* part of the Stable ABI.
1123+
11201124
.. list-table::
11211125
:align: left
11221126
:widths: auto
11231127
:header-rows: 1
1128+
:class: no-stable-abi-notes
11241129
11251130
* * C name
1131+
* :ref:`S.ABI <stable>`
11261132
* Python name
11271133
* * .. c:var:: PyObject *PyExc_BaseException
1134+
*
11281135
* :exc:`BaseException`
11291136
* * .. c:var:: PyObject *PyExc_BaseExceptionGroup
1137+
* 3.11
11301138
* :exc:`BaseExceptionGroup`
11311139
* * .. c:var:: PyObject *PyExc_Exception
1140+
*
11321141
* :exc:`Exception`
11331142
* * .. c:var:: PyObject *PyExc_ArithmeticError
1143+
*
11341144
* :exc:`ArithmeticError`
11351145
* * .. c:var:: PyObject *PyExc_AssertionError
1146+
*
11361147
* :exc:`AssertionError`
11371148
* * .. c:var:: PyObject *PyExc_AttributeError
1149+
*
11381150
* :exc:`AttributeError`
11391151
* * .. c:var:: PyObject *PyExc_BlockingIOError
1152+
* 3.7
11401153
* :exc:`BlockingIOError`
11411154
* * .. c:var:: PyObject *PyExc_BrokenPipeError
1155+
* 3.7
11421156
* :exc:`BrokenPipeError`
11431157
* * .. c:var:: PyObject *PyExc_BufferError
1158+
*
11441159
* :exc:`BufferError`
11451160
* * .. c:var:: PyObject *PyExc_ChildProcessError
1161+
* 3.7
11461162
* :exc:`ChildProcessError`
11471163
* * .. c:var:: PyObject *PyExc_ConnectionAbortedError
1164+
* 3.7
11481165
* :exc:`ConnectionAbortedError`
11491166
* * .. c:var:: PyObject *PyExc_ConnectionError
1167+
* 3.7
11501168
* :exc:`ConnectionError`
11511169
* * .. c:var:: PyObject *PyExc_ConnectionRefusedError
1170+
* 3.7
11521171
* :exc:`ConnectionRefusedError`
11531172
* * .. c:var:: PyObject *PyExc_ConnectionResetError
1173+
* 3.7
11541174
* :exc:`ConnectionResetError`
11551175
* * .. c:var:: PyObject *PyExc_EOFError
1176+
*
11561177
* :exc:`EOFError`
11571178
* * .. c:var:: PyObject *PyExc_FileExistsError
1179+
* 3.7
11581180
* :exc:`FileExistsError`
11591181
* * .. c:var:: PyObject *PyExc_FileNotFoundError
1182+
* 3.7
11601183
* :exc:`FileNotFoundError`
11611184
* * .. c:var:: PyObject *PyExc_FloatingPointError
1185+
*
11621186
* :exc:`FloatingPointError`
11631187
* * .. c:var:: PyObject *PyExc_GeneratorExit
1188+
*
11641189
* :exc:`GeneratorExit`
11651190
* * .. c:var:: PyObject *PyExc_ImportCycleError
1191+
* N/A
11661192
* :exc:`ImportCycleError`
11671193
* * .. c:var:: PyObject *PyExc_ImportError
1194+
*
11681195
* :exc:`ImportError`
11691196
* * .. c:var:: PyObject *PyExc_IndentationError
1197+
*
11701198
* :exc:`IndentationError`
11711199
* * .. c:var:: PyObject *PyExc_IndexError
1200+
*
11721201
* :exc:`IndexError`
11731202
* * .. c:var:: PyObject *PyExc_InterruptedError
1203+
* 3.7
11741204
* :exc:`InterruptedError`
11751205
* * .. c:var:: PyObject *PyExc_IsADirectoryError
1206+
* 3.7
11761207
* :exc:`IsADirectoryError`
11771208
* * .. c:var:: PyObject *PyExc_KeyError
1209+
*
11781210
* :exc:`KeyError`
11791211
* * .. c:var:: PyObject *PyExc_KeyboardInterrupt
1212+
*
11801213
* :exc:`KeyboardInterrupt`
11811214
* * .. c:var:: PyObject *PyExc_LookupError
1215+
*
11821216
* :exc:`LookupError`
11831217
* * .. c:var:: PyObject *PyExc_MemoryError
1218+
*
11841219
* :exc:`MemoryError`
11851220
* * .. c:var:: PyObject *PyExc_ModuleNotFoundError
1221+
* 3.6
11861222
* :exc:`ModuleNotFoundError`
11871223
* * .. c:var:: PyObject *PyExc_NameError
1224+
*
11881225
* :exc:`NameError`
11891226
* * .. c:var:: PyObject *PyExc_NotADirectoryError
1227+
* 3.7
11901228
* :exc:`NotADirectoryError`
11911229
* * .. c:var:: PyObject *PyExc_NotImplementedError
1230+
*
11921231
* :exc:`NotImplementedError`
11931232
* * .. c:var:: PyObject *PyExc_OSError
1233+
*
11941234
* :exc:`OSError`
11951235
* * .. c:var:: PyObject *PyExc_OverflowError
1236+
*
11961237
* :exc:`OverflowError`
11971238
* * .. c:var:: PyObject *PyExc_PermissionError
1239+
* 3.7
11981240
* :exc:`PermissionError`
11991241
* * .. c:var:: PyObject *PyExc_ProcessLookupError
1242+
* 3.7
12001243
* :exc:`ProcessLookupError`
12011244
* * .. c:var:: PyObject *PyExc_PythonFinalizationError
1245+
* N/A
12021246
* :exc:`PythonFinalizationError`
12031247
* * .. c:var:: PyObject *PyExc_RecursionError
1248+
* 3.7
12041249
* :exc:`RecursionError`
12051250
* * .. c:var:: PyObject *PyExc_ReferenceError
1251+
*
12061252
* :exc:`ReferenceError`
12071253
* * .. c:var:: PyObject *PyExc_RuntimeError
1254+
*
12081255
* :exc:`RuntimeError`
12091256
* * .. c:var:: PyObject *PyExc_StopAsyncIteration
1257+
* 3.7
12101258
* :exc:`StopAsyncIteration`
12111259
* * .. c:var:: PyObject *PyExc_StopIteration
1260+
*
12121261
* :exc:`StopIteration`
12131262
* * .. c:var:: PyObject *PyExc_SyntaxError
1263+
*
12141264
* :exc:`SyntaxError`
12151265
* * .. c:var:: PyObject *PyExc_SystemError
1266+
*
12161267
* :exc:`SystemError`
12171268
* * .. c:var:: PyObject *PyExc_SystemExit
1269+
*
12181270
* :exc:`SystemExit`
12191271
* * .. c:var:: PyObject *PyExc_TabError
1272+
*
12201273
* :exc:`TabError`
12211274
* * .. c:var:: PyObject *PyExc_TimeoutError
1275+
* 3.7
12221276
* :exc:`TimeoutError`
12231277
* * .. c:var:: PyObject *PyExc_TypeError
1278+
*
12241279
* :exc:`TypeError`
12251280
* * .. c:var:: PyObject *PyExc_UnboundLocalError
1281+
*
12261282
* :exc:`UnboundLocalError`
12271283
* * .. c:var:: PyObject *PyExc_UnicodeDecodeError
1284+
*
12281285
* :exc:`UnicodeDecodeError`
12291286
* * .. c:var:: PyObject *PyExc_UnicodeEncodeError
1287+
*
12301288
* :exc:`UnicodeEncodeError`
12311289
* * .. c:var:: PyObject *PyExc_UnicodeError
1290+
*
12321291
* :exc:`UnicodeError`
12331292
* * .. c:var:: PyObject *PyExc_UnicodeTranslateError
1293+
*
12341294
* :exc:`UnicodeTranslateError`
12351295
* * .. c:var:: PyObject *PyExc_ValueError
1296+
*
12361297
* :exc:`ValueError`
12371298
* * .. c:var:: PyObject *PyExc_ZeroDivisionError
1299+
*
12381300
* :exc:`ZeroDivisionError`
12391301
12401302
.. versionadded:: 3.3
@@ -1265,21 +1327,29 @@ The following are a compatibility aliases to :c:data:`PyExc_OSError`.
12651327
.. versionchanged:: 3.3
12661328
These aliases used to be separate exception types.
12671329
1330+
These pointers are part of the :ref:`Stable ABI <stable>` since the version
1331+
in the *S.ABI* column, or since 3.2 if that column is empty.
1332+
12681333
.. list-table::
12691334
:align: left
12701335
:widths: auto
12711336
:header-rows: 1
1337+
:class: no-stable-abi-notes
12721338
12731339
* * C name
1340+
* :ref:`S.ABI <stable>`
12741341
* Python name
12751342
* Notes
12761343
* * .. c:var:: PyObject *PyExc_EnvironmentError
1344+
*
12771345
* :exc:`OSError`
12781346
*
12791347
* * .. c:var:: PyObject *PyExc_IOError
1348+
*
12801349
* :exc:`OSError`
12811350
*
12821351
* * .. c:var:: PyObject *PyExc_WindowsError
1352+
* 3.7
12831353
* :exc:`OSError`
12841354
* [win]_
12851355
@@ -1295,36 +1365,53 @@ Notes:
12951365
Warning types
12961366
-------------
12971367
1368+
These pointers are part of the :ref:`Stable ABI <stable>` since the version
1369+
in the *S.ABI* column, or since 3.2 if that column is empty.
1370+
12981371
.. list-table::
12991372
:align: left
13001373
:widths: auto
13011374
:header-rows: 1
1375+
:class: no-stable-abi-notes
13021376
13031377
* * C name
1378+
* :ref:`S.ABI <stable>`
13041379
* Python name
13051380
* * .. c:var:: PyObject *PyExc_Warning
1381+
*
13061382
* :exc:`Warning`
13071383
* * .. c:var:: PyObject *PyExc_BytesWarning
1384+
*
13081385
* :exc:`BytesWarning`
13091386
* * .. c:var:: PyObject *PyExc_DeprecationWarning
1387+
*
13101388
* :exc:`DeprecationWarning`
13111389
* * .. c:var:: PyObject *PyExc_EncodingWarning
1390+
* 3.10
13121391
* :exc:`EncodingWarning`
13131392
* * .. c:var:: PyObject *PyExc_FutureWarning
1393+
*
13141394
* :exc:`FutureWarning`
13151395
* * .. c:var:: PyObject *PyExc_ImportWarning
1396+
*
13161397
* :exc:`ImportWarning`
13171398
* * .. c:var:: PyObject *PyExc_PendingDeprecationWarning
1399+
*
13181400
* :exc:`PendingDeprecationWarning`
13191401
* * .. c:var:: PyObject *PyExc_ResourceWarning
1402+
* 3.7
13201403
* :exc:`ResourceWarning`
13211404
* * .. c:var:: PyObject *PyExc_RuntimeWarning
1405+
*
13221406
* :exc:`RuntimeWarning`
13231407
* * .. c:var:: PyObject *PyExc_SyntaxWarning
1408+
*
13241409
* :exc:`SyntaxWarning`
13251410
* * .. c:var:: PyObject *PyExc_UnicodeWarning
1411+
*
13261412
* :exc:`UnicodeWarning`
13271413
* * .. c:var:: PyObject *PyExc_UserWarning
1414+
*
13281415
* :exc:`UserWarning`
13291416
13301417
.. versionadded:: 3.2

Doc/tools/extensions/c_annotations.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
"data": "var",
3737
}
3838

39+
# Class that suppresses the generated Stable ABI notes for all C API
40+
# elements documented inside the element it is set on (see
41+
# ``_stable_abi_note_suppressed``).
42+
NO_STABLE_ABI_NOTES_CLASS = "no-stable-abi-notes"
43+
3944

4045
@dataclasses.dataclass(slots=True)
4146
class RefCountEntry:
@@ -183,8 +188,9 @@ def add_annotations(app: Sphinx, doctree: nodes.document) -> None:
183188
f"{ROLE_TO_OBJECT_TYPE[record.role]!r} != {objtype!r}"
184189
)
185190
raise ValueError(msg)
186-
annotation = _stable_abi_annotation(record)
187-
node.insert(0, annotation)
191+
if not _stable_abi_note_suppressed(node):
192+
annotation = _stable_abi_annotation(record)
193+
node.insert(0, annotation)
188194

189195
# Unstable API annotation.
190196
if name.startswith("PyUnstable"):
@@ -203,6 +209,26 @@ def add_annotations(app: Sphinx, doctree: nodes.document) -> None:
203209
node.insert(0, annotation)
204210

205211

212+
def _stable_abi_note_suppressed(node: nodes.Element) -> bool:
213+
"""Return whether an ancestor of *node* opts out of Stable ABI notes.
214+
215+
In compact tables of many similar definitions, the generated notes
216+
get repetitive and take up too much vertical space.
217+
Such a table can instead carry a single manually written note, and
218+
suppress the generated ones by setting the ``no-stable-abi-notes``
219+
class, for example::
220+
221+
.. list-table::
222+
:class: no-stable-abi-notes
223+
"""
224+
ancestor = node.parent
225+
while ancestor is not None:
226+
if NO_STABLE_ABI_NOTES_CLASS in ancestor.get("classes", ()):
227+
return True
228+
ancestor = ancestor.parent
229+
return False
230+
231+
206232
def _stable_abi_annotation(
207233
record: StableABIEntry,
208234
is_corresponding_slot: bool = False,

0 commit comments

Comments
 (0)