-
Notifications
You must be signed in to change notification settings - Fork 28
/
cpp_domain_fixes.py
1019 lines (825 loc) · 34.7 KB
/
cpp_domain_fixes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Fixes for the C and C++ domains."""
import re
from typing import (
cast,
Tuple,
Iterator,
List,
Dict,
Type,
Optional,
Union,
Any,
)
import docutils.nodes
import docutils.parsers.rst.states
import sphinx.addnodes
import sphinx.application
import sphinx.directives
import sphinx.domains.c
import sphinx.domains.cpp
import sphinx.util.logging
from . import sphinx_utils
logger = sphinx.util.logging.getLogger(__name__)
PARAMETER_OBJECT_TYPES = (
"functionParam",
"macroParam",
"templateParam",
"templateTypeParam",
"templateTemplateParam",
"templateNonTypeParam",
)
CPP_PARAM_KIND_PATTERN = re.compile(r"([^\[]+)\[([^\]]+)\]$")
"""Regular expression pattern for matching parameter names with a "kind" suffix.
For example, "param[in]" or "param[out]" or "param[in, out]" all match with
kinds of "in", "out", and "in, out" respectively.
"""
class CppParamField(sphinx.util.docfields.GroupedField):
def make_field(
self,
types: Dict[str, List[docutils.nodes.Node]],
domain: str,
items: Tuple,
env: Optional[sphinx.environment.BuildEnvironment] = None,
inliner: Optional[docutils.parsers.rst.states.Inliner] = None,
location: Optional[docutils.nodes.Node] = None,
) -> docutils.nodes.field:
bodynode = docutils.nodes.definition_list()
bodynode["classes"].append("api-field")
bodynode["classes"].append("highlight")
def handle_item(
fieldarg: str, content: List[docutils.nodes.Node]
) -> docutils.nodes.Node:
node = docutils.nodes.definition_list_item()
term_node = docutils.nodes.term()
m = CPP_PARAM_KIND_PATTERN.fullmatch(fieldarg)
if m is not None:
param_name = m.group(1)
kind = m.group(2).strip()
else:
param_name = fieldarg
kind = None
term_node["paramname"] = param_name
if kind is not None:
term_node["param_kind"] = kind
term_node["toc_title"] = f"{param_name} [{kind}]"
term_node += sphinx.addnodes.desc_name(param_name, param_name)
node += term_node
def_node = docutils.nodes.definition()
p = docutils.nodes.paragraph()
p += content
def_node += p
node += def_node
return node
for fieldarg, content in items:
bodynode += handle_item(fieldarg, content)
fieldname = docutils.nodes.field_name("", self.label)
fieldbody = docutils.nodes.field_body("", bodynode)
return docutils.nodes.field("", fieldname, fieldbody)
def _monkey_patch_cpp_parameter_fields(doc_field_types):
for i, field in enumerate(doc_field_types):
if field.name in ("parameter", "template parameter"):
doc_field_types[i] = CppParamField(
name=field.name,
names=field.names,
label=field.label,
rolename=field.rolename,
can_collapse=field.can_collapse,
)
class desc_cpp_template_param(sphinx.addnodes.desc_sig_element):
"""Wraps a single template parameter.
This allows template parameters to be more easily identified when
transforming docutils nodes.
"""
class desc_cpp_template_params(sphinx.addnodes.desc_sig_element):
"""Wraps an entire template parameter list.
This allows template parameter lists to be more easily identified when
transforming docutils nodes.
"""
class desc_cpp_requires_clause(sphinx.addnodes.desc_sig_element):
"""Wraps a c++ requires clause.
This allows requires clauses to be more easily identified when transforming
docutils nodes.
"""
class desc_cpp_explicit(sphinx.addnodes.desc_sig_element):
"""Wraps an explicit spec.
This allows template parameters to be more easily identified when
transforming docutils nodes.
"""
def _monkey_patch_cpp_ast_template_params():
ASTTemplateParams = sphinx.domains.cpp.ASTTemplateParams
orig_describe_signature_as_introducer = (
ASTTemplateParams.describe_signature_as_introducer
)
def describe_signature_as_introducer(
self: ASTTemplateParams,
parentNode: sphinx.addnodes.desc_signature,
mode: str,
env: sphinx.environment.BuildEnvironment,
symbol: sphinx.domains.cpp.Symbol,
lineSpec: bool,
) -> None:
fake_parent = sphinx.addnodes.desc_signature("", "")
signode = desc_cpp_template_params("", "")
parentNode += signode
orig_describe_signature_as_introducer(
self, signode, mode, env, symbol, lineSpec
)
# Ensure the requires clause is not wrapped in
# `desc_cpp_template_params`.
if signode.children:
last_child = signode.children[-1]
if isinstance(last_child, desc_cpp_requires_clause):
del signode.children[-1]
parentNode += last_child
for x in signode.traverse(condition=sphinx.addnodes.desc_name):
# Ensure template parameter names aren't styled as the main entity
# name.
x["classes"].append("sig-name-nonprimary")
ASTTemplateParams.describe_signature_as_introducer = (
describe_signature_as_introducer
)
def _monkey_patch_cpp_ast_wrap_signature_node(
ast_type: Any, wrapper_type: Type[docutils.nodes.Element]
):
"""Patches an AST node type to wrap its signature with a docutils node."""
orig_describe_signature = ast_type.describe_signature
def describe_signature(
self: ast_type, signode: docutils.nodes.TextElement, *args, **kwargs
) -> None:
wrapper = wrapper_type("", "")
signode += wrapper
orig_describe_signature(self, wrapper, *args, **kwargs)
ast_type.describe_signature = describe_signature
last_resolved_symbol = None
"""Symbol resolved by `_resolve_xref_inner` or `get_objects`.
This allows additional customizations of those functions to access the symbol.
"""
def _monkey_patch_resolve_xref_save_symbol(symbol_class, domain_class):
"""Monkey patches C/C++ resolve_xref to save the resolved symbol.
This also other customizations to make use of the resolved symbol.
"""
orig_resolve_xref_inner = domain_class._resolve_xref_inner
def _resolve_xref_inner(
self: sphinx.domains.cpp.CPPDomain,
env: sphinx.environment.BuildEnvironment,
fromdocname: str,
builder: sphinx.builders.Builder,
typ: str,
target: str,
node: sphinx.addnodes.pending_xref,
contnode: docutils.nodes.Element,
) -> Tuple[Optional[docutils.nodes.Element], Optional[str]]:
global last_resolved_symbol
last_resolved_symbol = None
orig_find_name = getattr(symbol_class, "find_name", None)
if orig_find_name:
def find_name(self: symbol_class, *args, **kwargs):
global last_resolved_symbol
symbols, failReason = orig_find_name(self, *args, **kwargs)
if symbols:
last_resolved_symbol = symbols[0]
return symbols, failReason
symbol_class.find_name = find_name
orig_find_declaration = symbol_class.find_declaration
def find_declaration(self: symbol_class, *args, **kwargs):
global last_resolved_symbol
symbol = orig_find_declaration(self, *args, **kwargs)
if symbol:
last_resolved_symbol = symbol
return symbol
symbol_class.find_declaration = find_declaration
try:
return orig_resolve_xref_inner(
self, env, fromdocname, builder, typ, target, node, contnode
)
finally:
if orig_find_name:
symbol_class.find_name = orig_find_name
symbol_class.find_declaration = orig_find_declaration
domain_class._resolve_xref_inner = _resolve_xref_inner
POSSIBLE_MACRO_TARGET_PATTERN = re.compile("^[A-Z]+[A-Z_0-9]*(?:::[a-zA-Z0-9_]+)?$")
"""Pattern for targets that may possibly refer to a macro or macro parameter.
Any targets matching this pattern that are not defined as C++ symbols will be
looked up in the C domain.
For efficiency, this is restricted to names that consist of only uppercase
letters and digits, to avoid a duplicate C domain query for all symbols that
will be resolved by `sphinx_immaterial.external_cpp_references`.
Since macro parameters are named as "<MACRO_NAME>::<PARAMETER_NAME>", this
pattern allows "::" even though it would normally only match C++ symbols.
"""
POSSIBLE_MACRO_PARAMETER_TARGET_PATTERN = re.compile("^[a-zA-Z0-9_]+$")
"""Pattern for targets that may refer to a macro parameter within the scope of a
C macro definition.
"""
def _monkey_patch_cpp_resolve_c_xrefs():
"""Patch C and C++ domain resolve_xref implementation.
This adds support for:
- Resolving C macros from C++ domain xrefs. This allows a role like
`cpp:expr` to match both C++ symbols and C macro and macro parameters.
https://github.com/sphinx-doc/sphinx/issues/10262
- Normal resolution of missing symbols starting with `std::`. Normally, the
C++ domain silences warnings about these symbols in a way that blocks
`missing-reference` handlers from receiving them.
"""
orig_resolve_xref_inner = sphinx.domains.cpp.CPPDomain._resolve_xref_inner
def _resolve_xref_inner(
self: sphinx.domains.cpp.CPPDomain,
env: sphinx.environment.BuildEnvironment,
fromdocname: str,
builder: sphinx.builders.Builder,
typ: str,
target: str,
node: sphinx.addnodes.pending_xref,
contnode: docutils.nodes.Element,
) -> Tuple[Optional[docutils.nodes.Element], Optional[str]]:
try:
refnode, objtype = orig_resolve_xref_inner(
self, env, fromdocname, builder, typ, target, node, contnode
)
except sphinx.errors.NoUri:
# Only raised for `std::` symbols. Give `missing-reference` handlers a
# change to resolve it.
return None, None
if refnode is not None:
return refnode, objtype
if POSSIBLE_MACRO_TARGET_PATTERN.match(target) or (
POSSIBLE_MACRO_PARAMETER_TARGET_PATTERN.match(target)
and node.get("c:parent_key")
):
# Give C domain a chance to resolve it.
c_domain = cast(sphinx.domains.c.CDomain, env.get_domain("c"))
return c_domain._resolve_xref_inner(
env, fromdocname, builder, typ, target, node, contnode
)
return None, None
sphinx.domains.cpp.CPPDomain._resolve_xref_inner = _resolve_xref_inner
SYNOPSIS_ATTR = "_sphinx_immaterial_synopsis"
def set_synopsis(
symbol: Union[sphinx.domains.cpp.Symbol, sphinx.domains.c.Symbol], synopsis: str
) -> None:
"""Sets the synopsis for a given symbol."""
setattr(symbol.declaration, SYNOPSIS_ATTR, synopsis)
def _get_precise_template_parameter_object_type(
object_type: str, symbol: Optional[sphinx.domains.cpp.Symbol]
) -> str:
if object_type == "templateParam":
# Determine more precise object type.
if symbol is not None:
if isinstance(
symbol.declaration.declaration,
sphinx.domains.cpp.ASTTemplateParamNonType,
):
object_type = "templateNonTypeParam"
elif isinstance(
symbol.declaration.declaration,
sphinx.domains.cpp.ASTTemplateParamTemplateType,
):
object_type = "templateTemplateParam"
else:
object_type = "templateTypeParam"
return object_type
def _monkey_patch_add_object_type_and_synopsis(
domain_class: Union[
Type[sphinx.domains.cpp.CPPDomain], Type[sphinx.domains.c.CDomain]
]
):
"""Patch C/C++ resolve_xref to add object type-dependent CSS classes."""
orig_resolve_xref_inner = domain_class._resolve_xref_inner
def _resolve_xref_inner(
self: domain_class,
env: sphinx.environment.BuildEnvironment,
fromdocname: str,
builder: sphinx.builders.Builder,
typ: str,
target: str,
node: sphinx.addnodes.pending_xref,
contnode: docutils.nodes.Element,
) -> Tuple[Optional[docutils.nodes.Element], Optional[str]]:
refnode, objtype = orig_resolve_xref_inner(
self, env, fromdocname, builder, typ, target, node, contnode
)
if refnode is None:
return refnode, objtype
objtype = _get_precise_template_parameter_object_type(
objtype, last_resolved_symbol
)
if objtype in (
"templateTypeParam",
"templateTemplateParam",
"class",
"union",
"type",
"enum",
"concept",
):
refnode["classes"].append("desctype")
reftitle = refnode["reftitle"]
if last_resolved_symbol is not None:
label = self.get_type_name(self.object_types[objtype])
reftitle += f" ({label})"
synopsis = getattr(last_resolved_symbol.declaration, SYNOPSIS_ATTR, None)
if synopsis:
reftitle += f" — {synopsis}"
refnode["reftitle"] = reftitle
return refnode, objtype
domain_class._resolve_xref_inner = _resolve_xref_inner
def _monkey_patch_c_macro_parameter_symbols():
"""Adds support for the `macroParam` object type to the C domain."""
CSymbol = sphinx.domains.c.Symbol
ASTMacro = sphinx.domains.c.ASTMacro
ASTMacroParameter = sphinx.domains.c.ASTMacroParameter
ASTDeclaration = sphinx.domains.c.ASTDeclaration
orig_add_function_params = CSymbol._add_function_params
def _add_function_params(self: CSymbol) -> None:
orig_add_function_params(self)
if self.declaration is None or not isinstance(
self.declaration.declaration, ASTMacro
):
return
args = self.declaration.declaration.args
if not args:
return
for p in args:
nn = p.arg
if nn is None:
continue
decl = ASTDeclaration("macroParam", None, p)
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, decl, self.docname, self.line)
CSymbol._add_function_params = _add_function_params
def get_id(
self: ASTMacroParameter, version: int, objectType: str, symbol: CSymbol
) -> str:
# the anchor will be our parent
return symbol.parent.declaration.get_id(version, prefixed=False)
ASTMacroParameter.get_id = get_id
sphinx.domains.c.CDomain.object_types["macroParam"] = sphinx.domains.ObjType(
"macro parameter", "identifier", "var", "member", "data"
)
def _monkey_patch_cpp_expr_role_to_include_c_parent_key():
"""Includes ``c:parent_key`` in pending_xref nodes created by CPPExprRole.
This allows CPPExprRole to be used from the context of C object descriptions,
in particular macros.
"""
CPPExprRole = sphinx.domains.cpp.CPPExprRole
orig_run = CPPExprRole.run
def run(
self: CPPExprRole,
) -> Tuple[List[docutils.nodes.Node], List[docutils.nodes.system_message]]:
nodes, messages = orig_run(self)
c_parent_key = self.env.ref_context.get("c:parent_key")
if c_parent_key is not None:
for node in nodes:
for refnode in node.traverse(condition=sphinx.addnodes.pending_xref):
if refnode.get("refdomain") == "cpp":
refnode["c:parent_key"] = c_parent_key
return nodes, messages
CPPExprRole.run = run
ANCHOR_ATTR = "sphinx_immaterial_anchor"
def get_symbol_anchor(symbol):
anchor = getattr(symbol.declaration, ANCHOR_ATTR, None)
if anchor is None:
anchor = symbol.declaration.get_newest_id()
return anchor
OBJECT_PRIORITY_DEFAULT = 1
OBJECT_PRIORITY_UNIMPORTANT = 2
def _monkey_patch_cpp_add_precise_template_parameter_object_types():
"""Adds more precise template{Type,NonType,Template}Param object types."""
sphinx.domains.cpp.CPPDomain.object_types[
"templateTypeParam"
] = sphinx.domains.ObjType(
"type template parameter",
"identifier",
"class",
"struct",
"union",
"type",
)
sphinx.domains.cpp.CPPDomain.object_types[
"templateNonTypeParam"
] = sphinx.domains.ObjType(
"non-type template parameter", "identifier", "member", "var"
)
sphinx.domains.cpp.CPPDomain.object_types[
"templateTemplateParam"
] = sphinx.domains.ObjType(
"template template parameter", "identifier", "member", "var"
)
def get_objects(
self: sphinx.domains.cpp.CPPDomain,
) -> Iterator[Tuple[str, str, str, str, str, int]]:
global last_resolved_symbol
rootSymbol = self.data["root_symbol"]
for symbol in rootSymbol.get_all_symbols():
if symbol.declaration is None:
continue
assert symbol.docname
fullNestedName = symbol.get_full_nested_name()
name = str(fullNestedName).lstrip(":")
dispname = fullNestedName.get_display_string().lstrip(":")
objectType = _get_precise_template_parameter_object_type(
symbol.declaration.objectType, symbol
)
docname = symbol.docname
anchor = get_symbol_anchor(symbol)
# Allow other customizations to access the symbol.
last_resolved_symbol = symbol
if objectType in PARAMETER_OBJECT_TYPES:
priority = OBJECT_PRIORITY_UNIMPORTANT
else:
priority = OBJECT_PRIORITY_DEFAULT
yield (name, dispname, objectType, docname, anchor, priority)
sphinx.domains.cpp.CPPDomain.get_objects = get_objects
def _strip_namespaces_from_signature(
node: docutils.nodes.Element, namespaces: List[str]
):
# Collect nodes to remove first, then remove them in reverse order.
removals = []
for child in node.traverse(condition=sphinx.addnodes.desc_sig_name):
parent = child.parent
if not isinstance(parent, sphinx.addnodes.pending_xref):
continue
if (
parent["reftype"] != "identifier"
or parent["refdomain"] != "cpp"
or parent["reftarget"] not in namespaces
):
continue
grandparent = parent.parent
if isinstance(grandparent, sphinx.addnodes.desc_addname):
continue
index = grandparent.index(parent)
if index + 1 >= len(grandparent.children):
continue
sibling = grandparent[index + 1]
if (
not isinstance(sibling, sphinx.addnodes.desc_sig_punctuation)
or sibling.astext() != "::"
):
continue
removals.append((grandparent, index))
removals.reverse()
for (parent, index) in removals:
del parent[index : index + 2]
def _strip_namespaces_from_signatures(
app: sphinx.application.Sphinx,
domain: str,
objtype: str,
content: docutils.nodes.Element,
) -> None:
"""object-description-transform callback that strips namespaces."""
if domain != "cpp":
return
namespaces = app.config.cpp_strip_namespaces_from_signatures
if not namespaces:
return
signatures = content.parent[:-1]
for signode in signatures:
_strip_namespaces_from_signature(signode, namespaces)
def _monkey_patch_domain_to_support_include_directives(
object_class: Type[sphinx.directives.ObjectDescription], language: str
):
orig_get_signatures = object_class.get_signatures
def is_include(sig: str) -> bool:
return sig.startswith("#")
def get_signatures(self: object_class) -> List[str]:
return [sig for sig in orig_get_signatures(self) if not is_include(sig)]
object_class.get_signatures = get_signatures
orig_run = object_class.run
def run(self: object_class) -> List[docutils.nodes.Node]:
nodes = orig_run(self)
include_directives = [
sig for sig in orig_get_signatures(self) if is_include(sig)
]
if include_directives:
obj_desc = nodes[-1]
include_sig = sphinx.addnodes.desc_signature("", "")
include_sig["classes"].append("api-include-path")
for directive in include_directives:
container = docutils.nodes.container()
container += docutils.nodes.literal(
directive,
directive,
classes=[language, "code", "highlight"],
language=language,
)
include_sig.append(container)
self.set_source_info(include_sig)
obj_desc.insert(0, include_sig)
return nodes
object_class.run = run
def _add_parameter_links_to_signature(
env, signode: sphinx.addnodes.desc_signature, symbol, domain: str
) -> Dict[str, docutils.nodes.Element]:
"""Cross-links parameter names in signature to parameter objects.
Returns:
Map of parameter name to original (not linked) parameter node.
"""
sig_param_nodes: Dict[str, docutils.nodes.Element] = {}
replacements = []
node_identifier_key = "sphinx_immaterial_param_name_identifier"
def add_replacement(
name_node: docutils.nodes.Element, param_node: docutils.nodes.Element
) -> docutils.nodes.Element:
replacements.append((name_node, param_node))
name = name_node.astext()
# Mark `name_node` so that it can be identified after the deep copy of its
# ancestor `param_node`.
name_node[node_identifier_key] = True
param_node_copy = param_node.deepcopy()
source, line = docutils.utils.get_source_line(param_node)
param_node_copy.source = source
param_node_copy.line = line
sig_param_nodes[name] = param_node_copy
del name_node[node_identifier_key]
for name_node_copy in param_node_copy.traverse(condition=type(name_node)):
if name_node_copy.get(node_identifier_key):
return name_node_copy
raise ValueError("Could not locate name node within parameter")
for sig_param_node in signode.traverse(condition=sphinx.addnodes.desc_sig_name):
desc_param_node = sig_param_node.parent
if not isinstance(desc_param_node, sphinx.addnodes.desc_parameter):
continue
while not isinstance(desc_param_node, sphinx.addnodes.desc_parameter):
desc_param_node = desc_param_node.parent
new_sig_param_node = add_replacement(sig_param_node, sig_param_node.parent)
new_sig_param_node["classes"].append("sig-name")
for desc_sig_name_node in signode.traverse(condition=sphinx.addnodes.desc_sig_name):
parent = desc_sig_name_node.parent
if not isinstance(parent, sphinx.addnodes.desc_name):
continue
grandparent = parent.parent
if not isinstance(grandparent, desc_cpp_template_param):
continue
new_sig_name_node = add_replacement(desc_sig_name_node, grandparent)
new_sig_name_node.parent["classes"].remove("sig-name-nonprimary")
lookup_key = symbol.get_lookup_key()
for name_node, param_node in replacements:
name = name_node.astext()
refnode = sphinx.addnodes.pending_xref(
"",
name_node.deepcopy(),
refdomain="cpp",
reftype="identifier",
reftarget=name,
refwarn=True,
)
refnode[f"{domain}:parent_key"] = lookup_key
name_node.replace_self(refnode)
return sig_param_nodes
def _add_parameter_documentation_ids(
env,
obj_content: sphinx.addnodes.desc_content,
sig_param_nodes_for_signature: List[Dict[str, docutils.nodes.Element]],
symbols,
domain_module,
starting_id_version,
) -> None:
qualify_parameter_ids = env.config.cpp_qualify_parameter_ids
for param_node in list(obj_content.traverse(condition=docutils.nodes.term)):
param_name = param_node.get("paramname")
if not param_name:
continue
kind = param_node.get("param_kind")
# Determine the number of unique declarations of this parameter
unique_decls: Dict[str, Tuple[int, docutils.nodes.Element]] = {}
for i, sig_param_nodes in enumerate(sig_param_nodes_for_signature):
desc_param_node = sig_param_nodes.get(param_name)
if desc_param_node is None:
continue
decl_text = desc_param_node.astext().strip()
unique_decls.setdefault(decl_text, (i, desc_param_node))
if not unique_decls:
logger.warning(
"Invalid parameter name: %r", param_name, location=param_node
)
continue
if env.config.cpp_generate_synopses:
synopsis = sphinx_utils.summarize_element_text(param_node.parent[-1])
else:
synopsis = None
# Set ids of the parameter node.
for symbol_i, _ in unique_decls.values():
parent_symbol = symbols[symbol_i]
param_symbol = parent_symbol.find_identifier(
domain_module.ASTIdentifier(param_name),
matchSelf=False,
recurseInAnon=False,
searchInSiblings=False,
)
if param_symbol is None or param_symbol.declaration is None:
logger.warning(
"Failed to find parameter symbol: %r",
param_name,
location=param_node,
)
continue
if synopsis:
set_synopsis(param_symbol, synopsis)
param_id_suffix = f"p-{param_name}"
# Set symbol id, since by default parameters don't have unique ids,
# they just use the same id as the parent symbol.
setattr(
param_symbol.declaration,
AST_ID_OVERRIDE_ATTR,
parent_symbol.declaration.get_newest_id() + "-" + param_id_suffix,
)
if qualify_parameter_ids:
# Generate a separate id for each id version.
prev_parent_id = None
id_prefixes = []
for i in range(starting_id_version, domain_module._max_id + 1):
try:
parent_id = parent_symbol.declaration.get_id(version=i)
if parent_id == prev_parent_id:
continue
prev_parent_id = parent_id
id_prefixes.append(parent_id + "-")
except domain_module.NoOldIdError:
continue
else:
id_prefixes = [""]
setattr(param_symbol.declaration, ANCHOR_ATTR, param_id_suffix)
if id_prefixes:
for id_prefix in id_prefixes:
param_id = id_prefix + param_id_suffix
param_node["ids"].append(param_id)
if not qualify_parameter_ids:
param_node["ids"].append(param_id_suffix)
del param_node[:]
new_param_nodes = []
if kind:
kind_node = docutils.nodes.term(kind, kind)
kind_node["classes"].append("api-parameter-kind")
new_param_nodes.append(kind_node)
for i, desc_param_node in unique_decls.values():
new_param_node = param_node.deepcopy()
if i != 0:
del new_param_node["ids"][:]
source, line = docutils.utils.get_source_line(desc_param_node)
new_children = list(c.deepcopy() for c in desc_param_node.children)
new_param_node.extend(new_children)
for child in new_children:
child.source = source
child.line = line
new_param_nodes.append(new_param_node)
param_node.parent[:1] = new_param_nodes
_FIRST_PARAMETER_ID_VERSIONS: Dict[str, int] = {"c": 1, "cpp": 4}
"""First id version to include when generating parameter ids.
Multiple id versions allow old anchors to work on new versions of the
documentation (as long as the signatures remain identical).
However, there is no need to support id versions from before this support was
added to the theme.
"""
def _cross_link_parameters(
app: sphinx.application.Sphinx,
domain: str,
content: docutils.nodes.Element,
symbols,
) -> None:
"""object-description-transform callback that cross-links parameters."""
obj_desc = content.parent
signodes = [
signode
for signode in obj_desc.children[:-1]
if "api-include-path" not in signode["classes"]
]
assert len(signodes) == len(symbols)
sig_param_nodes_for_signature = []
for signode, symbol in zip(signodes, symbols):
sig_param_nodes_for_signature.append(
_add_parameter_links_to_signature(app.env, signode, symbol, domain=domain)
)
_add_parameter_documentation_ids(
env=app.env,
obj_content=content,
sig_param_nodes_for_signature=sig_param_nodes_for_signature,
symbols=symbols,
domain_module=getattr(sphinx.domains, domain),
starting_id_version=_FIRST_PARAMETER_ID_VERSIONS[domain],
)
def _monkey_patch_domain_to_cross_link_parameters_and_add_synopses(
object_class: Type[sphinx.directives.ObjectDescription],
domain: str,
):
orig_after_content = object_class.after_content
orig_transform_content = object_class.transform_content
def transform_content(self: object_class, contentnode) -> None:
self.contentnode = contentnode
orig_transform_content(self, contentnode)
object_class.transform_content = transform_content
def after_content(self: object_class) -> None:
symbols = [self.env.temp_data[f"{domain}:parent_symbol"]]
while symbols[-1].siblingAbove:
symbols.append(symbols[-1].siblingAbove)
symbols.reverse()
_cross_link_parameters(
app=self.env.app, domain=domain, content=self.contentnode, symbols=symbols
)
if self.env.config.cpp_generate_synopses:
synopsis = sphinx_utils.summarize_element_text(
self.contentnode, first_sentence_only=False
)
if synopsis:
for symbol in symbols:
set_synopsis(symbol, synopsis)
orig_after_content(self)
object_class.after_content = after_content
AST_ID_OVERRIDE_ATTR = "_sphinx_immaterial_id"
def _monkey_patch_override_ast_id(ast_declaration_class):
"""Allows the Symbol id to be overridden."""
orig_get_id = ast_declaration_class.get_id
def get_id(self: ast_declaration_class, version: int, prefixed: bool = True):
entity_id = getattr(self, AST_ID_OVERRIDE_ATTR, None)
if entity_id is not None:
return entity_id
return orig_get_id(self, version, prefixed)
ast_declaration_class.get_id = get_id
def _monkey_patch_domain_get_object_synopses(domain_class):
def get_object_synopses(
self: domain_class,
) -> Iterator[Tuple[Tuple[str, str], str]]:
for symbol in self.data["root_symbol"].get_all_symbols():
if symbol.declaration is None:
continue
assert symbol.docname
synopsis = getattr(symbol.declaration, SYNOPSIS_ATTR, None)
if not synopsis:
continue
yield ((symbol.docname, symbol.declaration.get_newest_id()), synopsis)
domain_class.get_object_synopses = get_object_synopses
def setup(app: sphinx.application.Sphinx):
_monkey_patch_cpp_parameter_fields(sphinx.domains.cpp.CPPObject.doc_field_types)
_monkey_patch_cpp_parameter_fields(
sphinx.domains.cpp.CPPFunctionObject.doc_field_types
)
_monkey_patch_cpp_parameter_fields(sphinx.domains.c.CObject.doc_field_types)
_monkey_patch_cpp_parameter_fields(sphinx.domains.c.CFunctionObject.doc_field_types)
_monkey_patch_cpp_parameter_fields(sphinx.domains.c.CMacroObject.doc_field_types)
_monkey_patch_cpp_ast_template_params()
for ast_type in (
sphinx.domains.cpp.ASTTemplateParamType,
sphinx.domains.cpp.ASTTemplateParamTemplateType,
sphinx.domains.cpp.ASTTemplateParamNonType,
):
_monkey_patch_cpp_ast_wrap_signature_node(
ast_type=ast_type, wrapper_type=desc_cpp_template_param
)
_monkey_patch_cpp_ast_wrap_signature_node(
ast_type=sphinx.domains.cpp.ASTExplicitSpec, wrapper_type=desc_cpp_explicit
)
_monkey_patch_cpp_ast_wrap_signature_node(
ast_type=sphinx.domains.cpp.ASTRequiresClause,
wrapper_type=desc_cpp_requires_clause,
)
for node in (
desc_cpp_template_param,
desc_cpp_template_params,
desc_cpp_requires_clause,
desc_cpp_explicit,
):
app.add_node(node)
sphinx.addnodes.SIG_ELEMENTS.append(node)
# We monkey patch `resolve_xref` multiple times. We must call
# `_monkey_patch_cpp_resolve_c_xrefs` last, to ensure that the other logic
# only runs once.
_monkey_patch_resolve_xref_save_symbol(
sphinx.domains.cpp.Symbol, sphinx.domains.cpp.CPPDomain
)
_monkey_patch_resolve_xref_save_symbol(
sphinx.domains.c.Symbol, sphinx.domains.c.CDomain
)
_monkey_patch_add_object_type_and_synopsis(sphinx.domains.c.CDomain)
_monkey_patch_add_object_type_and_synopsis(sphinx.domains.cpp.CPPDomain)
_monkey_patch_cpp_resolve_c_xrefs()
_monkey_patch_c_macro_parameter_symbols()
_monkey_patch_cpp_expr_role_to_include_c_parent_key()
_monkey_patch_domain_to_support_include_directives(
object_class=sphinx.domains.cpp.CPPObject, language="cpp"
)
_monkey_patch_domain_to_support_include_directives(
object_class=sphinx.domains.c.CObject, language="c"
)
app.add_config_value(
"cpp_strip_namespaces_from_signatures", default=[], rebuild="env"
)
app.add_config_value("cpp_qualify_parameter_ids", default=True, rebuild="env")
app.connect("object-description-transform", _strip_namespaces_from_signatures)