C++, add support for concept declarations.

Concept placeholders are automatically linked.
Thanks to mickk-on-cpp.
This commit is contained in:
Jakob Lykke Andersen 2016-05-28 15:31:03 +09:00
parent 1825559f74
commit 74191207db
4 changed files with 162 additions and 9 deletions

View File

@ -17,6 +17,7 @@ Features added
* #2463, #2516: Add keywords of "meta" directive to search index * #2463, #2516: Add keywords of "meta" directive to search index
* ``:maxdepth:`` option of toctree affects ``secnumdepth`` (ref: #2547) * ``:maxdepth:`` option of toctree affects ``secnumdepth`` (ref: #2547)
* #2575: Now ``sphinx.ext.graphviz`` allows ``:align:`` option * #2575: Now ``sphinx.ext.graphviz`` allows ``:align:`` option
* C++, added concept directive. Thanks to mickk-on-cpp.
Bugs fixed Bugs fixed
---------- ----------

View File

@ -685,6 +685,61 @@ a visibility statement (``public``, ``private`` or ``protected``).
.. cpp::enumerator:: MyEnum::myOtherEnumerator = 42 .. 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<typename It> std::Iterator
Proxy to an element of a notional sequence that can be compared,
indirected, or incremented.
.. cpp:concept:: template<typename Cont> std::Container()
Holder of elements, to which it can provide access via
:cpp:concept:`Iterator` s.
They will render as follows:
.. cpp:concept:: template<typename It> std::Iterator
Proxy to an element of a notional sequence that can be compared,
indirected, or incremented.
.. cpp:concept:: template<typename Cont> 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 Namespacing
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -790,6 +845,7 @@ These roles link to the given declaration types:
cpp:member cpp:member
cpp:var cpp:var
cpp:type cpp:type
cpp:concept
cpp:enum cpp:enum
cpp:enumerator 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. Currently the lookup only succeed if the template parameter identifiers are equal strings.
The Standard Domain The Standard Domain
------------------- -------------------

View File

@ -54,7 +54,8 @@ from sphinx.util.docfields import Field, GroupedField
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
See http://www.nongnu.org/hcb/ for the grammar, 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. for the newest grammar.
common grammar things: 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 We additionally add the possibility for specifying the visibility as the
first thing. 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: type_object:
goal: goal:
either a single type (e.g., "MyClass:Something_T" or a typedef-like 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) 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): class ASTBaseClass(ASTBase):
def __init__(self, name, visibility, virtual, pack): def __init__(self, name, visibility, virtual, pack):
self.name = name self.name = name
@ -2233,6 +2273,8 @@ class ASTDeclaration(ASTBase):
prefix = self.declaration.get_type_declaration_prefix() prefix = self.declaration.get_type_declaration_prefix()
prefix += ' ' prefix += ' '
mainDeclNode += addnodes.desc_annotation(prefix, prefix) mainDeclNode += addnodes.desc_annotation(prefix, prefix)
elif self.objectType == 'concept':
mainDeclNode += addnodes.desc_annotation('concept ', 'concept ')
elif self.objectType == 'member': elif self.objectType == 'member':
pass pass
elif self.objectType == 'function': elif self.objectType == 'function':
@ -3427,6 +3469,18 @@ class DefinitionParser(object):
type = self._parse_type(False, None) type = self._parse_type(False, None)
return ASTTypeUsing(name, type) 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): def _parse_class(self):
name = self._parse_nested_name() name = self._parse_nested_name()
self.skip_ws() self.skip_ws()
@ -3545,15 +3599,19 @@ class DefinitionParser(object):
prevErrors.append((e, "")) prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header) raise self._make_multi_error(prevErrors, header)
def _parse_template_declaration_prefix(self): def _parse_template_declaration_prefix(self, objectType):
templates = [] templates = []
while 1: while 1:
self.skip_ws() self.skip_ws()
if not self.skip_word("template"): if not self.skip_word("template"):
break break
if objectType == 'concept' and len(templates) > 0:
self.fail("More than 1 template parameter list for concept.")
params = self._parse_template_parameter_list() params = self._parse_template_parameter_list()
templates.append(params) templates.append(params)
if len(templates) == 0: if len(templates) == 0:
if objectType == 'concept':
self.fail('Missing template parameter list for concept.')
return None return None
else: else:
return ASTTemplateDeclarationPrefix(templates) return ASTTemplateDeclarationPrefix(templates)
@ -3591,7 +3649,7 @@ class DefinitionParser(object):
return templatePrefix return templatePrefix
def parse_declaration(self, objectType): def parse_declaration(self, objectType):
if objectType not in ('type', 'member', if objectType not in ('type', 'concept', 'member',
'function', 'class', 'enum', 'enumerator'): 'function', 'class', 'enum', 'enumerator'):
raise Exception('Internal error, unknown objectType "%s".' % objectType) raise Exception('Internal error, unknown objectType "%s".' % objectType)
visibility = None visibility = None
@ -3602,8 +3660,8 @@ class DefinitionParser(object):
if self.match(_visibility_re): if self.match(_visibility_re):
visibility = self.matched_text visibility = self.matched_text
if objectType in ('type', 'member', 'function', 'class'): if objectType in ('type', 'concept', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix() templatePrefix = self._parse_template_declaration_prefix(objectType)
if objectType == 'type': if objectType == 'type':
prevErrors = [] prevErrors = []
@ -3623,6 +3681,8 @@ class DefinitionParser(object):
prevErrors.append((e, "If type alias or template alias")) prevErrors.append((e, "If type alias or template alias"))
header = "Error in type declaration." header = "Error in type declaration."
raise self._make_multi_error(prevErrors, header) raise self._make_multi_error(prevErrors, header)
elif objectType == 'concept':
declaration = self._parse_concept()
elif objectType == 'member': elif objectType == 'member':
declaration = self._parse_type_with_init(named=True, outer='member') declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function': elif objectType == 'function':
@ -3642,7 +3702,7 @@ class DefinitionParser(object):
templatePrefix, declaration) templatePrefix, declaration)
def parse_namespace_object(self): def parse_namespace_object(self):
templatePrefix = self._parse_template_declaration_prefix() templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
name = self._parse_nested_name() name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix, templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=False) fullSpecShorthand=False)
@ -3651,7 +3711,7 @@ class DefinitionParser(object):
return res return res
def parse_xref_object(self): def parse_xref_object(self):
templatePrefix = self._parse_template_declaration_prefix() templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
name = self._parse_nested_name() name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix, templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=True) fullSpecShorthand=True)
@ -3812,6 +3872,26 @@ class CPPTypeObject(CPPObject):
ast.describe_signature(signode, 'lastIsName', self.env) 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): class CPPMemberObject(CPPObject):
def get_index_text(self, name): def get_index_text(self, name):
return _('%s (C++ member)') % name return _('%s (C++ member)') % name
@ -4026,6 +4106,7 @@ class CPPDomain(Domain):
'function': ObjType(l_('function'), 'func'), 'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'), 'member': ObjType(l_('member'), 'member'),
'type': ObjType(l_('type'), 'type'), 'type': ObjType(l_('type'), 'type'),
'concept': ObjType(l_('concept'), 'concept'),
'enum': ObjType(l_('enum'), 'enum'), 'enum': ObjType(l_('enum'), 'enum'),
'enumerator': ObjType(l_('enumerator'), 'enumerator') 'enumerator': ObjType(l_('enumerator'), 'enumerator')
} }
@ -4036,6 +4117,7 @@ class CPPDomain(Domain):
'member': CPPMemberObject, 'member': CPPMemberObject,
'var': CPPMemberObject, 'var': CPPMemberObject,
'type': CPPTypeObject, 'type': CPPTypeObject,
'concept': CPPConceptObject,
'enum': CPPEnumObject, 'enum': CPPEnumObject,
'enum-struct': CPPEnumObject, 'enum-struct': CPPEnumObject,
'enum-class': CPPEnumObject, 'enum-class': CPPEnumObject,
@ -4051,6 +4133,7 @@ class CPPDomain(Domain):
'member': CPPXRefRole(), 'member': CPPXRefRole(),
'var': CPPXRefRole(), 'var': CPPXRefRole(),
'type': CPPXRefRole(), 'type': CPPXRefRole(),
'concept': CPPXRefRole(),
'enum': CPPXRefRole(), 'enum': CPPXRefRole(),
'enumerator': CPPXRefRole() 'enumerator': CPPXRefRole()
} }

