diff --git a/CHANGES b/CHANGES index 79512ffe4..48c4fae5b 100644 --- a/CHANGES +++ b/CHANGES @@ -202,6 +202,11 @@ Bugs fixed be searched. * #5889: LaTeX: user ``numfig_format`` is stripped of spaces and may cause build failure +* 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. +* #5928: KeyError: 'DOCUTILSCONFIG' when running build Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 6124620e3..27a917c83 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -365,8 +365,8 @@ _keywords = [ 'while', 'xor', 'xor_eq' ] -_max_id = 3 -_id_prefix = [None, '', '_CPPv2', '_CPPv3'] +_max_id = 4 +_id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4'] # ------------------------------------------------------------------------------ # Id v1 constants @@ -892,6 +892,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 @@ -918,9 +919,23 @@ class ASTFoldExpr(ASTBase): # type: (int) -> str assert version >= 3 if version == 3: - return str(self) - # TODO: find the right mangling scheme - assert False + return text_type(self) + # 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('(')) @@ -2635,11 +2650,12 @@ class ASTDeclSpecs(ASTBase): res.append('C') return ''.join(res) res = [] - if self.leftSpecs.volatile or self.rightSpecs.volatile: + if self.allSpecs.volatile: res.append('V') - if self.leftSpecs.const or self.rightSpecs.volatile: + if self.allSpecs.const: res.append('K') - res.append(self.trailingTypeSpec.get_id(version)) + if self.trailingTypeSpec is not None: + res.append(self.trailingTypeSpec.get_id(version)) return ''.join(res) def _stringify(self, transform): @@ -3286,6 +3302,14 @@ class ASTType(ASTBase): if objectType == 'function': # also modifiers modifiers = self.decl.get_modifiers_id(version) res.append(symbol.get_full_nested_name().get_id(version, modifiers)) + if version >= 4: + # with templates we need to mangle the return type in as well + templ = symbol.declaration.templatePrefix + if templ is not None: + typeId = self.decl.get_ptr_suffix_id(version) + returnTypeId = self.declSpecs.get_id(version) + res.append(typeId) + res.append(returnTypeId) res.append(self.decl.get_param_id(version)) elif objectType == 'type': # just the name res.append(symbol.get_full_nested_name().get_id(version)) @@ -4753,13 +4777,45 @@ class DefinitionParser: 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 @@ -5068,7 +5124,7 @@ class DefinitionParser: 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: @@ -6488,7 +6544,7 @@ class CPPObject(ObjectDescription): # Assume we are actually in the old symbol, # instead of the newly created duplicate. self.env.temp_data['cpp:last_symbol'] = e.symbol - self.warn("Duplicate declaration.") + self.warn("Duplicate declaration, %s" % sig) if ast.objectType == 'enumerator': self._add_enumerator_to_parent(ast) diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 2d915a4cb..e55cd016e 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -170,7 +170,7 @@ def using_user_docutils_conf(confdir): yield finally: if docutilsconfig is None: - os.environ.pop('DOCUTILSCONFIG') + os.environ.pop('DOCUTILSCONFIG', None) else: os.environ['DOCUTILSCONFIG'] = docutilsconfig diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index c45ed367d..3a506cd31 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -104,9 +104,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') @@ -153,7 +156,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 @@ -242,7 +247,7 @@ def test_expressions(): # a < expression that starts with something that could be a template exprCheck('A < 42', 'lt1AL42E') check('function', 'template<> void f(A &v)', - {2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE"}) + {2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE", 4: "IE1fvR1AI1BXL2EEE"}) exprCheck('A<1>::value', 'N1AIXL1EEE5valueE') check('class', "template A", {2: "I_iE1A"}) check('enumerator', 'A = std::numeric_limits::max()', {2: "1A"}) @@ -426,7 +431,7 @@ def test_function_definitions(): check('function', 'virtual void f()', {1: "f", 2: "1fv"}) # test for ::nestedName, from issue 1738 check("function", "result(int val, ::std::error_category const &cat)", - {1: "result__i.std::error_categoryCR", 2: "6resultiRNSt14error_categoryE"}) + {1: "result__i.std::error_categoryCR", 2: "6resultiRKNSt14error_categoryE"}) check("function", "int *f()", {1: "f", 2: "1fv"}) # tests derived from issue #1753 (skip to keep sanity) check("function", "f(int (&array)[10])", {2: "1fRA10_i", 3: "1fRAL10E_i"}) @@ -477,6 +482,10 @@ def test_function_definitions(): check('function', 'int C::* f(int, double)', {2: '1fid'}) check('function', 'void f(int C::* *)', {2: '1fPM1Ci'}) + # exceptions from return type mangling + check('function', 'template C()', {2: 'I0E1Cv'}) + check('function', 'template operator int()', {1: None, 2: 'I0Ecviv'}) + def test_operators(): check('function', 'void operator new [ ] ()', @@ -556,7 +565,7 @@ def test_templates(): check('class', "A", {2: "IE1AI1TE"}, output="template<> A") # first just check which objects support templating check('class', "template<> A", {2: "IE1A"}) - check('function', "template<> void A()", {2: "IE1Av"}) + check('function', "template<> void A()", {2: "IE1Av", 4: "IE1Avv"}) check('member', "template<> A a", {2: "IE1a"}) check('type', "template<> a = A", {2: "IE1a"}) with pytest.raises(DefinitionError): @@ -594,6 +603,10 @@ def test_templates(): "std::basic_ostream &os, " "const c_string_view_base &str)", {2: "I00ElsRNSt13basic_ostreamI4Char6TraitsEE" + "RK18c_string_view_baseIK4Char6TraitsE", + 4: "I00Els" + "RNSt13basic_ostreamI4Char6TraitsEE" + "RNSt13basic_ostreamI4Char6TraitsEE" "RK18c_string_view_baseIK4Char6TraitsE"}) # template introductions @@ -617,9 +630,11 @@ def test_templates(): check('type', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar = ghi::qux', {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()', - {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv'}) + {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv', + 4: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEvv'}) check('function', 'abc::ns::foo{id_0, id_1, ...id_2} void xyz::bar()', - {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barEv'}) + {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barEv', + 4: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barEvv'}) check('member', 'abc::ns::foo{id_0, id_1, id_2} ghi::qux xyz::bar', {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar', @@ -646,7 +661,8 @@ def test_template_args(): "template " "void allow(F *f, typename func::type tt)", {2: "I0E5allowP1FN4funcI1F1BXG != 1EE4typeE", - 3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE"}) + 3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE", + 4: "I0E5allowvP1FN4funcI1F1BXne1GL1EEE4typeE"}) # from #3542 check('type', "template " "enable_if_not_array_t = std::enable_if_t::value, int>",