diff --git a/CHANGES b/CHANGES
index be7f5d162..b34a19431 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,7 @@ Bugs fixed
* C++, fix hyperlinks for declarations involving east cv-qualifiers.
* #5755: C++, fix duplicate declaration error on function templates with constraints
in the return type.
+* C++, parse unary right fold expressions and binary fold expressions.
Testing
--------
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 4ba235067..519deccf7 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -872,6 +872,7 @@ class ASTParenExpr(ASTBase):
class ASTFoldExpr(ASTBase):
def __init__(self, leftExpr, op, rightExpr):
+ # type: (Any, str, Any) -> None
assert leftExpr is not None or rightExpr is not None
self.leftExpr = leftExpr
self.op = op
@@ -895,10 +896,24 @@ class ASTFoldExpr(ASTBase):
def get_id(self, version):
assert version >= 3
- if version == 3 or version == 4:
+ if version == 3:
return text_type(self)
- # TODO: find the right mangling scheme
- assert False
+ # https://github.com/itanium-cxx-abi/cxx-abi/pull/67
+ res = []
+ if self.leftExpr is None: # (... op expr)
+ res.append('fl')
+ elif self.rightExpr is None: # (expr op ...)
+ res.append('fr')
+ else: # (expr op ... op expr)
+ # we don't check where the parameter pack is,
+ # we just always call this a binary left fold
+ res.append('fL')
+ res.append(_id_operator_v2[self.op])
+ if self.leftExpr:
+ res.append(self.leftExpr.get_id(version))
+ if self.rightExpr:
+ res.append(self.rightExpr.get_id(version))
+ return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
signode.append(nodes.Text('('))
@@ -4632,13 +4647,45 @@ class DefinitionParser(object):
if not self.skip_string(')'):
self.fail("Expected ')' in end of fold expression.")
return ASTFoldExpr(None, op, rightExpr)
- # TODO: actually try to parse fold expression
- # fall back to a paren expression
- res = self._parse_expression(inTemplate=False)
+ # try first parsing a unary right fold, or a binary fold
+ pos = self.pos
+ try:
+ self.skip_ws()
+ leftExpr = self._parse_cast_expression()
+ self.skip_ws()
+ if not self.match(_fold_operator_re):
+ self.fail("Expected fold operator after left expression in fold expression.")
+ op = self.matched_text
+ self.skip_ws()
+ if not self.skip_string_and_ws('...'):
+ self.fail("Expected '...' after fold operator in fold expression.")
+ except DefinitionError as eFold:
+ self.pos = pos
+ # fall back to a paren expression
+ try:
+ res = self._parse_expression(inTemplate=False)
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in end of parenthesized expression.")
+ except DefinitionError as eExpr:
+ raise self._make_multi_error([
+ (eFold, "If fold expression"),
+ (eExpr, "If parenthesized expression")
+ ], "Error in fold expression or parenthesized expression.")
+ return ASTParenExpr(res)
+ # now it definitely is a fold expression
+ if self.skip_string(')'):
+ return ASTFoldExpr(leftExpr, op, None)
+ if not self.match(_fold_operator_re):
+ self.fail("Expected fold operator or ')' after '...' in fold expression.")
+ if op != self.matched_text:
+ self.fail("Operators are different in binary fold: '%s' and '%s'."
+ % (op, self.matched_text))
+ rightExpr = self._parse_cast_expression()
self.skip_ws()
if not self.skip_string(')'):
- self.fail("Expected ')' in end of fold expression or parenthesized expression.")
- return ASTParenExpr(res)
+ self.fail("Expected ')' to end binary fold expression.")
+ return ASTFoldExpr(leftExpr, op, rightExpr)
def _parse_primary_expression(self):
# literal
@@ -4947,7 +4994,7 @@ class DefinitionParser(object):
try:
typ = self._parse_type(False)
if not self.skip_string(')'):
- raise DefinitionError("Expected ')' in cast expression.")
+ self.fail("Expected ')' in cast expression.")
expr = self._parse_cast_expression()
return ASTCastExpr(typ, expr)
except DefinitionError as exCast:
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index f39d0f41e..0e35c0045 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -106,9 +106,12 @@ def test_fundamental_types():
def test_expressions():
- def exprCheck(expr, id):
+ def exprCheck(expr, id, id4=None):
ids = 'IE1CIA%s_1aE'
- check('class', 'template<> C' % expr, {2: ids % expr, 3: ids % id})
+ idDict = {2: ids % expr, 3: ids % id}
+ if id4 is not None:
+ idDict[4] = ids % id4
+ check('class', 'template<> C' % expr, idDict)
# primary
exprCheck('nullptr', 'LDnE')
exprCheck('true', 'L1E')
@@ -155,7 +158,9 @@ def test_expressions():
exprCheck(p + "'\\U0001F34C'", t + "127820")
# TODO: user-defined lit
- exprCheck('(... + Ns)', '(... + Ns)')
+ exprCheck('(... + Ns)', '(... + Ns)', id4='flpl2Ns')
+ exprCheck('(Ns + ...)', '(Ns + ...)', id4='frpl2Ns')
+ exprCheck('(Ns + ... + 0)', '(Ns + ... + 0)', id4='fLpl2NsL0E')
exprCheck('(5)', 'L5E')
exprCheck('C', '1C')
# postfix