diff --git a/CHANGES b/CHANGES index c1c3d5cb6..227d941ac 100644 --- a/CHANGES +++ b/CHANGES @@ -104,6 +104,8 @@ Bugs fixed supporting images * #7610: incorrectly renders consecutive backslashes for docutils-0.16 * #7646: handle errors on event handlers +* C++, fix rendering and xrefs in nested names explicitly starting + in global scope, e.g., ``::A::B``. Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 0755ce229..b801f4030 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -752,11 +752,17 @@ class ASTNestedName(ASTBase): names = self.names[:-1] if mode == 'lastIsName' else self.names # If lastIsName, then wrap all of the prefix in a desc_addname, # else append directly to signode. - # NOTE: Breathe relies on the prefix being in the desc_addname node, + # NOTE: Breathe previously relied on the prefix being in the desc_addname node, # so it can remove it in inner declarations. dest = signode if mode == 'lastIsName': dest = addnodes.desc_addname() + if self.rooted: + prefix += '::' + if mode == 'lastIsName' and len(names) == 0: + signode += nodes.Text('::') + else: + dest += nodes.Text('::') for i in range(len(names)): nne = names[i] template = self.templates[i] diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index bfb6934cf..38c89d282 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -33,15 +33,29 @@ def parse(name, string): return ast -def _check(name, input, idDict, output): +def _check(name, input, idDict, output, key, asTextOutput): + if key is None: + key = name + key += ' ' + if name in ('function', 'member'): + inputActual = input + outputAst = output + outputAsText = output + else: + inputActual = input.format(key='') + outputAst = output.format(key='') + outputAsText = output.format(key=key) + if asTextOutput is not None: + outputAsText = asTextOutput + # first a simple check of the AST - ast = parse(name, input) + ast = parse(name, inputActual) res = str(ast) - if res != output: + if res != outputAst: print("") print("Input: ", input) print("Result: ", res) - print("Expected: ", output) + print("Expected: ", outputAst) raise DefinitionError("") rootSymbol = Symbol(None, None, None, None, None, None) symbol = rootSymbol.add_declaration(ast, docname="TestDoc") @@ -49,6 +63,13 @@ def _check(name, input, idDict, output): signode = addnodes.desc_signature(input, '') parentNode += signode ast.describe_signature(signode, 'lastIsName', symbol, options={}) + resAsText = parentNode.astext() + if resAsText != outputAsText: + print("") + print("Input: ", input) + print("astext(): ", resAsText) + print("Expected: ", outputAsText) + raise DefinitionError("") idExpected = [None] for i in range(1, _max_id + 1): @@ -81,13 +102,14 @@ def _check(name, input, idDict, output): raise DefinitionError("") -def check(name, input, idDict, output=None): +def check(name, input, idDict, output=None, key=None, asTextOutput=None): if output is None: output = input # First, check without semicolon - _check(name, input, idDict, output) + _check(name, input, idDict, output, key, asTextOutput) # Second, check with semicolon - _check(name, input + ' ;', idDict, output + ';') + _check(name, input + ' ;', idDict, output + ';', key, + asTextOutput + ';' if asTextOutput is not None else None) def test_fundamental_types(): @@ -113,10 +135,11 @@ def test_fundamental_types(): def test_expressions(): def exprCheck(expr, id, id4=None): ids = 'IE1CIA%s_1aE' - idDict = {2: ids % expr, 3: ids % id} + # call .format() on the expr to unescape double curly braces + idDict = {2: ids % expr.format(), 3: ids % id} if id4 is not None: idDict[4] = ids % id4 - check('class', 'template<> C' % expr, idDict) + check('class', 'template<> {key}C' % expr, idDict) class Config: cpp_id_attributes = ["id_attr"] @@ -229,8 +252,8 @@ def test_expressions(): exprCheck('new int()', 'nw_ipiE') exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE') exprCheck('::new int', 'nw_iE') - exprCheck('new int{}', 'nw_iilE') - exprCheck('new int{5, 42}', 'nw_iilL5EL42EE') + exprCheck('new int{{}}', 'nw_iilE') + exprCheck('new int{{5, 42}}', 'nw_iilL5EL42EE') # delete-expression exprCheck('delete p', 'dl1p') exprCheck('delete [] p', 'da1p') @@ -291,7 +314,7 @@ def test_expressions(): exprCheck('a xor_eq 5', 'eO1aL5E') exprCheck('a |= 5', 'oR1aL5E') exprCheck('a or_eq 5', 'oR1aL5E') - exprCheck('a = {1, 2, 3}', 'aS1ailL1EL2EL3EE') + exprCheck('a = {{1, 2, 3}}', 'aS1ailL1EL2EL3EE') # comma operator exprCheck('a, 5', 'cm1aL5E') @@ -301,8 +324,8 @@ def test_expressions(): check('function', 'template<> void f(A &v)', {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"}) + check('class', "template {key}A", {2: "I_iE1A"}) + check('enumerator', '{key}A = std::numeric_limits::max()', {2: "1A"}) exprCheck('operator()()', 'clclE') exprCheck('operator()()', 'clclIiEE') @@ -312,58 +335,59 @@ def test_expressions(): def test_type_definitions(): - check("type", "public bool b", {1: "b", 2: "1b"}, "bool b") - check("type", "bool A::b", {1: "A::b", 2: "N1A1bE"}) - check("type", "bool *b", {1: "b", 2: "1b"}) - check("type", "bool *const b", {1: "b", 2: "1b"}) - check("type", "bool *volatile const b", {1: "b", 2: "1b"}) - check("type", "bool *volatile const b", {1: "b", 2: "1b"}) - check("type", "bool *volatile const *b", {1: "b", 2: "1b"}) - check("type", "bool &b", {1: "b", 2: "1b"}) - check("type", "bool b[]", {1: "b", 2: "1b"}) - check("type", "std::pair coord", {1: "coord", 2: "5coord"}) - check("type", "long long int foo", {1: "foo", 2: "3foo"}) - check("type", 'std::vector> module::blah', - {1: "module::blah", 2: "N6module4blahE"}) - check("type", "std::function F", {1: "F", 2: "1F"}) - check("type", "std::function F", {1: "F", 2: "1F"}) - check("type", "std::function F", {1: "F", 2: "1F"}) - check("type", "std::function F", {1: "F", 2: "1F"}) - check("type", "MyContainer::const_iterator", + check("type", "public bool b", {1: "b", 2: "1b"}, "{key}bool b", key='typedef') + check("type", "{key}bool A::b", {1: "A::b", 2: "N1A1bE"}, key='typedef') + check("type", "{key}bool *b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool *const b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool *volatile const *b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool &b", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}bool b[]", {1: "b", 2: "1b"}, key='typedef') + check("type", "{key}std::pair coord", {1: "coord", 2: "5coord"}, key='typedef') + check("type", "{key}long long int foo", {1: "foo", 2: "3foo"}, key='typedef') + check("type", '{key}std::vector> module::blah', + {1: "module::blah", 2: "N6module4blahE"}, key='typedef') + check("type", "{key}std::function F", {1: "F", 2: "1F"}, key='typedef') + check("type", "{key}std::function F", {1: "F", 2: "1F"}, key='typedef') + check("type", "{key}std::function F", {1: "F", 2: "1F"}, key='typedef') + check("type", "{key}std::function F", {1: "F", 2: "1F"}, key='typedef') + check("type", "{key}MyContainer::const_iterator", {1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"}) check("type", "public MyContainer::const_iterator", {1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"}, - output="MyContainer::const_iterator") + output="{key}MyContainer::const_iterator") # test decl specs on right - check("type", "bool const b", {1: "b", 2: "1b"}) + check("type", "{key}bool const b", {1: "b", 2: "1b"}, key='typedef') # test name in global scope - check("type", "bool ::B::b", {1: "B::b", 2: "N1B1bE"}) + check("type", "{key}bool ::B::b", {1: "B::b", 2: "N1B1bE"}, key='typedef') - check('type', 'A = B', {2: '1A'}) - check('type', 'A = decltype(b)', {2: '1A'}) + check('type', '{key}A = B', {2: '1A'}, key='using') + check('type', '{key}A = decltype(b)', {2: '1A'}, key='using') # from breathe#267 (named function parameters for function pointers - check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)', - {1: 'gpio_callback_t', 2: '15gpio_callback_t'}) - check('type', 'void (*f)(std::function g)', {1: 'f', 2: '1f'}) + check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)', + {1: 'gpio_callback_t', 2: '15gpio_callback_t'}, key='typedef') + check('type', '{key}void (*f)(std::function g)', {1: 'f', 2: '1f'}, + key='typedef') - check('type', 'T = A::template B::template C', {2: '1T'}) + check('type', '{key}T = A::template B::template C', {2: '1T'}, key='using') - check('type', 'T = Q', {2: '1T'}) - check('type', 'T = Q>', {2: '1T'}) - check('type', 'T = Q', {2: '1T'}) + check('type', '{key}T = Q', {2: '1T'}, key='using') + check('type', '{key}T = Q>', {2: '1T'}, key='using') + check('type', '{key}T = Q', {2: '1T'}, key='using') def test_concept_definitions(): - check('concept', 'template A::B::Concept', + check('concept', 'template {key}A::B::Concept', {2: 'I0EN1A1B7ConceptE'}) - check('concept', 'template Foo', + check('concept', 'template {key}Foo', {2: 'I00DpE3Foo'}) with pytest.raises(DefinitionError): - parse('concept', 'Foo') + parse('concept', '{key}Foo') with pytest.raises(DefinitionError): - parse('concept', 'template template Foo') + parse('concept', 'template template {key}Foo') def test_member_definitions(): @@ -639,95 +663,102 @@ def test_operators(): check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"}) +class test_nested_name(): + check('class', '{key}::A', {1: "A", 2: "1A"}) + check('class', '{key}::A::B', {1: "A::B", 2: "N1A1BE"}) + check('function', 'void f(::A a)', {1: "f__A", 2: "1f1A"}) + check('function', 'void f(::A::B a)', {1: "f__A::B", 2: "1fN1A1BE"}) + + def test_class_definitions(): - check('class', 'public A', {1: "A", 2: "1A"}, output='A') - check('class', 'private A', {1: "A", 2: "1A"}) - check('class', 'A final', {1: 'A', 2: '1A'}) + check('class', 'public A', {1: "A", 2: "1A"}, output='{key}A') + check('class', 'private {key}A', {1: "A", 2: "1A"}) + check('class', '{key}A final', {1: 'A', 2: '1A'}) # test bases - check('class', 'A', {1: "A", 2: "1A"}) - check('class', 'A::B::C', {1: "A::B::C", 2: "N1A1B1CE"}) - check('class', 'A : B', {1: "A", 2: "1A"}) - check('class', 'A : private B', {1: "A", 2: "1A"}) - check('class', 'A : public B', {1: "A", 2: "1A"}) - check('class', 'A : B, C', {1: "A", 2: "1A"}) - check('class', 'A : B, protected C, D', {1: "A", 2: "1A"}) - check('class', 'A : virtual private B', {1: 'A', 2: '1A'}, output='A : private virtual B') - check('class', 'A : private virtual B', {1: 'A', 2: '1A'}) - check('class', 'A : B, virtual C', {1: 'A', 2: '1A'}) - check('class', 'A : public virtual B', {1: 'A', 2: '1A'}) - check('class', 'A : B, C...', {1: 'A', 2: '1A'}) - check('class', 'A : B..., C', {1: 'A', 2: '1A'}) + check('class', '{key}A', {1: "A", 2: "1A"}) + check('class', '{key}A::B::C', {1: "A::B::C", 2: "N1A1B1CE"}) + check('class', '{key}A : B', {1: "A", 2: "1A"}) + check('class', '{key}A : private B', {1: "A", 2: "1A"}) + check('class', '{key}A : public B', {1: "A", 2: "1A"}) + check('class', '{key}A : B, C', {1: "A", 2: "1A"}) + check('class', '{key}A : B, protected C, D', {1: "A", 2: "1A"}) + check('class', 'A : virtual private B', {1: 'A', 2: '1A'}, output='{key}A : private virtual B') + check('class', '{key}A : private virtual B', {1: 'A', 2: '1A'}) + check('class', '{key}A : B, virtual C', {1: 'A', 2: '1A'}) + check('class', '{key}A : public virtual B', {1: 'A', 2: '1A'}) + check('class', '{key}A : B, C...', {1: 'A', 2: '1A'}) + check('class', '{key}A : B..., C', {1: 'A', 2: '1A'}) # from #4094 - check('class', 'template> has_var', {2: 'I00E7has_var'}) - check('class', 'template has_var>', + check('class', 'template> {key}has_var', {2: 'I00E7has_var'}) + check('class', 'template {key}has_var>', {2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'}) - check('class', 'template T', + check('class', 'template {key}T', {2: 'IDpE1TIJPFi2TsEEE'}) - check('class', 'template T<(Is)...>', + check('class', 'template {key}T<(Is)...>', {2: 'I_DpiE1TIJX(Is)EEE', 3: 'I_DpiE1TIJX2IsEEE'}) def test_union_definitions(): - check('union', 'A', {2: "1A"}) + check('union', '{key}A', {2: "1A"}) def test_enum_definitions(): - check('enum', 'A', {2: "1A"}) - check('enum', 'A : std::underlying_type::type', {2: "1A"}) - check('enum', 'A : unsigned int', {2: "1A"}) - check('enum', 'public A', {2: "1A"}, output='A') - check('enum', 'private A', {2: "1A"}) + check('enum', '{key}A', {2: "1A"}) + check('enum', '{key}A : std::underlying_type::type', {2: "1A"}) + check('enum', '{key}A : unsigned int', {2: "1A"}) + check('enum', 'public A', {2: "1A"}, output='{key}A') + check('enum', 'private {key}A', {2: "1A"}) - check('enumerator', 'A', {2: "1A"}) - check('enumerator', 'A = std::numeric_limits::max()', {2: "1A"}) + check('enumerator', '{key}A', {2: "1A"}) + check('enumerator', '{key}A = std::numeric_limits::max()', {2: "1A"}) def test_anon_definitions(): - check('class', '@a', {3: "Ut1_a"}) - check('union', '@a', {3: "Ut1_a"}) - check('enum', '@a', {3: "Ut1_a"}) - check('class', '@1', {3: "Ut1_1"}) - check('class', '@a::A', {3: "NUt1_a1AE"}) + check('class', '@a', {3: "Ut1_a"}, asTextOutput='class [anonymous]') + check('union', '@a', {3: "Ut1_a"}, asTextOutput='union [anonymous]') + check('enum', '@a', {3: "Ut1_a"}, asTextOutput='enum [anonymous]') + check('class', '@1', {3: "Ut1_1"}, asTextOutput='class [anonymous]') + check('class', '@a::A', {3: "NUt1_a1AE"}, asTextOutput='class [anonymous]::A') def test_templates(): - check('class', "A", {2: "IE1AI1TE"}, output="template<> A") + check('class', "A", {2: "IE1AI1TE"}, output="template<> {key}A") # first just check which objects support templating - check('class', "template<> A", {2: "IE1A"}) + check('class', "template<> {key}A", {2: "IE1A"}) check('function', "template<> void A()", {2: "IE1Av", 4: "IE1Avv"}) check('member', "template<> A a", {2: "IE1a"}) - check('type', "template<> a = A", {2: "IE1a"}) + check('type', "template<> {key}a = A", {2: "IE1a"}, key='using') with pytest.raises(DefinitionError): parse('enum', "template<> A") with pytest.raises(DefinitionError): parse('enumerator', "template<> A") # then all the real tests - check('class', "template A", {2: "I00E1A"}) - check('type', "template<> a", {2: "IE1a"}) + check('class', "template {key}A", {2: "I00E1A"}) + check('type', "template<> {key}a", {2: "IE1a"}, key='using') - check('class', "template A", {2: "I0E1A"}) - check('class', "template A", {2: "I0E1A"}) - check('class', "template A", {2: "IDpE1A"}) - check('class', "template A", {2: "IDpE1A"}) - check('class', "template A", {2: "I0E1A"}) - check('class', "template A", {2: "I0E1A"}) + check('class', "template {key}A", {2: "I0E1A"}) + check('class', "template {key}A", {2: "I0E1A"}) + check('class', "template {key}A", {2: "IDpE1A"}) + check('class', "template {key}A", {2: "IDpE1A"}) + check('class', "template {key}A", {2: "I0E1A"}) + check('class', "template {key}A", {2: "I0E1A"}) - check('class', "template typename T> A", {2: "II0E0E1A"}) - check('class', "template typename> A", {2: "II0E0E1A"}) - check('class', "template typename ...T> A", {2: "II0EDpE1A"}) - check('class', "template typename...> A", {2: "II0EDpE1A"}) + check('class', "template typename T> {key}A", {2: "II0E0E1A"}) + check('class', "template typename> {key}A", {2: "II0E0E1A"}) + check('class', "template typename ...T> {key}A", {2: "II0EDpE1A"}) + check('class', "template typename...> {key}A", {2: "II0EDpE1A"}) - check('class', "template A", {2: "I_iE1A"}) - check('class', "template A", {2: "I_iE1A"}) - check('class', "template A", {2: "I_DpiE1A"}) - check('class', "template A", {2: "I_iE1A"}) - check('class', "template A", {2: "I_iE1A"}) + check('class', "template {key}A", {2: "I_iE1A"}) + check('class', "template {key}A", {2: "I_iE1A"}) + check('class', "template {key}A", {2: "I_DpiE1A"}) + check('class', "template {key}A", {2: "I_iE1A"}) + check('class', "template {key}A", {2: "I_iE1A"}) - check('class', "template<> A>", {2: "IE1AIN2NS1BIEEE"}) + check('class', "template<> {key}A>", {2: "IE1AIN2NS1BIEEE"}) # from #2058 check('function', @@ -747,21 +778,21 @@ def test_templates(): parse('enum', 'abc::ns::foo{id_0, id_1, id_2} A') with pytest.raises(DefinitionError): parse('enumerator', 'abc::ns::foo{id_0, id_1, id_2} A') - check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar', + check('class', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar', {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) - check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', + check('class', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar', {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) - check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar', + check('class', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar', {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barI4id_04id_14id_2EE'}) - check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', + check('class', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar', {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barI4id_04id_1Dp4id_2EE'}) - check('class', 'template<> Concept{U} A::B', {2: 'IEI0EX7ConceptI1UEEN1AIiE1BE'}) + check('class', 'template<> Concept{{U}} {key}A::B', {2: 'IEI0EX7ConceptI1UEEN1AIiE1BE'}) - check('type', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar = ghi::qux', - {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) - check('type', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar = ghi::qux', - {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) + check('type', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar = ghi::qux', + {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}, key='using') + check('type', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar = ghi::qux', + {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}, key='using') check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()', {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv', 4: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEvv'}) @@ -772,8 +803,8 @@ def test_templates(): {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar', {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) - check('concept', 'Iterator{T, U} Another', {2: 'I00EX8IteratorI1T1UEE7Another'}) - check('concept', 'template Numerics = (... && Numeric)', + check('concept', 'Iterator{{T, U}} {key}Another', {2: 'I00EX8IteratorI1T1UEE7Another'}) + check('concept', 'template {key}Numerics = (... && Numeric)', {2: 'IDpE8Numerics'}) # explicit specializations of members @@ -785,7 +816,7 @@ def test_templates(): output='template<> template<> int A::B::b') # same as above # defaulted constrained type parameters - check('type', 'template A', {2: 'I_1CE1A'}) + check('type', 'template {key}A', {2: 'I_1CE1A'}, key='using') def test_template_args(): @@ -797,9 +828,10 @@ def test_template_args(): 3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE", 4: "I0E5allowvP1FN4funcI1F1BXne1GL1EEE4typeE"}) # from #3542 - check('type', "template " + check('type', "template {key}" "enable_if_not_array_t = std::enable_if_t::value, int>", - {2: "I0E21enable_if_not_array_t"}) + {2: "I0E21enable_if_not_array_t"}, + key='using') def test_initializers():