summaryrefslogtreecommitdiff
path: root/sphinx/domains/cpp.py
diff options
context:
space:
mode:
authorJeremy Maitin-Shepard <jbms@google.com>2022-03-21 17:28:20 -0700
committerJakob Lykke Andersen <Jakob@caput.dk>2022-07-24 21:37:56 +0200
commitac1b0d490cd572fb048e80574e6cc8e7fed94e0a (patch)
treee14cacf53a7f5b9c50011afa03aa1446486554d1 /sphinx/domains/cpp.py
parent3c469c4258107589f35ae6612f5d7ecade97c805 (diff)
downloadsphinx-git-ac1b0d490cd572fb048e80574e6cc8e7fed94e0a.tar.gz
[C++] Support requires-clause in more places
Previously a C++20 requires-clause was only supported on `function` declarations. However, the C++ standard allows a require-clause on class/union templates, alias templates, and variable templates, and also allows a requires clause after each template parameter list, not just the final one. This moves the requiresClause to be a property of `ASTTemplateParams` rather than `ASTDeclaration` to better match the C++ grammar and allows requires clauses in many places that are supported by C++20 but were not previously allowed by Sphinx, namely: - On class templates, alias templates, and variable templates - After each template parameter list, not just the last one. - After the template parameter list in template template parameters. When encoding the id, the requires clause of the last template parameter list is treated specially in order to preserve compatibility with existing v4 ids.
Diffstat (limited to 'sphinx/domains/cpp.py')
-rw-r--r--sphinx/domains/cpp.py99
1 files changed, 66 insertions, 33 deletions
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index d7dbc8b48..b7ecc1651 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -3687,17 +3687,29 @@ class ASTTemplateParamNonType(ASTTemplateParam):
class ASTTemplateParams(ASTBase):
- def __init__(self, params: List[ASTTemplateParam]) -> None:
+ def __init__(self, params: List[ASTTemplateParam],
+ requiresClause: Optional["ASTRequiresClause"]) -> None:
assert params is not None
self.params = params
+ self.requiresClause = requiresClause
- def get_id(self, version: int) -> str:
+ def get_id(self, version: int, exclude_requires: bool = False) -> str:
+ # Note: For `version==4`, `exclude_requires` is set to `True` when
+ # encoding the id of the last template parameter list of a declaration,
+ # as that requires-clause, if any, is instead encoded by
+ # `ASTDeclaration.get_id` after encoding the template prefix, for
+ # consistency with the existing v4 format used when only a single
+ # requires-clause was supported.
assert version >= 2
res = []
res.append("I")
for param in self.params:
res.append(param.get_id(version))
res.append("E")
+ if not exclude_requires and self.requiresClause:
+ res.append('IQ')
+ res.append(self.requiresClause.expr.get_id(version))
+ res.append('E')
return ''.join(res)
def _stringify(self, transform: StringifyTransform) -> str:
@@ -3705,6 +3717,9 @@ class ASTTemplateParams(ASTBase):
res.append("template<")
res.append(", ".join(transform(a) for a in self.params))
res.append("> ")
+ if self.requiresClause is not None:
+ res.append(transform(self.requiresClause))
+ res.append(" ")
return ''.join(res)
def describe_signature(self, signode: TextElement, mode: str,
@@ -3719,6 +3734,9 @@ class ASTTemplateParams(ASTBase):
first = False
param.describe_signature(signode, mode, env, symbol)
signode += addnodes.desc_sig_punctuation('>', '>')
+ if self.requiresClause:
+ signode += addnodes.desc_sig_space()
+ self.requiresClause.describe_signature(signode, mode, env, symbol)
def describe_signature_as_introducer(
self, parentNode: desc_signature, mode: str, env: "BuildEnvironment",
@@ -3743,6 +3761,11 @@ class ASTTemplateParams(ASTBase):
if lineSpec and not first:
lineNode = makeLine(parentNode)
lineNode += addnodes.desc_sig_punctuation('>', '>')
+ if self.requiresClause:
+ reqNode = addnodes.desc_signature_line()
+ reqNode.sphinx_line_type = 'requiresClause'
+ parentNode += reqNode
+ self.requiresClause.describe_signature(reqNode, 'markType', env, symbol)
# Template introducers
@@ -3865,8 +3888,12 @@ class ASTTemplateDeclarationPrefix(ASTBase):
assert version >= 2
# this is not part of a normal name mangling system
res = []
- for t in self.templates:
- res.append(t.get_id(version))
+ last_index = len(self.templates) - 1
+ for i, t in enumerate(self.templates):
+ if isinstance(t, ASTTemplateParams):
+ res.append(t.get_id(version, exclude_requires=(i == last_index)))
+ else:
+ res.append(t.get_id(version))
return ''.join(res)
def _stringify(self, transform: StringifyTransform) -> str:
@@ -3889,7 +3916,7 @@ class ASTRequiresClause(ASTBase):
def _stringify(self, transform: StringifyTransform) -> str:
return 'requires ' + transform(self.expr)
- def describe_signature(self, signode: addnodes.desc_signature_line, mode: str,
+ def describe_signature(self, signode: nodes.TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += addnodes.desc_sig_keyword('requires', 'requires')
signode += addnodes.desc_sig_space()
@@ -3900,16 +3927,16 @@ class ASTRequiresClause(ASTBase):
################################################################################
class ASTDeclaration(ASTBase):
- def __init__(self, objectType: str, directiveType: str, visibility: str,
- templatePrefix: ASTTemplateDeclarationPrefix,
- requiresClause: ASTRequiresClause, declaration: Any,
- trailingRequiresClause: ASTRequiresClause,
+ def __init__(self, objectType: str, directiveType: Optional[str] = None,
+ visibility: Optional[str] = None,
+ templatePrefix: Optional[ASTTemplateDeclarationPrefix] = None,
+ declaration: Any = None,
+ trailingRequiresClause: Optional[ASTRequiresClause] = None,
semicolon: bool = False) -> None:
self.objectType = objectType
self.directiveType = directiveType
self.visibility = visibility
self.templatePrefix = templatePrefix
- self.requiresClause = requiresClause
self.declaration = declaration
self.trailingRequiresClause = trailingRequiresClause
self.semicolon = semicolon
@@ -3920,11 +3947,10 @@ class ASTDeclaration(ASTBase):
def clone(self) -> "ASTDeclaration":
templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
- requiresClasueClone = self.requiresClause.clone() if self.requiresClause else None
trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
if self.trailingRequiresClause else None
return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
- templatePrefixClone, requiresClasueClone,
+ templatePrefixClone,
self.declaration.clone(), trailingRequiresClasueClone,
self.semicolon)
@@ -3933,6 +3959,18 @@ class ASTDeclaration(ASTBase):
return self.declaration.name
@property
+ def requiresClause(self) -> Optional[ASTRequiresClause]:
+ templatePrefix = self.templatePrefix
+ if templatePrefix is None:
+ return None
+ if not templatePrefix.templates:
+ return None
+ last_template = templatePrefix.templates[-1]
+ if not isinstance(last_template, ASTTemplateParams):
+ return None
+ return last_template.requiresClause
+
+ @property
def function_params(self) -> List[ASTFunctionParameter]:
if self.objectType != 'function':
return None
@@ -3940,7 +3978,7 @@ class ASTDeclaration(ASTBase):
def get_id(self, version: int, prefixed: bool = True) -> str:
if version == 1:
- if self.templatePrefix:
+ if self.templatePrefix or self.trailingRequiresClause:
raise NoOldIdError()
if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
return self.enumeratorScopedSymbol.declaration.get_id(version)
@@ -3954,14 +3992,17 @@ class ASTDeclaration(ASTBase):
res = []
if self.templatePrefix:
res.append(self.templatePrefix.get_id(version))
- if self.requiresClause or self.trailingRequiresClause:
+ # Encode the last requires clause specially to avoid introducing a new
+ # id version number.
+ requiresClause = self.requiresClause
+ if requiresClause or self.trailingRequiresClause:
if version < 4:
raise NoOldIdError()
res.append('IQ')
- if self.requiresClause and self.trailingRequiresClause:
+ if requiresClause and self.trailingRequiresClause:
res.append('aa')
- if self.requiresClause:
- res.append(self.requiresClause.expr.get_id(version))
+ if requiresClause:
+ res.append(requiresClause.expr.get_id(version))
if self.trailingRequiresClause:
res.append(self.trailingRequiresClause.expr.get_id(version))
res.append('E')
@@ -3978,9 +4019,6 @@ class ASTDeclaration(ASTBase):
res.append(' ')
if self.templatePrefix:
res.append(transform(self.templatePrefix))
- if self.requiresClause:
- res.append(transform(self.requiresClause))
- res.append(' ')
res.append(transform(self.declaration))
if self.trailingRequiresClause:
res.append(' ')
@@ -4005,11 +4043,6 @@ class ASTDeclaration(ASTBase):
self.templatePrefix.describe_signature(signode, mode, env,
symbol=self.symbol,
lineSpec=options.get('tparam-line-spec'))
- if self.requiresClause:
- reqNode = addnodes.desc_signature_line()
- reqNode.sphinx_line_type = 'requiresClause'
- signode.append(reqNode)
- self.requiresClause.describe_signature(reqNode, 'markType', env, self.symbol)
signode += mainDeclNode
if self.visibility and self.visibility != "public":
mainDeclNode += addnodes.desc_sig_keyword(self.visibility, self.visibility)
@@ -4192,7 +4225,7 @@ class Symbol:
continue
# only add a declaration if we our self are from a declaration
if self.declaration:
- decl = ASTDeclaration('templateParam', None, None, None, None, tp, None)
+ decl = ASTDeclaration(objectType='templateParam', declaration=tp)
else:
decl = None
nne = ASTNestedNameElement(tp.get_identifier(), None)
@@ -4207,7 +4240,7 @@ class Symbol:
if nn is None:
continue
# (comparing to the template params: we have checked that we are a declaration)
- decl = ASTDeclaration('functionParam', None, None, None, None, fp, None)
+ decl = ASTDeclaration(objectType='functionParam', declaration=fp)
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, [], decl, self.docname, self.line)
@@ -6761,7 +6794,8 @@ class DefinitionParser(BaseParser):
err = eParam
self.skip_ws()
if self.skip_string('>'):
- return ASTTemplateParams(templateParams)
+ requiresClause = self._parse_requires_clause()
+ return ASTTemplateParams(templateParams, requiresClause)
elif self.skip_string(','):
continue
else:
@@ -6883,6 +6917,8 @@ class DefinitionParser(BaseParser):
return ASTTemplateDeclarationPrefix(None)
else:
raise e
+ if objectType == 'concept' and params.requiresClause is not None:
+ self.fail('requires-clause not allowed for concept')
else:
params = self._parse_template_introduction()
if not params:
@@ -6931,7 +6967,7 @@ class DefinitionParser(BaseParser):
newTemplates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] = []
for _i in range(numExtra):
- newTemplates.append(ASTTemplateParams([]))
+ newTemplates.append(ASTTemplateParams([], requiresClause=None))
if templatePrefix and not isMemberInstantiation:
newTemplates.extend(templatePrefix.templates)
templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
@@ -6947,7 +6983,6 @@ class DefinitionParser(BaseParser):
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
visibility = None
templatePrefix = None
- requiresClause = None
trailingRequiresClause = None
declaration: Any = None
@@ -6957,8 +6992,6 @@ class DefinitionParser(BaseParser):
if objectType in ('type', 'concept', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix(objectType)
- if objectType == 'function' and templatePrefix is not None:
- requiresClause = self._parse_requires_clause()
if objectType == 'type':
prevErrors = []
@@ -7003,7 +7036,7 @@ class DefinitionParser(BaseParser):
self.skip_ws()
semicolon = self.skip_string(';')
return ASTDeclaration(objectType, directiveType, visibility,
- templatePrefix, requiresClause, declaration,
+ templatePrefix, declaration,
trailingRequiresClause, semicolon)
def parse_namespace_object(self) -> ASTNamespace: