mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
C++, add support for concept declarations.
Concept placeholders are automatically linked. Thanks to mickk-on-cpp.
This commit is contained in:
parent
1825559f74
commit
74191207db
1
CHANGES
1
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
|
||||
----------
|
||||
|
@ -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<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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -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
|
||||
-------------------
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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<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():
|
||||
check('member', ' const std::string & name = 42',
|
||||
"name__ssCR", "4name", output='const std::string &name = 42')
|
||||
|
Loading…
Reference in New Issue
Block a user