From c010b4758986ba3f137ad0453a09d6fe45a42c99 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 3 Jan 2015 16:59:08 +0100 Subject: [PATCH 1/6] C++, initial enum support (see #772) --- CHANGES | 4 +- doc/domains.rst | 33 +++++- sphinx/domains/cpp.py | 225 +++++++++++++++++++++++++++++++++++---- tests/test_domain_cpp.py | 15 +++ 4 files changed, 256 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 54f329d9d..5fcc0e9f0 100644 --- a/CHANGES +++ b/CHANGES @@ -187,7 +187,7 @@ Bugs fixed Thanks to Takeshi Komiya. * PR#265: Fix could not capture caption of graphviz node by xref. Thanks to Takeshi Komiya. -* PR#263, #1013, #1103: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen. +* PR#263, #1013, #1103, #772: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen. * Hyperlinks to all found nested names and template arguments (#1103). * Support for function types everywhere, e.g., in @@ -203,6 +203,8 @@ Bugs fixed * Index names may be different now. Elements are indexed by their fully qualified name. It should be rather easy to change this behaviour and potentially index by namespaces/classes as well. + * Support for scoped and unscoped enums. Enumerators in unscoped enums + are injected into the parent scope in addition to the enum scope. * PR#258, #939: Add dedent option for :rst:dir:`code-block` and :rst:dir:`literal-include`. Thanks to Zafar Siddiqui. diff --git a/doc/domains.rst b/doc/domains.rst index 4bfc91ec2..6c2038551 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -520,8 +520,8 @@ The C++ Domain The C++ domain (name **cpp**) supports documenting C++ projects. -The following directives are available. All declarations can start with a visibility statement -(``public``, ``private`` or ``protected``). +The following directives are available. All declarations except for enumerators +can start with a visibility statement (``public``, ``private`` or ``protected``). .. rst:directive:: .. cpp:class:: class speicifer @@ -578,6 +578,35 @@ The following directives are available. All declarations can start with a visibi Declaration of a type alias with unspecified type. +.. rst:directive:: .. cpp:enum:: enum declaration + + Describe a (scoped) enum, possibly with the underlying type specified. Note that for scoped + enums the ``struct``/``class`` keyword must come before the optional visibility specifier. + Any enumerators declared inside an unscoped enum will be declared both in the enum scope + and in the parent scope. + Examples: + + .. cpp:enum:: MyEnum + + An unscoped enum. + + .. cpp:enum:: MySpecificEnum : long + + An unscoped enum with specified underlying type. + + .. cpp:enum:: class MyScopedEnum + + A scoped enum. + + .. cpp:enum:: struct protected MyScopedVisibilityEnum : std::underlying_type::type + + A scoped enum with non-default visibility, and with a specified underlying type. + +.. rst:directive:: .. cpp:enumerator:: name + .. cpp:enumerator:: name = constant + + Describe an enumerator, optionally with its value defined. + .. rst:directive:: .. cpp:namespace:: namespace Select the current namespace for the following objects. Note that the namespace diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 23bd469fd..291196667 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -108,7 +108,15 @@ memberFunctionInit -> "=" "0" # (note: only "0" is allowed as the value, according to the standard, # right?) - + + enum-head -> + enum-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier enum-base[opt] + enum-key -> "enum" | "enum struct" | "enum class" + enum-base -> + ":" type + enumerator-definition -> + identifier + | identifier "=" constant-expression We additionally add the possibility for specifying the visibility as the first thing. @@ -127,7 +135,6 @@ grammar, typedef-like: no initilizer decl-specifier-seq declerator - member_object: goal: as a type_object which must have a declerator, and optionally with a initializer @@ -138,6 +145,26 @@ goal: a function declaration, TODO: what about templates? for now: skip grammar: no initializer decl-specifier-seq declerator + + class_object: + goal: a class declaration, but with specification of a base class, TODO: what about templates? for now: skip + grammar: + nested-name + | nested-name ":" 'comma-separated list of nested-name optionally with visibility' + + enum_object: + goal: an unscoped enum or a scoped enum, optionally with the underlying type specified + grammar: + ("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt] + enumerator_object: + goal: an element in a scoped or unscoped enum. The name should be injected according to the scopedness. + grammar: + nested-name ("=" constant-expression) + + namespace_object: + goal: a directive to put all following declarations in a specific scope + grammar: + nested-name """ import re @@ -1003,12 +1030,13 @@ class ASTBaseClass(ASTBase): signode += addnodes.desc_annotation( self.visibility, self.visibility) signode += nodes.Text(' ') - self.name.describe_signature(signode, mode, env) + self.name.describe_signature(signode, mode, env) class ASTClass(ASTBase): - def __init__(self, name, bases): + def __init__(self, name, visibility, bases): self.name = name + self.visibility = visibility self.bases = bases def get_id(self): @@ -1016,6 +1044,9 @@ class ASTClass(ASTBase): def __unicode__(self): res = [] + if self.visibility != 'public': + res.append(self.visibility) + res.append(' ') res.append(text_type(self.name)) if len(self.bases) > 0: res.append(' : ') @@ -1029,6 +1060,10 @@ class ASTClass(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mode(mode) + if self.visibility != 'public': + signode += addnodes.desc_annotation( + self.visibility, self.visibility) + signode += nodes.Text(' ') self.name.describe_signature(signode, mode, env) if len(self.bases) > 0: signode += nodes.Text(' : ') @@ -1037,6 +1072,63 @@ class ASTClass(ASTBase): signode += nodes.Text(', ') signode.pop() +class ASTEnum(ASTBase): + def __init__(self, name, visibility, scoped, underlyingType): + self.name = name + self.visibility = visibility + self.scoped = scoped + self.underlyingType = underlyingType + + def get_id(self): + return _id_prefix + self.prefixedName.get_id() + + def __unicode__(self): + res = [] + if self.scoped: + res.append(self.scoped) + res.append(' ') + if self.visibility != 'public': + res.append(self.visibility) + res.append(' ') + res.append(text_type(self.name)) + if self.underlyingType: + res.append(' : ') + res.append(text_type(self.underlyingType)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + # self.scoped has been done by the CPPEnumObject + if self.visibility != 'public': + signode += addnodes.desc_annotation( + self.visibility, self.visibility) + signode += nodes.Text(' ') + self.name.describe_signature(signode, mode, env) + if self.underlyingType: + signode += nodes.Text(' : ') + self.underlyingType.describe_signature(signode, 'noneIsName', env) + +class ASTEnumerator(ASTBase): + def __init__(self, name, init): + self.name = name + self.init = init + + def get_id(self): + return _id_prefix + self.prefixedName.get_id() + + def __unicode__(self): + res = [] + res.append(text_type(self.name)) + if self.init: + res.append(text_type(self.init)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + self.name.describe_signature(signode, mode, env) + if self.init: + self.init.describe_signature(signode, 'noneIsName') + class DefinitionParser(object): # those without signedness and size modifiers @@ -1583,6 +1675,9 @@ class DefinitionParser(object): return ASTTypeWithInit(type, init) def _parse_class(self): + classVisibility = 'public' + if self.match(_visibility_re): + classVisibility = self.matched_text name = self._parse_nested_name() bases = [] self.skip_ws() @@ -1599,8 +1694,35 @@ class DefinitionParser(object): continue else: break - return ASTClass(name, bases) - + return ASTClass(name, classVisibility, bases) + + def _parse_enum(self): + scoped = None + self.skip_ws() + if self.skip_word_and_ws('class'): + scoped = 'class' + elif self.skip_word_and_ws('struct'): + scoped = 'struct' + visibility = 'public' + if self.match(_visibility_re): + visibility = self.matched_text + self.skip_ws() + name = self._parse_nested_name() + self.skip_ws() + underlyingType = None + if self.skip_string(':'): + underlyingType = self._parse_type() + return ASTEnum(name, visibility, scoped, underlyingType) + + def _parse_enumerator(self): + name = self._parse_nested_name() + self.skip_ws() + init = None + if self.skip_string('='): + self.skip_ws() + init = ASTInitializer(self.read_rest()) + return ASTEnumerator(name, init) + def parse_type_object(self): res = self._parse_type(outer='type') res.objectType = 'type' @@ -1620,6 +1742,16 @@ class DefinitionParser(object): res = self._parse_class() res.objectType = 'class' return res + + def parse_enum_object(self): + res = self._parse_enum() + res.objectType = 'enum' + return res + + def parse_enumerator_object(self): + res = self._parse_enumerator() + res.objectType = 'enumerator' + return res def parse_namespace_object(self): res = self._parse_nested_name() @@ -1658,14 +1790,27 @@ class CPPObject(ObjectDescription): signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) if not name in objects: - objects.setdefault(name, - (self.env.docname, ast.objectType, theid)) + objects.setdefault(name, (self.env.docname, ast)) + if ast.objectType == 'enumerator': + # find the parent, if it exists && is an enum && it's unscoped, then add the name to the parent scope + assert len(ast.prefixedName.names) > 0 + parentPrefixedAstName = ASTNestedName(ast.prefixedName.names[:-1]) + parentPrefixedName = text_type(parentPrefixedAstName) + if parentPrefixedName in objects: + docname, parentAst = objects[parentPrefixedName] + if parentAst.objectType == 'enum' and not parentAst.scoped: + enumeratorName = ASTNestedName([ast.prefixedName.names[-1]]) + assert len(parentAst.prefixedName.names) > 0 + enumScope = ASTNestedName(parentAst.prefixedName.names[:-1]) + unscopedName = enumeratorName.prefix_nested_name(enumScope) + txtUnscopedName = text_type(unscopedName) + if not txtUnscopedName in objects: + objects.setdefault(txtUnscopedName, (self.env.docname, ast)) # add the uninstantiated template if it doesn't exist uninstantiated = ast.prefixedName.get_name_no_last_template() if uninstantiated != name and uninstantiated not in objects: signode['names'].append(uninstantiated) - objects.setdefault(uninstantiated, ( - self.env.docname, ast.objectType, theid)) + objects.setdefault(uninstantiated, (self.env.docname, ast)) self.env.temp_data['cpp:lastname'] = ast.prefixedName indextext = self.get_index_text(name) @@ -1757,6 +1902,45 @@ class CPPClassObject(CPPObject): def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('class ', 'class ') ast.describe_signature(signode, 'lastIsName', self.env) + + +class CPPEnumObject(CPPObject): + def get_index_text(self, name): + return _('%s (C++ enum)') % name + + def before_content(self): + lastname = self.env.temp_data['cpp:lastname'] + assert lastname + if 'cpp:parent' in self.env.temp_data: + self.env.temp_data['cpp:parent'].append(lastname) + else: + self.env.temp_data['cpp:parent'] = [lastname] + + def after_content(self): + self.env.temp_data['cpp:parent'].pop() + + def parse_definition(self, parser): + return parser.parse_enum_object() + + def describe_signature(self, signode, ast): + prefix = 'enum ' + if ast.scoped: + prefix += ast.scoped + prefix += ' ' + signode += addnodes.desc_annotation(prefix, prefix) + ast.describe_signature(signode, 'lastIsName', self.env) + + +class CPPEnumeratorObject(CPPObject): + def get_index_text(self, name): + return _('%s (C++ enumerator)') % name + + def parse_definition(self, parser): + return parser.parse_enumerator_object() + + def describe_signature(self, signode, ast): + signode += addnodes.desc_annotation('enumerator ', 'enumerator ') + ast.describe_signature(signode, 'lastIsName', self.env) class CPPNamespaceObject(Directive): @@ -1804,7 +1988,6 @@ class CPPXRefRole(XRefRole): title = title[dcolon + 2:] return title, target - class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' @@ -1813,7 +1996,9 @@ class CPPDomain(Domain): 'class': ObjType(l_('class'), 'class'), 'function': ObjType(l_('function'), 'func'), 'member': ObjType(l_('member'), 'member'), - 'type': ObjType(l_('type'), 'type') + 'type': ObjType(l_('type'), 'type'), + 'enum': ObjType(l_('enum'), 'enum'), + 'enumerator': ObjType(l_('enumerator'), 'enumerator') } directives = { @@ -1821,16 +2006,20 @@ class CPPDomain(Domain): 'function': CPPFunctionObject, 'member': CPPMemberObject, 'type': CPPTypeObject, + 'enum': CPPEnumObject, + 'enumerator': CPPEnumeratorObject, 'namespace': CPPNamespaceObject } roles = { 'class': CPPXRefRole(), 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), - 'type': CPPXRefRole() + 'type': CPPXRefRole(), + 'enum': CPPXRefRole(), + 'enumerator': CPPXRefRole() } initial_data = { - 'objects': {}, # prefixedName -> (docname, objectType, id) + 'objects': {}, # prefixedName -> (docname, ast) } def clear_doc(self, docname): @@ -1847,8 +2036,8 @@ class CPPDomain(Domain): name = nameAst.get_name_no_last_template() if name not in self.data['objects']: return None - docname, objectType, id = self.data['objects'][name] - return make_refnode(builder, fromdocname, docname, id, contnode, + docname, ast = self.data['objects'][name] + return make_refnode(builder, fromdocname, docname, ast.get_id(), contnode, name) parser = DefinitionParser(target) @@ -1874,5 +2063,5 @@ class CPPDomain(Domain): return None def get_objects(self): - for refname, (docname, type, theid) in iteritems(self.data['objects']): - yield (refname, refname, type, docname, refname, 1) \ No newline at end of file + for refname, (docname, ast) in iteritems(self.data['objects']): + yield (refname, refname, ast.objectType, docname, refname, 1) \ No newline at end of file diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 0b4219a52..1223a4256 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -108,6 +108,21 @@ def test_type_definitions(): raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') check('function', 'int foo(const A&... a)') check('function', 'virtual void f()') + + check('class', 'public A', 'A') + check('class', 'private A') + + check('enum', 'A') + check('enum', 'A : std::underlying_type::type') + check('enum', 'struct A') + check('enum', 'struct A : unsigned int') + check('enum', 'class A') + check('enum', 'class A : unsigned int') + check('enum', 'class public A', 'class A') + check('enum', 'class private A') + + check('enumerator', 'A') + check('enumerator', 'A = std::numeric_limits::max()') def test_bases(): check('class', 'A') From d09bcfa717d995a084d23381b1bf294c3ffdc9ca Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 7 Feb 2015 01:26:35 +0100 Subject: [PATCH 2/6] C++, minor code formatting fixes. --- sphinx/domains/cpp.py | 81 ++++++++++++++++++++-------------------- tests/test_domain_cpp.py | 7 ++-- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 3defefee1..3a864fb1e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -121,19 +121,16 @@ # (note: only "0" is allowed as the value, according to the standard, # right?) -<<<<<<< HEAD enum-head -> - enum-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier enum-base[opt] + enum-key attribute-specifier-seq[opt] nested-name-specifier[opt] + identifier enum-base[opt] enum-key -> "enum" | "enum struct" | "enum class" enum-base -> ":" type enumerator-definition -> identifier | identifier "=" constant-expression - -======= ->>>>>>> master We additionally add the possibility for specifying the visibility as the first thing. @@ -150,12 +147,7 @@ -> decl-specifier-seq abstract-declarator[opt] grammar, typedef-like: no initilizer decl-specifier-seq declerator -<<<<<<< HEAD - -======= - ->>>>>>> master member_object: goal: as a type_object which must have a declerator, and optionally with a initializer @@ -166,22 +158,26 @@ goal: a function declaration, TODO: what about templates? for now: skip grammar: no initializer decl-specifier-seq declerator - + class_object: - goal: a class declaration, but with specification of a base class, TODO: what about templates? for now: skip + goal: a class declaration, but with specification of a base class + TODO: what about templates? for now: skip grammar: nested-name - | nested-name ":" 'comma-separated list of nested-name optionally with visibility' - + | nested-name ":" + 'comma-separated list of nested-name optionally with visibility' + enum_object: - goal: an unscoped enum or a scoped enum, optionally with the underlying type specified + goal: an unscoped enum or a scoped enum, optionally with the underlying + type specified grammar: ("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt] enumerator_object: - goal: an element in a scoped or unscoped enum. The name should be injected according to the scopedness. + goal: an element in a scoped or unscoped enum. The name should be + injected according to the scopedness. grammar: nested-name ("=" constant-expression) - + namespace_object: goal: a directive to put all following declarations in a specific scope grammar: @@ -1397,7 +1393,7 @@ class ASTEnum(ASTBase): def get_id_v2(self): return _id_prefix_v2 + self.prefixedName.get_id_v2() - + def __unicode__(self): res = [] if self.scoped: @@ -1411,7 +1407,7 @@ class ASTEnum(ASTBase): res.append(' : ') res.append(text_type(self.underlyingType)) return u''.join(res) - + def describe_signature(self, signode, mode, env): _verify_description_mode(mode) # self.scoped has been done by the CPPEnumObject @@ -1423,31 +1419,31 @@ class ASTEnum(ASTBase): if self.underlyingType: signode += nodes.Text(' : ') self.underlyingType.describe_signature(signode, 'noneIsName', env) - + class ASTEnumerator(ASTBase): def __init__(self, name, init): self.name = name self.init = init - + def get_id_v1(self): return None # did not exist at that time - + def get_id_v2(self): return _id_prefix_v2 + self.prefixedName.get_id_v2() - + def __unicode__(self): res = [] res.append(text_type(self.name)) if self.init: res.append(text_type(self.init)) return u''.join(res) - + def describe_signature(self, signode, mode, env): _verify_description_mode(mode) self.name.describe_signature(signode, mode, env) if self.init: self.init.describe_signature(signode, 'noneIsName') - + class DefinitionParser(object): # those without signedness and size modifiers @@ -2018,7 +2014,7 @@ class DefinitionParser(object): else: break return ASTClass(name, classVisibility, bases) - + def _parse_enum(self): scoped = None self.skip_ws() @@ -2036,7 +2032,7 @@ class DefinitionParser(object): if self.skip_string(':'): underlyingType = self._parse_type() return ASTEnum(name, visibility, scoped, underlyingType) - + def _parse_enumerator(self): name = self._parse_nested_name() self.skip_ws() @@ -2045,7 +2041,7 @@ class DefinitionParser(object): self.skip_ws() init = ASTInitializer(self.read_rest()) return ASTEnumerator(name, init) - + def parse_type_object(self): res = self._parse_type(outer='type') res.objectType = 'type' @@ -2065,12 +2061,12 @@ class DefinitionParser(object): res = self._parse_class() res.objectType = 'class' return res - + def parse_enum_object(self): res = self._parse_enum() res.objectType = 'enum' return res - + def parse_enumerator_object(self): res = self._parse_enumerator() res.objectType = 'enumerator' @@ -2126,7 +2122,9 @@ class CPPObject(ObjectDescription): if not name in objects: objects.setdefault(name, (self.env.docname, ast)) if ast.objectType == 'enumerator': - # find the parent, if it exists && is an enum && it's unscoped, then add the name to the parent scope + # find the parent, if it exists && is an enum + # && it's unscoped, + # then add the name to the parent scope assert len(ast.prefixedName.names) > 0 parentPrefixedAstName = ASTNestedName(ast.prefixedName.names[:-1]) parentPrefixedName = text_type(parentPrefixedAstName) @@ -2139,7 +2137,8 @@ class CPPObject(ObjectDescription): unscopedName = enumeratorName.prefix_nested_name(enumScope) txtUnscopedName = text_type(unscopedName) if not txtUnscopedName in objects: - objects.setdefault(txtUnscopedName, (self.env.docname, ast)) + objects.setdefault(txtUnscopedName, + (self.env.docname, ast)) # add the uninstantiated template if it doesn't exist uninstantiated = ast.prefixedName.get_name_no_last_template() if uninstantiated != name and uninstantiated not in objects: @@ -2236,12 +2235,12 @@ class CPPClassObject(CPPObject): def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('class ', 'class ') ast.describe_signature(signode, 'lastIsName', self.env) - + class CPPEnumObject(CPPObject): def get_index_text(self, name): return _('%s (C++ enum)') % name - + def before_content(self): lastname = self.env.ref_context['cpp:lastname'] assert lastname @@ -2249,13 +2248,13 @@ class CPPEnumObject(CPPObject): self.env.ref_context['cpp:parent'].append(lastname) else: self.env.ref_context['cpp:parent'] = [lastname] - + def after_content(self): self.env.ref_context['cpp:parent'].pop() - + def parse_definition(self, parser): return parser.parse_enum_object() - + def describe_signature(self, signode, ast): prefix = 'enum ' if ast.scoped: @@ -2263,15 +2262,15 @@ class CPPEnumObject(CPPObject): prefix += ' ' signode += addnodes.desc_annotation(prefix, prefix) ast.describe_signature(signode, 'lastIsName', self.env) - + class CPPEnumeratorObject(CPPObject): def get_index_text(self, name): return _('%s (C++ enumerator)') % name - + def parse_definition(self, parser): return parser.parse_enumerator_object() - + def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('enumerator ', 'enumerator ') ast.describe_signature(signode, 'lastIsName', self.env) @@ -2379,7 +2378,7 @@ class CPPDomain(Domain): docname, ast = self.data['objects'][name] return make_refnode(builder, fromdocname, docname, ast.newestId, contnode, name), ast.objectType - + parser = DefinitionParser(target) try: nameAst = parser.parse_xref_object().name diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 4eb3b8742..7d1b30267 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -133,10 +133,10 @@ def test_type_definitions(): raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') check('function', 'int foo(const A&... a)') check('function', 'virtual void f()') - + check('class', 'public A', 'A') check('class', 'private A') - + check('enum', 'A') check('enum', 'A : std::underlying_type::type') check('enum', 'struct A') @@ -145,7 +145,7 @@ def test_type_definitions(): check('enum', 'class A : unsigned int') check('enum', 'class public A', 'class A') check('enum', 'class private A') - + check('enumerator', 'A') check('enumerator', 'A = std::numeric_limits::max()') @@ -158,7 +158,6 @@ def test_bases(): check('class', 'A : B, C') check('class', 'A : B, protected C, D') - def test_operators(): check('function', 'void operator new [ ] ()', 'void operator new[]()') check('function', 'void operator delete ()', 'void operator delete()') From 75c30ba2af5e3b12c3c11bb80b2c1304215936cc Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 7 Feb 2015 12:27:29 +0100 Subject: [PATCH 3/6] HTML search results for C++. Fixes sphinx-doc/sphinx#1591. The C++ domain did not return the actual id of elements, but the name. The fix is as Rapptz suggested. --- CHANGES | 1 + sphinx/domains/cpp.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index dffedcc23..533dcae8b 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Bugs fixed * #1687: linkcheck now treats 401 Unauthorized responses as "working". * #1690: toctrees with ``glob`` option now can also contain entries for single documents with explicit title. +* #1591: html search results for C++ elements now has correct interpage links. Release 1.3b2 (released Dec 5, 2014) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 3a864fb1e..01b1c8303 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -2417,4 +2417,4 @@ class CPPDomain(Domain): def get_objects(self): for refname, (docname, ast) in iteritems(self.data['objects']): - yield (refname, refname, ast.objectType, docname, refname, 1) + yield (refname, refname, ast.objectType, docname, ast.newestId, 1) From fd84a3d602848d890560fb7957bdad20c10fc303 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 8 Feb 2015 21:40:23 +0100 Subject: [PATCH 4/6] Update CHANGES --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 533dcae8b..4fa0735f0 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Features added * The ``language`` config value is now available in the HTML templates. * The ``env-updated`` event can now return a value, which is interpreted as an iterable of additional docnames that need to be rewritten. +* #772: Support for scoped and unscoped enums in C++. Enumerators in unscoped + enums are injected into the parent scope in addition to the enum scope. Bugs fixed ---------- @@ -196,7 +198,7 @@ Bugs fixed Thanks to Takeshi Komiya. * PR#265: Fix could not capture caption of graphviz node by xref. Thanks to Takeshi Komiya. -* PR#263, #1013, #1103, #772: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen. +* PR#263, #1013, #1103: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen. * Hyperlinks to all found nested names and template arguments (#1103). * Support for function types everywhere, e.g., in @@ -212,8 +214,6 @@ Bugs fixed * Index names may be different now. Elements are indexed by their fully qualified name. It should be rather easy to change this behaviour and potentially index by namespaces/classes as well. - * Support for scoped and unscoped enums. Enumerators in unscoped enums - are injected into the parent scope in addition to the enum scope. * PR#258, #939: Add dedent option for `code-block` and `literalinclude`. Thanks to Zafar Siddiqui. From 44ee9889813452cbdcc5f44078ea50706bf4dc25 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 8 Feb 2015 23:09:44 +0100 Subject: [PATCH 5/6] C++, update docs and enums. Fixes sphinx-doc/sphinx#772. --- doc/domains.rst | 35 +++++++++++++++++-------------- sphinx/domains/cpp.py | 48 +++++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/doc/domains.rst b/doc/domains.rst index b498de4f4..7b39a37e3 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -520,8 +520,8 @@ The C++ Domain The C++ domain (name **cpp**) supports documenting C++ projects. -The following directives are available. All declarations except for enumerators -can start with a visibility statement (``public``, ``private`` or ``protected``). +The following directives are available. All declarations can start with +a visibility statement (``public``, ``private`` or ``protected``). .. rst:directive:: .. cpp:class:: class speicifer @@ -578,10 +578,11 @@ can start with a visibility statement (``public``, ``private`` or ``protected``) Declaration of a type alias with unspecified type. -.. rst:directive:: .. cpp:enum:: enum declaration +.. rst:directive:: .. cpp:enum:: unscoped enum declaration + .. cpp:enum-struct:: scoped enum declaration + .. cpp:enum-class:: scoped enum declaration - Describe a (scoped) enum, possibly with the underlying type specified. Note that for scoped - enums the ``struct``/``class`` keyword must come before the optional visibility specifier. + Describe a (scoped) enum, possibly with the underlying type specified. Any enumerators declared inside an unscoped enum will be declared both in the enum scope and in the parent scope. Examples: @@ -594,11 +595,11 @@ can start with a visibility statement (``public``, ``private`` or ``protected``) An unscoped enum with specified underlying type. - .. cpp:enum:: class MyScopedEnum + .. cpp:enum-class:: MyScopedEnum A scoped enum. - .. cpp:enum:: struct protected MyScopedVisibilityEnum : std::underlying_type::type + .. cpp:enum-struct:: protected MyScopedVisibilityEnum : std::underlying_type::type A scoped enum with non-default visibility, and with a specified underlying type. @@ -609,27 +610,31 @@ can start with a visibility statement (``public``, ``private`` or ``protected``) .. rst:directive:: .. cpp:namespace:: namespace - Select the current namespace for the following objects. Note that the namespace + Select the current namespace for the subsequent objects. Note that the namespace does not need to correspond to C++ namespaces, but can end in names of classes, e.g.,:: .. cpp:namespace:: Namespace1::Namespace2::SomeClass::AnInnerClass - All following objects will be defined as if their name were declared with the namespace - prepended. The following cross-references will be search for by both their specified name + All subsequent objects will be defined as if their name were declared with the namespace + prepended. The subsequent cross-references will be searched for by both their specified name and with the namespace prepended. + Using ``NULL``, ``0``, or ``nullptr`` as the namespace will reset it to the global namespace. + .. _cpp-roles: These roles link to the given object types: .. rst:role:: cpp:class - cpp:func - cpp:member - cpp:type + cpp:func + cpp:member + cpp:type + cpp:enum + cpp:enumerator - Reference a C++ object. You can give the full specification (and need to, for - overloaded functions.) + Reference a C++ object by name. The name must be properly qualified relative to the + position of the link. .. note:: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 01b1c8303..26f707ff8 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -2016,12 +2016,8 @@ class DefinitionParser(object): return ASTClass(name, classVisibility, bases) def _parse_enum(self): - scoped = None + scoped = None # is set by CPPEnumObject self.skip_ws() - if self.skip_word_and_ws('class'): - scoped = 'class' - elif self.skip_word_and_ws('struct'): - scoped = 'struct' visibility = 'public' if self.match(_visibility_re): visibility = self.matched_text @@ -2219,12 +2215,14 @@ class CPPClassObject(CPPObject): return _('%s (C++ class)') % name def before_content(self): - lastname = self.env.ref_context['cpp:lastname'] - assert lastname - if 'cpp:parent' in self.env.ref_context: - self.env.ref_context['cpp:parent'].append(lastname) - else: - self.env.ref_context['cpp:parent'] = [lastname] + # lastname may not be set if there was an error + if 'cpp:lastname' in self.env.ref_context: + lastname = self.env.ref_context['cpp:lastname'] + assert lastname + if 'cpp:parent' in self.env.ref_context: + self.env.ref_context['cpp:parent'].append(lastname) + else: + self.env.ref_context['cpp:parent'] = [lastname] def after_content(self): self.env.ref_context['cpp:parent'].pop() @@ -2242,18 +2240,30 @@ class CPPEnumObject(CPPObject): return _('%s (C++ enum)') % name def before_content(self): - lastname = self.env.ref_context['cpp:lastname'] - assert lastname - if 'cpp:parent' in self.env.ref_context: - self.env.ref_context['cpp:parent'].append(lastname) - else: - self.env.ref_context['cpp:parent'] = [lastname] + # lastname may not be set if there was an error + if 'cpp:lastname' in self.env.ref_context: + lastname = self.env.ref_context['cpp:lastname'] + assert lastname + if 'cpp:parent' in self.env.ref_context: + self.env.ref_context['cpp:parent'].append(lastname) + else: + self.env.ref_context['cpp:parent'] = [lastname] def after_content(self): self.env.ref_context['cpp:parent'].pop() def parse_definition(self, parser): - return parser.parse_enum_object() + ast = parser.parse_enum_object() + # self.objtype is set by ObjectDescription in run() + if self.objtype == "enum": + ast.scoped = None + elif self.objtype == "enum-struct": + ast.scoped = "struct" + elif self.objtype == "enum-class": + ast.scoped = "class" + else: + assert False + return ast def describe_signature(self, signode, ast): prefix = 'enum ' @@ -2340,6 +2350,8 @@ class CPPDomain(Domain): 'member': CPPMemberObject, 'type': CPPTypeObject, 'enum': CPPEnumObject, + 'enum-struct': CPPEnumObject, + 'enum-class': CPPEnumObject, 'enumerator': CPPEnumeratorObject, 'namespace': CPPNamespaceObject } From 46cd97873c2e7ae8c9838a8ae4b66454013f97ad Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 8 Feb 2015 23:22:31 +0100 Subject: [PATCH 6/6] Fix C++ tests. --- tests/test_domain_cpp.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 7d1b30267..98cbd294f 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -139,12 +139,9 @@ def test_type_definitions(): check('enum', 'A') check('enum', 'A : std::underlying_type::type') - check('enum', 'struct A') - check('enum', 'struct A : unsigned int') - check('enum', 'class A') - check('enum', 'class A : unsigned int') - check('enum', 'class public A', 'class A') - check('enum', 'class private A') + check('enum', 'A : unsigned int') + check('enum', 'public A', 'A') + check('enum', 'private A') check('enumerator', 'A') check('enumerator', 'A = std::numeric_limits::max()')