Skip to content

Commit

Permalink
C++, parse (trailing) requires clauses
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobandersen committed Jun 2, 2020
1 parent 3419079 commit c78318b
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Features added
* #7734: napoleon: overescaped trailing underscore on attribute
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions
* #7295: C++, parse (trailing) requires clauses.

Bugs fixed
----------
Expand Down
132 changes: 120 additions & 12 deletions sphinx/domains/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3538,6 +3538,8 @@ def describe_signature_as_introducer(
signode += nodes.Text('}')


################################################################################

class ASTTemplateDeclarationPrefix(ASTBase):
def __init__(self,
templates: List[Union[ASTTemplateParams,
Expand Down Expand Up @@ -3566,32 +3568,50 @@ def describe_signature(self, signode: desc_signature, mode: str,
t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)


class ASTRequiresClause(ASTBase):
def __init__(self, expr: ASTExpression) -> None:
self.expr = expr

def _stringify(self, transform: StringifyTransform) -> str:
return 'requires ' + transform(self.expr)

def describe_signature(self, signode: addnodes.desc_signature_line, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += nodes.Text('requires ', 'requires ')
self.expr.describe_signature(signode, mode, env, symbol)


################################################################################
################################################################################

class ASTDeclaration(ASTBase):
def __init__(self, objectType: str, directiveType: str, visibility: str,
templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any,
templatePrefix: ASTTemplateDeclarationPrefix,
requiresClause: ASTRequiresClause, declaration: Any,
trailingRequiresClause: ASTRequiresClause,
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

self.symbol = None # type: Symbol
# set by CPPObject._add_enumerator_to_parent
self.enumeratorScopedSymbol = None # type: Symbol

def clone(self) -> "ASTDeclaration":
if self.templatePrefix:
templatePrefixClone = self.templatePrefix.clone()
else:
templatePrefixClone = None
return ASTDeclaration(self.objectType, self.directiveType,
self.visibility, templatePrefixClone,
self.declaration.clone(), self.semicolon)
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,
self.declaration.clone(), trailingRequiresClasueClone,
self.semicolon)

@property
def name(self) -> ASTNestedName:
Expand Down Expand Up @@ -3619,6 +3639,17 @@ def get_id(self, version: int, prefixed: bool = True) -> str:
res = []
if self.templatePrefix:
res.append(self.templatePrefix.get_id(version))
if self.requiresClause or self.trailingRequiresClause:
if version < 4:
raise NoOldIdError()
res.append('IQ')
if self.requiresClause and self.trailingRequiresClause:
res.append('aa')
if self.requiresClause:
res.append(self.requiresClause.expr.get_id(version))
if self.trailingRequiresClause:
res.append(self.trailingRequiresClause.expr.get_id(version))
res.append('E')
res.append(self.declaration.get_id(version, self.objectType, self.symbol))
return ''.join(res)

Expand All @@ -3632,7 +3663,13 @@ def _stringify(self, transform: StringifyTransform) -> str:
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(' ')
res.append(transform(self.trailingRequiresClause))
if self.semicolon:
res.append(';')
return ''.join(res)
Expand All @@ -3653,6 +3690,11 @@ def describe_signature(self, signode: desc_signature, mode: str,
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_annotation(self.visibility + " ",
Expand Down Expand Up @@ -3688,6 +3730,12 @@ def describe_signature(self, signode: desc_signature, mode: str,
else:
assert False
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
if self.trailingRequiresClause:
trailingReqNode = addnodes.desc_signature_line()
trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
signode.append(trailingReqNode)
self.trailingRequiresClause.describe_signature(
trailingReqNode, 'markType', env, self.symbol)
if self.semicolon:
mainDeclNode += nodes.Text(';')

Expand Down Expand Up @@ -3808,7 +3856,7 @@ def _add_template_and_function_params(self) -> None:
continue
# only add a declaration if we our self are from a declaration
if self.declaration:
decl = ASTDeclaration('templateParam', None, None, None, tp)
decl = ASTDeclaration('templateParam', None, None, None, None, tp, None)
else:
decl = None
nne = ASTNestedNameElement(tp.get_identifier(), None)
Expand All @@ -3823,7 +3871,7 @@ def _add_template_and_function_params(self) -> None:
if nn is None:
continue
# (comparing to the template params: we have checked that we are a declaration)
decl = ASTDeclaration('functionParam', None, None, None, fp)
decl = ASTDeclaration('functionParam', None, None, None, None, fp, None)
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, [], decl, self.docname)
Expand Down Expand Up @@ -6297,8 +6345,61 @@ def _parse_template_introduction(self) -> ASTTemplateIntroduction:
'Expected ",", or "}".')
return ASTTemplateIntroduction(concept, params)

def _parse_requires_clause(self) -> Optional[ASTRequiresClause]:
# requires-clause -> 'requires' constraint-logical-or-expression
# constraint-logical-or-expression
# -> constraint-logical-and-expression
# | constraint-logical-or-expression '||' constraint-logical-and-expression
# constraint-logical-and-expression
# -> primary-expression
# | constraint-logical-and-expression '&&' primary-expression
self.skip_ws()
if not self.skip_word('requires'):
return None

def parse_and_expr(self: DefinitionParser) -> ASTExpression:
andExprs = []
ops = []
andExprs.append(self._parse_primary_expression())
while True:
self.skip_ws()
oneMore = False
if self.skip_string('&&'):
oneMore = True
ops.append('&&')
elif self.skip_word('and'):
oneMore = True
ops.append('and')
if not oneMore:
break
andExprs.append(self._parse_primary_expression())
if len(andExprs) == 1:
return andExprs[0]
else:
return ASTBinOpExpr(andExprs, ops)

orExprs = []
ops = []
orExprs.append(parse_and_expr(self))
while True:
self.skip_ws()
oneMore = False
if self.skip_string('||'):
oneMore = True
ops.append('||')
elif self.skip_word('or'):
oneMore = True
ops.append('or')
if not oneMore:
break
orExprs.append(parse_and_expr(self))
if len(orExprs) == 1:
return ASTRequiresClause(orExprs[0])
else:
return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))

def _parse_template_declaration_prefix(self, objectType: str
) -> ASTTemplateDeclarationPrefix:
) -> Optional[ASTTemplateDeclarationPrefix]:
templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]]
while 1:
self.skip_ws()
Expand Down Expand Up @@ -6377,6 +6478,8 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
visibility = None
templatePrefix = None
requiresClause = None
trailingRequiresClause = None
declaration = None # type: Any

self.skip_ws()
Expand All @@ -6385,6 +6488,8 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati

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 = []
Expand All @@ -6410,6 +6515,8 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
declaration = self._parse_type(named=True, outer='function')
if templatePrefix is not None:
trailingRequiresClause = self._parse_requires_clause()
elif objectType == 'class':
declaration = self._parse_class()
elif objectType == 'union':
Expand All @@ -6427,7 +6534,8 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
self.skip_ws()
semicolon = self.skip_string(';')
return ASTDeclaration(objectType, directiveType, visibility,
templatePrefix, declaration, semicolon)
templatePrefix, requiresClause, declaration,
trailingRequiresClause, semicolon)

def parse_namespace_object(self) -> ASTNamespace:
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
Expand Down
9 changes: 9 additions & 0 deletions tests/test_domain_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,15 @@ def test_templates():
check('type', 'template<C T = int&> {key}A', {2: 'I_1CE1A'}, key='using')


def test_requires_clauses():
check('function', 'template<typename T> requires A auto f() -> void requires B',
{4: 'I0EIQaa1A1BE1fvv'})
check('function', 'template<typename T> requires A || B or C void f()',
{4: 'I0EIQoo1Aoo1B1CE1fvv'})
check('function', 'template<typename T> requires A && B || C and D void f()',
{4: 'I0EIQooaa1A1Baa1C1DE1fvv'})


def test_template_args():
# from breathe#218
check('function',
Expand Down

0 comments on commit c78318b

Please sign in to comment.