diff --git a/CHANGES b/CHANGES index 91aacb9a436..905b10f3bc2 100644 --- a/CHANGES +++ b/CHANGES @@ -76,6 +76,12 @@ Features added * C++, also hyperlink operator overloads in expressions and alias declarations. * #8247: Allow production lists to refer to tokens from other production groups * #8813: Show what extension (or module) caused it on errors on event handler +* #8213: C++: add ``maxdepth`` option to :rst:dir:`cpp:alias` to insert nested + declarations. +* C, add ``noroot`` option to :rst:dir:`c:alias` to render only nested + declarations. +* C++, add ``noroot`` option to :rst:dir:`cpp:alias` to render only nested + declarations. Bugs fixed ---------- @@ -148,6 +154,9 @@ Bugs fixed builds * #8865: LaTeX: Restructure the index nodes inside title nodes only on LaTeX builds +* C, :rst:dir:`c:alias` skip symbols without explicit declarations + instead of crashing. +* C, :rst:dir:`c:alias` give a warning when the root symbol is not declared. Testing -------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index f3754ab7c20..e4909f7930d 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -755,6 +755,13 @@ The following directive can be used for this purpose. .. versionadded:: 3.3 + .. rst:directive:option:: noroot + + Skip the mentioned declarations and only render nested declarations. + Requires ``maxdepth`` either 0 or at least 2. + + .. versionadded:: 3.5 + .. c:namespace-pop:: @@ -1179,6 +1186,24 @@ The following directive can be used for this purpose. .. versionadded:: 2.0 + .. rubric:: Options + + .. rst:directive:option:: maxdepth: int + + Insert nested declarations as well, up to the total depth given. + Use 0 for infinite depth and 1 for just the mentioned declaration. + Defaults to 1. + + .. versionadded:: 3.5 + + .. rst:directive:option:: noroot + + Skip the mentioned declarations and only render nested declarations. + Requires ``maxdepth`` either 0 or at least 2. + + .. versionadded:: 3.5 + + Constrained Templates ~~~~~~~~~~~~~~~~~~~~~ diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 5336b003c89..061010d6651 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -3435,12 +3435,12 @@ def run(self) -> List[Node]: class AliasNode(nodes.Element): - def __init__(self, sig: str, maxdepth: int, document: Any, env: "BuildEnvironment" = None, + def __init__(self, sig: str, aliasOptions: dict, + document: Any, env: "BuildEnvironment" = None, parentKey: LookupKey = None) -> None: super().__init__() self.sig = sig - self.maxdepth = maxdepth - assert maxdepth >= 0 + self.aliasOptions = aliasOptions self.document = document if env is not None: if 'c:parent_symbol' not in env.temp_data: @@ -3452,19 +3452,16 @@ def __init__(self, sig: str, maxdepth: int, document: Any, env: "BuildEnvironmen self.parentKey = parentKey def copy(self) -> 'AliasNode': - return self.__class__(self.sig, self.maxdepth, self.document, + return self.__class__(self.sig, self.aliasOptions, self.document, env=None, parentKey=self.parentKey) class AliasTransform(SphinxTransform): default_priority = ReferencesResolver.default_priority - 1 - def _render_symbol(self, s: Symbol, maxdepth: int, document: Any) -> List[Node]: - nodes = [] # type: List[Node] - options = dict() # type: ignore - signode = addnodes.desc_signature('', '') - nodes.append(signode) - s.declaration.describe_signature(signode, 'markName', self.env, options) + def _render_symbol(self, s: Symbol, maxdepth: int, skipThis: bool, + aliasOptions: dict, renderOptions: dict, + document: Any) -> List[Node]: if maxdepth == 0: recurse = True elif maxdepth == 1: @@ -3472,26 +3469,43 @@ def _render_symbol(self, s: Symbol, maxdepth: int, document: Any) -> List[Node]: else: maxdepth -= 1 recurse = True + + nodes = [] # type: List[Node] + if not skipThis: + signode = addnodes.desc_signature('', '') + nodes.append(signode) + s.declaration.describe_signature(signode, 'markName', self.env, renderOptions) + if recurse: - content = addnodes.desc_content() - desc = addnodes.desc() - content.append(desc) - desc.document = document - desc['domain'] = 'c' - # 'desctype' is a backwards compatible attribute - desc['objtype'] = desc['desctype'] = 'alias' - desc['noindex'] = True + if skipThis: + childContainer = nodes # type: Union[List[Node], addnodes.desc] + else: + content = addnodes.desc_content() + desc = addnodes.desc() + content.append(desc) + desc.document = document + desc['domain'] = 'c' + # 'desctype' is a backwards compatible attribute + desc['objtype'] = desc['desctype'] = 'alias' + desc['noindex'] = True + childContainer = desc for sChild in s.children: - childNodes = self._render_symbol(sChild, maxdepth, document) - desc.extend(childNodes) + if sChild.declaration is None: + continue + childNodes = self._render_symbol( + sChild, maxdepth=maxdepth, skipThis=False, + aliasOptions=aliasOptions, renderOptions=renderOptions, + document=document) + childContainer.extend(childNodes) - if len(desc.children) != 0: + if not skipThis and len(desc.children) != 0: nodes.append(content) return nodes def apply(self, **kwargs: Any) -> None: for node in self.document.traverse(AliasNode): + node = cast(AliasNode, node) sig = node.sig parentKey = node.parentKey try: @@ -3531,17 +3545,40 @@ def apply(self, **kwargs: Any) -> None: location=node) node.replace_self(signode) continue + # Declarations like .. var:: int Missing::var + # may introduce symbols without declarations. + # But if we skip the root then it is ok to start recursion from it. + if not node.aliasOptions['noroot'] and s.declaration is None: + signode = addnodes.desc_signature(sig, '') + node.append(signode) + signode.clear() + signode += addnodes.desc_name(sig, sig) + + logger.warning( + "Can not render C declaration for alias '%s'. No such declaration." % name, + location=node) + node.replace_self(signode) + continue - nodes = self._render_symbol(s, maxdepth=node.maxdepth, document=node.document) + nodes = self._render_symbol(s, maxdepth=node.aliasOptions['maxdepth'], + skipThis=node.aliasOptions['noroot'], + aliasOptions=node.aliasOptions, + renderOptions=dict(), document=node.document) node.replace_self(nodes) class CAliasObject(ObjectDescription): option_spec = { - 'maxdepth': directives.nonnegative_int + 'maxdepth': directives.nonnegative_int, + 'noroot': directives.flag, } # type: Dict def run(self) -> List[Node]: + """ + On purpose this doesn't call the ObjectDescription version, but is based on it. + Each alias signature may expand into multiple real signatures if 'noroot'. + The code is therefore based on the ObjectDescription version. + """ if ':' in self.name: self.domain, self.objtype = self.name.split(':', 1) else: @@ -3555,10 +3592,19 @@ def run(self) -> List[Node]: node['noindex'] = True self.names = [] # type: List[str] - maxdepth = self.options.get('maxdepth', 1) + aliasOptions = { + 'maxdepth': self.options.get('maxdepth', 1), + 'noroot': 'noroot' in self.options, + } + if aliasOptions['noroot'] and aliasOptions['maxdepth'] == 1: + logger.warning("Error in C alias declaration." + " Requested 'noroot' but 'maxdepth' 1." + " When skipping the root declaration," + " need 'maxdepth' 0 for infinite or at least 2.", + location=self.get_source_info()) signatures = self.get_signatures() for i, sig in enumerate(signatures): - node.append(AliasNode(sig, maxdepth, self.state.document, env=self.env)) + node.append(AliasNode(sig, aliasOptions, self.state.document, env=self.env)) return [node] diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 25e6f1421b6..7ee023d1dc1 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -10,7 +10,7 @@ import re from typing import (Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple, Type, - TypeVar, Union) + TypeVar, Union, cast) from docutils import nodes from docutils.nodes import Element, Node, TextElement, system_message @@ -3742,6 +3742,7 @@ def describe_signature(self, signode: desc_signature, mode: str, elif self.objectType == 'enumerator': mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ') else: + print(self.objectType) assert False self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol) lastDeclNode = mainDeclNode @@ -7046,10 +7047,12 @@ def run(self) -> List[Node]: class AliasNode(nodes.Element): - def __init__(self, sig: str, env: "BuildEnvironment" = None, + def __init__(self, sig: str, aliasOptions: dict, + env: "BuildEnvironment" = None, parentKey: LookupKey = None) -> None: super().__init__() self.sig = sig + self.aliasOptions = aliasOptions if env is not None: if 'cpp:parent_symbol' not in env.temp_data: root = env.domaindata['cpp']['root_symbol'] @@ -7060,14 +7063,62 @@ def __init__(self, sig: str, env: "BuildEnvironment" = None, self.parentKey = parentKey def copy(self) -> 'AliasNode': - return self.__class__(self.sig, env=None, parentKey=self.parentKey) + return self.__class__(self.sig, self.aliasOptions, + env=None, parentKey=self.parentKey) class AliasTransform(SphinxTransform): default_priority = ReferencesResolver.default_priority - 1 + def _render_symbol(self, s: Symbol, maxdepth: int, skipThis: bool, + aliasOptions: dict, renderOptions: dict, + document: Any) -> List[Node]: + if maxdepth == 0: + recurse = True + elif maxdepth == 1: + recurse = False + else: + maxdepth -= 1 + recurse = True + + nodes = [] # type: List[Node] + if not skipThis: + signode = addnodes.desc_signature('', '') + nodes.append(signode) + s.declaration.describe_signature(signode, 'markName', self.env, renderOptions) + + if recurse: + if skipThis: + childContainer = nodes # type: Union[List[Node], addnodes.desc] + else: + content = addnodes.desc_content() + desc = addnodes.desc() + content.append(desc) + desc.document = document + desc['domain'] = 'cpp' + # 'desctype' is a backwards compatible attribute + desc['objtype'] = desc['desctype'] = 'alias' + desc['noindex'] = True + childContainer = desc + + for sChild in s._children: + if sChild.declaration is None: + continue + if sChild.declaration.objectType in ("templateParam", "functionParam"): + continue + childNodes = self._render_symbol( + sChild, maxdepth=maxdepth, skipThis=False, + aliasOptions=aliasOptions, renderOptions=renderOptions, + document=document) + childContainer.extend(childNodes) + + if not skipThis and len(desc.children) != 0: + nodes.append(content) + return nodes + def apply(self, **kwargs: Any) -> None: for node in self.document.traverse(AliasNode): + node = cast(AliasNode, node) sig = node.sig parentKey = node.parentKey try: @@ -7131,22 +7182,31 @@ def apply(self, **kwargs: Any) -> None: signode.clear() signode += addnodes.desc_name(sig, sig) - logger.warning("Could not find C++ declaration for alias '%s'." % ast, + logger.warning("Can not find C++ declaration for alias '%s'." % ast, location=node) node.replace_self(signode) else: nodes = [] - options = dict() - options['tparam-line-spec'] = False + renderOptions = { + 'tparam-line-spec': False, + } for s in symbols: - signode = addnodes.desc_signature(sig, '') - nodes.append(signode) - s.declaration.describe_signature(signode, 'markName', self.env, options) + assert s.declaration is not None + res = self._render_symbol( + s, maxdepth=node.aliasOptions['maxdepth'], + skipThis=node.aliasOptions['noroot'], + aliasOptions=node.aliasOptions, + renderOptions=renderOptions, + document=node.document) + nodes.extend(res) node.replace_self(nodes) class CPPAliasObject(ObjectDescription): - option_spec = {} # type: Dict + option_spec = { + 'maxdepth': directives.nonnegative_int, + 'noroot': directives.flag, + } # type: Dict def run(self) -> List[Node]: """ @@ -7166,9 +7226,19 @@ def run(self) -> List[Node]: node['objtype'] = node['desctype'] = self.objtype self.names = [] # type: List[str] + aliasOptions = { + 'maxdepth': self.options.get('maxdepth', 1), + 'noroot': 'noroot' in self.options, + } + if aliasOptions['noroot'] and aliasOptions['maxdepth'] == 1: + logger.warning("Error in C++ alias declaration." + " Requested 'noroot' but 'maxdepth' 1." + " When skipping the root declaration," + " need 'maxdepth' 0 for infinite or at least 2.", + location=self.get_source_info()) signatures = self.get_signatures() for i, sig in enumerate(signatures): - node.append(AliasNode(sig, env=self.env)) + node.append(AliasNode(sig, aliasOptions, env=self.env)) contentnode = addnodes.desc_content() node.append(contentnode)