-
Notifications
You must be signed in to change notification settings - Fork 28
/
cpp_resolve_c_xrefs.py
151 lines (113 loc) · 5.03 KB
/
cpp_resolve_c_xrefs.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
"""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.
"""
import re
from typing import Optional, Tuple, List, cast
import docutils.nodes
import sphinx.addnodes
import sphinx.domains.c
import sphinx.domains.cpp
from sphinx.domains.cpp import CPPExprRole
# 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.
from . import last_resolved_symbol # pylint: disable=unused-import
from . import synopses # pylint: disable=unused-import
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.apidoc.cpp.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("[^<>:()]+$")
"""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():
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
# chance to resolve it.
return None, None
if refnode is not None:
return refnode, objtype
macro_pattern = getattr(env.app, C_MACRO_PATTERN_ATTR, None)
if (macro_pattern is not None and macro_pattern.fullmatch(target)) or (
POSSIBLE_MACRO_PARAMETER_TARGET_PATTERN.fullmatch(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 # type: ignore[assignment]
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.
"""
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.findall(condition=sphinx.addnodes.pending_xref):
if refnode.get("refdomain") == "cpp":
refnode["c:parent_key"] = c_parent_key
return nodes, messages
CPPExprRole.run = run # type: ignore[assignment]
_monkey_patch_cpp_resolve_c_xrefs()
_monkey_patch_cpp_expr_role_to_include_c_parent_key()
C_MACRO_PATTERN_ATTR = "_sphinx_immaterial_cpp_xref_c_macro_pattern"
def _config_inited(
app: sphinx.application.Sphinx, config: sphinx.config.Config
) -> None:
c_macro_pattern = config.cpp_xref_resolve_c_macro_pattern
# Compute a regular expression that matches a macro name, or macro with
# `::<param>` suffix.
setattr(
app,
C_MACRO_PATTERN_ATTR,
re.compile("(?:" + c_macro_pattern + ")(?:::[a-zA-Z0-9_]+)?"),
)
def setup(app: sphinx.application.Sphinx):
app.add_config_value(
"cpp_xref_resolve_c_macro_pattern",
default="[A-Z]+[A-Z_0-9]*",
types=(str,),
rebuild="env",
)
app.connect("config-inited", _config_inited)
return {
"parallel_read_safe": True,
"parallel_write_safe": True,
}