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
|
* #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
|
||||||
----------
|
----------
|
||||||
|
@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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')
|
||||||
|
Loading…
Reference in New Issue
Block a user