Skip to content

Commit

Permalink
[C++] Support requires-clause on type, class, 'member' declarations
Browse files Browse the repository at this point in the history
Previously a C++20 requires-clause was only supported on `function`
declarations.  However, the C++ standard allows a require-clause on
class/union templates, alias templates, and variable templates.

Additionally:

- This adds support for template parameters on unions.

- This adds support for trailing requires clauses on functions with a
  template prefix.  This is allowed by C++20 for non-template members
  of class templates.
  • Loading branch information
jbms committed Mar 23, 2022
1 parent 670e8b1 commit b41aa75
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 6 deletions.
18 changes: 12 additions & 6 deletions sphinx/domains/cpp.py
Expand Up @@ -3912,7 +3912,7 @@ def function_params(self) -> List[ASTFunctionParameter]:

def get_id(self, version: int, prefixed: bool = True) -> str:
if version == 1:
if self.templatePrefix:
if self.templatePrefix or self.trailingRequiresClause:
raise NoOldIdError()
if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
return self.enumeratorScopedSymbol.declaration.get_id(version)
Expand Down Expand Up @@ -6475,7 +6475,14 @@ def _parse_type(self, named: Union[bool, str], outer: str = None) -> ASTType:
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
decl = self._parse_declarator(named=True, paramMode=outer,
typed=False)
self.assert_end(allowSemicolon=True)
must_end = True
if outer == 'function':
# Allow trailing requires on constructors
self.skip_ws()
if re.compile(r'requires\b').match(self.definition, self.pos):
must_end = False
if must_end:
self.assert_end(allowSemicolon=True)
except DefinitionError as exUntyped:
if outer == 'type':
desc = "If just a name"
Expand Down Expand Up @@ -6941,9 +6948,9 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
if self.match(_visibility_re):
visibility = self.matched_text

if objectType in ('type', 'concept', 'member', 'function', 'class'):
if objectType in ('type', 'concept', 'member', 'function', 'class', 'union'):
templatePrefix = self._parse_template_declaration_prefix(objectType)
if objectType == 'function' and templatePrefix is not None:
if objectType != 'concept' and templatePrefix is not None:
requiresClause = self._parse_requires_clause()

if objectType == 'type':
Expand All @@ -6970,8 +6977,7 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
declaration = self._parse_type(named=True, outer='function')
if templatePrefix is not None:
trailingRequiresClause = self._parse_requires_clause()
trailingRequiresClause = self._parse_requires_clause()
elif objectType == 'class':
declaration = self._parse_class()
elif objectType == 'union':
Expand Down
16 changes: 16 additions & 0 deletions tests/test_domain_cpp.py
Expand Up @@ -896,8 +896,24 @@ def test_domain_cpp_ast_requires_clauses():
{4: 'I0EIQaa1A1BE1fvv'})
check('function', 'template<typename T> requires A || B or C void f()',
{4: 'I0EIQoo1Aoo1B1CE1fvv'})
check('function', 'void f() requires A || B || C',
{4: 'IQoo1Aoo1B1CE1fv'})
check('function', 'Foo() requires A || B || C',
{4: 'IQoo1Aoo1B1CE3Foov'})
check('function', 'template<typename T> requires A && B || C and D void f()',
{4: 'I0EIQooaa1A1Baa1C1DE1fvv'})
check('type',
'template<typename T> requires IsValid<T> {key}T = true_type',
{4: 'I0EIQ7IsValidI1TEE1T'}, key='using')
check('class',
'template<typename T> requires IsValid<T> {key}T : Base',
{4: 'I0EIQ7IsValidI1TEE1T'}, key='class')
check('union',
'template<typename T> requires IsValid<T> {key}T',
{4: 'I0EIQ7IsValidI1TEE1T'}, key='union')
check('member',
'template<typename T> requires IsValid<T> int Val = 7',
{4: 'I0EIQ7IsValidI1TEE3Val'})


def test_domain_cpp_ast_template_args():
Expand Down

0 comments on commit b41aa75

Please sign in to comment.