diff --git a/CHANGES b/CHANGES index d3aa3e4a1..d7e7ccb16 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Features added * #2463, #2516: Add keywords of "meta" directive to search index * ``:maxdepth:`` option of toctree affects ``secnumdepth`` (ref: #2547) * #2575: Now ``sphinx.ext.graphviz`` allows ``:align:`` option +* C++, added concept directive. Thanks to mickk-on-cpp. Bugs fixed ---------- diff --git a/doc/domains.rst b/doc/domains.rst index 2aaf218c0..ce36af91e 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -685,6 +685,61 @@ a visibility statement (``public``, ``private`` or ``protected``). .. cpp::enumerator:: MyEnum::myOtherEnumerator = 42 +.. rst:directive:: .. cpp:concept:: template-parameter-list name + .. cpp:concept:: template-parameter-list name() + + .. warning:: The support for concepts is experimental. It is based on the + Concepts Technical Specification, and the features may change as the TS evolves. + + Describe a variable concept or a function concept. Both must have exactly 1 + template parameter list. The name may be a nested name. Examples:: + + .. cpp:concept:: template std::Iterator + + Proxy to an element of a notional sequence that can be compared, + indirected, or incremented. + + .. cpp:concept:: template std::Container() + + Holder of elements, to which it can provide access via + :cpp:concept:`Iterator` s. + + They will render as follows: + + .. cpp:concept:: template std::Iterator + + Proxy to an element of a notional sequence that can be compared, + indirected, or incremented. + + .. cpp:concept:: template std::Container() + + Holder of elements, to which it can provide access via + :cpp:concept:`Iterator` s. + +Constrained Templates +~~~~~~~~~~~~~~~~~~~~~ + +.. warning:: The support for constrained templates is experimental. It is based on the + Concepts Technical Specification, and the features may change as the TS evolves. + +.. note:: Sphinx does not currently support ``requires`` clauses. + +Placeholders +............ + +Declarations may use the name of a concept to introduce constrained template +parameters, or the keyword ``auto`` to introduce unconstrained template parameters:: + + .. cpp:function:: void f(auto &&arg) + + A function template with a single unconstrained template parameter. + + .. cpp:function:: void f(std::Iterator it) + + A function template with a single template parameter, constrained by the + Iterator concept. + + Namespacing ~~~~~~~~~~~~~~~~~ @@ -790,6 +845,7 @@ These roles link to the given declaration types: cpp:member cpp:var cpp:type + cpp:concept cpp:enum cpp:enumerator @@ -888,7 +944,6 @@ References to partial specialisations must always include the template parameter Currently the lookup only succeed if the template parameter identifiers are equal strings. - The Standard Domain ------------------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7d41d6570..aa7c61630 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -54,7 +54,8 @@ from sphinx.util.docfields import Field, GroupedField ---------------------------------------------------------------------------- See http://www.nongnu.org/hcb/ for the grammar, - or https://github.com/cplusplus/draft/blob/master/source/grammar.tex + and https://github.com/cplusplus/draft/blob/master/source/grammar.tex, + and https://github.com/cplusplus/concepts-ts for the newest grammar. common grammar things: @@ -201,6 +202,17 @@ from sphinx.util.docfields import Field, GroupedField We additionally add the possibility for specifying the visibility as the first thing. + concept_object: + goal: + just a declaration of the name (for now) + either a variable concept or function concept + + grammar: only a single template parameter list, and the nested name + may not have any template argument lists + + "template" "<" template-parameter-list ">" + nested-name-specifier "()"[opt] + type_object: goal: either a single type (e.g., "MyClass:Something_T" or a typedef-like @@ -2025,6 +2037,34 @@ class ASTTypeUsing(ASTBase): self.type.describe_signature(signode, 'markType', env, symbol=symbol) +class ASTConcept(ASTBase): + def __init__(self, nestedName, isFunction): + self.nestedName = nestedName + self.isFunction = isFunction # otherwise it's a variable concept + + @property + def name(self): + return self.nestedName + + def get_id_v1(self, objectType=None, symbol=None): + raise NoOldIdError() + + def get_id_v2(self, objectType, symbol): + return symbol.get_full_nested_name().get_id_v2() + + def __unicode__(self): + res = text_type(self.nestedName) + if self.isFunction: + res += "()" + return res + + def describe_signature(self, signode, mode, env, symbol): + signode += nodes.Text(text_type("bool ")) + self.nestedName.describe_signature(signode, mode, env, symbol) + if self.isFunction: + signode += nodes.Text("()") + + class ASTBaseClass(ASTBase): def __init__(self, name, visibility, virtual, pack): self.name = name @@ -2233,6 +2273,8 @@ class ASTDeclaration(ASTBase): prefix = self.declaration.get_type_declaration_prefix() prefix += ' ' mainDeclNode += addnodes.desc_annotation(prefix, prefix) + elif self.objectType == 'concept': + mainDeclNode += addnodes.desc_annotation('concept ', 'concept ') elif self.objectType == 'member': pass elif self.objectType == 'function': @@ -3427,6 +3469,18 @@ class DefinitionParser(object): type = self._parse_type(False, None) return ASTTypeUsing(name, type) + def _parse_concept(self): + nestedName = self._parse_nested_name() + isFunction = False + + self.skip_ws() + if self.skip_string('('): + isFunction = True + self.skip_ws() + if not self.skip_string(')'): + self.fail("Expected ')' in function concept declaration.") + return ASTConcept(nestedName, isFunction) + def _parse_class(self): name = self._parse_nested_name() self.skip_ws() @@ -3545,15 +3599,19 @@ class DefinitionParser(object): prevErrors.append((e, "")) raise self._make_multi_error(prevErrors, header) - def _parse_template_declaration_prefix(self): + def _parse_template_declaration_prefix(self, objectType): templates = [] while 1: self.skip_ws() if not self.skip_word("template"): break + if objectType == 'concept' and len(templates) > 0: + self.fail("More than 1 template parameter list for concept.") params = self._parse_template_parameter_list() templates.append(params) if len(templates) == 0: + if objectType == 'concept': + self.fail('Missing template parameter list for concept.') return None else: return ASTTemplateDeclarationPrefix(templates) @@ -3591,7 +3649,7 @@ class DefinitionParser(object): return templatePrefix def parse_declaration(self, objectType): - if objectType not in ('type', 'member', + if objectType not in ('type', 'concept', 'member', 'function', 'class', 'enum', 'enumerator'): raise Exception('Internal error, unknown objectType "%s".' % objectType) visibility = None @@ -3602,8 +3660,8 @@ class DefinitionParser(object): if self.match(_visibility_re): visibility = self.matched_text - if objectType in ('type', 'member', 'function', 'class'): - templatePrefix = self._parse_template_declaration_prefix() + if objectType in ('type', 'concept', 'member', 'function', 'class'): + templatePrefix = self._parse_template_declaration_prefix(objectType) if objectType == 'type': prevErrors = [] @@ -3623,6 +3681,8 @@ class DefinitionParser(object): prevErrors.append((e, "If type alias or template alias")) header = "Error in type declaration." raise self._make_multi_error(prevErrors, header) + elif objectType == 'concept': + declaration = self._parse_concept() elif objectType == 'member': declaration = self._parse_type_with_init(named=True, outer='member') elif objectType == 'function': @@ -3642,7 +3702,7 @@ class DefinitionParser(object): templatePrefix, declaration) def parse_namespace_object(self): - templatePrefix = self._parse_template_declaration_prefix() + templatePrefix = self._parse_template_declaration_prefix(objectType="namespace") name = self._parse_nested_name() templatePrefix = self._check_template_consistency(name, templatePrefix, fullSpecShorthand=False) @@ -3651,7 +3711,7 @@ class DefinitionParser(object): return res def parse_xref_object(self): - templatePrefix = self._parse_template_declaration_prefix() + templatePrefix = self._parse_template_declaration_prefix(objectType="xref") name = self._parse_nested_name() templatePrefix = self._check_template_consistency(name, templatePrefix, fullSpecShorthand=True) @@ -3812,6 +3872,26 @@ class CPPTypeObject(CPPObject): ast.describe_signature(signode, 'lastIsName', self.env) +class CPPConceptObject(CPPObject): + def get_index_text(self, name): + return _('%s (C++ concept)') % name + + def before_content(self): + lastSymbol = self.env.ref_context['cpp:last_symbol'] + assert lastSymbol + self.oldParentSymbol = self.env.ref_context['cpp:parent_symbol'] + self.env.ref_context['cpp:parent_symbol'] = lastSymbol + + def after_content(self): + self.env.ref_context['cpp:parent_symbol'] = self.oldParentSymbol + + def parse_definition(self, parser): + return parser.parse_declaration("concept") + + def describe_signature(self, signode, ast): + ast.describe_signature(signode, 'lastIsName', self.env) + + class CPPMemberObject(CPPObject): def get_index_text(self, name): return _('%s (C++ member)') % name @@ -4026,6 +4106,7 @@ class CPPDomain(Domain): 'function': ObjType(l_('function'), 'func'), 'member': ObjType(l_('member'), 'member'), 'type': ObjType(l_('type'), 'type'), + 'concept': ObjType(l_('concept'), 'concept'), 'enum': ObjType(l_('enum'), 'enum'), 'enumerator': ObjType(l_('enumerator'), 'enumerator') } @@ -4036,6 +4117,7 @@ class CPPDomain(Domain): 'member': CPPMemberObject, 'var': CPPMemberObject, 'type': CPPTypeObject, + 'concept': CPPConceptObject, 'enum': CPPEnumObject, 'enum-struct': CPPEnumObject, 'enum-class': CPPEnumObject, @@ -4051,6 +4133,7 @@ class CPPDomain(Domain): 'member': CPPXRefRole(), 'var': CPPXRefRole(), 'type': CPPXRefRole(), + 'concept': CPPXRefRole(), 'enum': CPPXRefRole(), 'enumerator': CPPXRefRole() } diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 129b942da..16db7cdb8 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -50,7 +50,7 @@ def check(name, input, idv1output=None, idv2output=None, output=None): print("Expected: ", output) raise DefinitionError("") rootSymbol = Symbol(None, None, None, None, None, None) - symbol = rootSymbol.add_declaration(ast, docname="Test") + symbol = rootSymbol.add_declaration(ast, docname="TestDoc") parentNode = addnodes.desc() signode = addnodes.desc_signature(input, '') parentNode += signode @@ -133,6 +133,20 @@ def test_type_definitions(): check('type', 'A = B', None, '1A') +def test_concept_definitions(): + check('concept', 'template A::B::Concept', + None, 'I0EN1A1B7ConceptE') + check('concept', 'template Foo', + None, 'I00DpE3Foo') + check('concept', 'template A::B::Concept()', + None, 'I0EN1A1B7ConceptE') + check('concept', 'template Foo()', + None, 'I00DpE3Foo') + raises(DefinitionError, parse, 'concept', 'Foo') + raises(DefinitionError, parse, 'concept', + 'template template Foo') + + def test_member_definitions(): check('member', ' const std::string & name = 42', "name__ssCR", "4name", output='const std::string &name = 42')