View File

@ -50,7 +50,7 @@ def check(name, input, idv1output=None, idv2output=None, output=None):
print("Expected: ", output) print("Expected: ", output)
raise DefinitionError("") raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None, None, None) 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() parentNode = addnodes.desc()
signode = addnodes.desc_signature(input, '') signode = addnodes.desc_signature(input, '')
parentNode += signode parentNode += signode
@ -133,6 +133,20 @@ def test_type_definitions():
check('type', 'A = B', None, '1A') check('type', 'A = B', None, '1A')
def test_concept_definitions():
check('concept', 'template<typename Param> A::B::Concept',
None, 'I0EN1A1B7ConceptE')
check('concept', 'template<typename A, typename B, typename ...C> Foo',
None, 'I00DpE3Foo')
check('concept', 'template<typename Param> A::B::Concept()',
None, 'I0EN1A1B7ConceptE')
check('concept', 'template<typename A, typename B, typename ...C> Foo()',
None, 'I00DpE3Foo')
raises(DefinitionError, parse, 'concept', 'Foo')
raises(DefinitionError, parse, 'concept',
'template<typename T> template<typename U> Foo')
def test_member_definitions(): def test_member_definitions():
check('member', ' const std::string & name = 42', check('member', ' const std::string & name = 42',
"name__ssCR", "4name", output='const std::string &name = 42') "name__ssCR", "4name", output='const std::string &name = 42')