From 8a64cfdd91f261925556649c779bea3d2fcfa658 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 28 May 2016 22:00:13 +0900 Subject: [PATCH] C++, add support for template introductions. Thanks to mickk-on-cpp. --- CHANGES | 1 + doc/domains.rst | 28 +++++++ sphinx/domains/cpp.py | 165 +++++++++++++++++++++++++++++++-------- tests/test_domain_cpp.py | 30 +++++++ 4 files changed, 191 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index d7e7ccb16..87f983bf8 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Features added * ``: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. +* C++, added support for template introduction syntax. Thanks to mickk-on-cpp. Bugs fixed ---------- diff --git a/doc/domains.rst b/doc/domains.rst index ce36af91e..7128067d3 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -739,6 +739,34 @@ parameters, or the keyword ``auto`` to introduce unconstrained template paramete A function template with a single template parameter, constrained by the Iterator concept. +Template Introductions +...................... + +Simple constrained function or class templates can be declared with a +`template introduction` instead of a template parameter list:: + + .. cpp:function:: std::Iterator{It} void advance(It &it) + + A function template with a template parameter constrained to be an Iterator. + + .. cpp:class:: std::LessThanComparable{T} MySortedContainer + + A class template with a template parameter constrained to be LessThanComparable. + +They are rendered as follows. + +.. cpp:function:: std::Iterator{It} void advance(It &it) + + A function template with a template parameter constrained to be an Iterator. + +.. cpp:class:: std::LessThanComparable{T} MySortedContainer + + A class template with a template parameter constrained to be LessThanComparable. + +Note however that no checking is performed with respect to parameter +compatibility. E.g., ``Iterator{A, B, C}`` will be accepted as an introduction +even though it would not be valid C++. + Namespacing ~~~~~~~~~~~~~~~~~ diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index aa7c61630..eb3524375 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -46,6 +46,7 @@ from sphinx.util.docfields import Field, GroupedField Each desc_signature node will have the attribute 'sphinx_cpp_tagname' set to - 'templateParams', if the line is on the form 'template<...>', + - 'templateIntroduction, if the line is on the form 'conceptName{...}' - 'declarator', if the line contains the name of the declared object. No other desc_signature nodes should exist (so far). @@ -763,6 +764,7 @@ class ASTTemplateParams(ASTBase): return ''.join(res) def describe_signature(self, signode, mode, env, symbol): + signode.sphinx_cpp_tagname = 'templateParams' signode += nodes.Text("template<") first = True for param in self.params: @@ -773,6 +775,78 @@ class ASTTemplateParams(ASTBase): signode += nodes.Text(">") +class ASTTemplateIntroductionParameter(ASTBase): + def __init__(self, identifier, parameterPack): + self.identifier = identifier + self.parameterPack = parameterPack + + def get_identifier(self): + return self.identifier + + def get_id_v2(self, objectType=None, symbol=None): + # this is not part of the normal name mangling in C++ + if symbol: + # the anchor will be our parent + return symbol.parent.declaration.get_id_v2(prefixed=None) + else: + if self.parameterPack: + return 'Dp' + else: + return '0' # we need to put something + + def __unicode__(self): + res = [] + if self.parameterPack: + res.append('...') + res.append(text_type(self.identifier)) + return ''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + if self.parameterPack: + signode += nodes.Text('...') + self.identifier.describe_signature(signode, mode, env, '', symbol) + + +class ASTTemplateIntroduction(ASTBase): + def __init__(self, concept, params): + assert len(params) > 0 + self.concept = concept + self.params = params + + # id_v1 does not exist + + def get_id_v2(self): + # first do the same as a normal template parameter list + res = [] + res.append("I") + for param in self.params: + res.append(param.get_id_v2()) + res.append("E") + # TODO: add stuff for the implicit requires clause + res.append("MissingRequiresMangling") + return ''.join(res) + + def __unicode__(self): + res = [] + res.append(text_type(self.concept)) + res.append('{') + res.append(', '.join(text_type(param) for param in self.params)) + res.append('} ') + return ''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + signode.sphinx_cpp_tagname = 'templateIntroduction' + self.concept.describe_signature(signode, 'markType', env, symbol) + signode += nodes.Text('{') + first = True + for param in self.params: + if not first: + signode += nodes.Text(', ') + first = False + param.describe_signature(signode, mode, env, symbol) + signode += nodes.Text('}') + + class ASTTemplateDeclarationPrefix(ASTBase): def __init__(self, templates): assert templates is not None @@ -798,7 +872,6 @@ class ASTTemplateDeclarationPrefix(ASTBase): _verify_description_mode(mode) for t in self.templates: templateNode = addnodes.desc_signature() - templateNode.sphinx_cpp_tagname = 'templateParams' t.describe_signature(templateNode, 'lastIsName', env, symbol) signode += templateNode @@ -3599,19 +3672,63 @@ class DefinitionParser(object): prevErrors.append((e, "")) raise self._make_multi_error(prevErrors, header) + def _parse_template_introduction(self): + pos = self.pos + try: + concept = self._parse_nested_name() + except: + self.pos = pos + return None + self.skip_ws() + if not self.skip_string('{'): + self.pos = pos + return None + + # for sure it must be a template introduction now + params = [] + while 1: + self.skip_ws() + parameterPack = self.skip_string('...') + self.skip_ws() + if not self.match(_identifier_re): + self.fail("Expected identifier in template introduction list.") + identifier = self.matched_text + # make sure there isn't a keyword + if identifier in _keywords: + self.fail("Expected identifier in template introduction list, " + "got keyword: %s" % identifier) + identifier = ASTIdentifier(identifier) + params.append(ASTTemplateIntroductionParameter(identifier, parameterPack)) + + self.skip_ws() + if self.skip_string('}'): + break + elif self.skip_string(','): + continue + else: + self.fail("Error in template introduction list. " + 'Expected ",", or "}".') + return ASTTemplateIntroduction(concept, params) + def _parse_template_declaration_prefix(self, objectType): templates = [] while 1: self.skip_ws() - if not self.skip_word("template"): - break + # the saved position is only used to provide a better error message + pos = self.pos + if self.skip_word("template"): + params = self._parse_template_parameter_list() + else: + params = self._parse_template_introduction() + if not params: + break if objectType == 'concept' and len(templates) > 0: + self.pos = pos self.fail("More than 1 template parameter list for concept.") - params = self._parse_template_parameter_list() templates.append(params) + if len(templates) == 0 and objectType == 'concept': + self.fail('Missing template parameter list for concept.') if len(templates) == 0: - if objectType == 'concept': - self.fail('Missing template parameter list for concept.') return None else: return ASTTemplateDeclarationPrefix(templates) @@ -3821,6 +3938,15 @@ class CPPObject(ObjectDescription): signode['first'] = (not self.names) # hmm, what is this abound? self.state.document.note_explicit_target(signode) + 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): raise NotImplementedError() @@ -3876,15 +4002,6 @@ 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") @@ -3918,15 +4035,6 @@ class CPPClassObject(CPPObject): def get_index_text(self, name): return _('%s (C++ class)') % 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("class") @@ -3938,15 +4046,6 @@ class CPPEnumObject(CPPObject): def get_index_text(self, name): return _('%s (C++ enum)') % 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): ast = parser.parse_declaration("enum") # self.objtype is set by ObjectDescription in run() diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 16db7cdb8..721d63f9a 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -410,6 +410,36 @@ def test_templates(): None, "I00ElsRNSt13basic_ostreamI4Char6TraitsEE" "RK18c_string_view_baseIK4Char6TraitsE") + # template introductions + raises(DefinitionError, parse, 'enum', 'abc::ns::foo{id_0, id_1, id_2} A') + 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', + None, 'I000EMissingRequiresManglingN3xyz3barE') + check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', + None, 'I00DpEMissingRequiresManglingN3xyz3barE') + check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar', + None, 'I000EMissingRequiresManglingN3xyz3barI4id_04id_14id_2EE') + check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', + None, 'I00DpEMissingRequiresManglingN3xyz3barI4id_04id_1Dp4id_2EE') + + check('class', 'template<> Concept{U} A::B', + None, 'IEI0EMissingRequiresManglingN1AIiE1BE') + + check('type', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar = ghi::qux', + None, 'I000EMissingRequiresManglingN3xyz3barE') + check('type', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar = ghi::qux', + None, 'I00DpEMissingRequiresManglingN3xyz3barE') + check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()', + None, 'I000EMissingRequiresManglingN3xyz3barEv') + check('function', 'abc::ns::foo{id_0, id_1, ...id_2} void xyz::bar()', + None, 'I00DpEMissingRequiresManglingN3xyz3barEv') + check('member', 'abc::ns::foo{id_0, id_1, id_2} ghi::qux xyz::bar', + None, 'I000EMissingRequiresManglingN3xyz3barE') + check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar', + None, 'I00DpEMissingRequiresManglingN3xyz3barE') + check('concept', 'Iterator{T, U} Another', + None, 'I00EMissingRequiresMangling7Another') + #def test_print(): # # used for getting all the ids out for checking