Skip to content

Commit

Permalink
C++, fix non-type template parameter parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobandersen committed Aug 2, 2020
1 parent 4f56fad commit a99039b
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -87,6 +87,8 @@ Bugs fixed
* #2050: Symbols sections are appeared twice in the index page
* #8017: Fix circular import in sphinx.addnodes
* #7986: CSS: make "highlight" selector more robust
* #7944: C++, parse non-type template parameters starting with
a dependent qualified name.

Testing
--------
Expand Down
126 changes: 74 additions & 52 deletions sphinx/domains/cpp.py
Expand Up @@ -6250,76 +6250,98 @@ def parser() -> ASTExpression:

# ==========================================================================

def _parse_template_parameter_list(self) -> ASTTemplateParams:
# only: '<' parameter-list '>'
# we assume that 'template' has just been parsed
templateParams = [] # type: List[ASTTemplateParam]
self.skip_ws()
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
prevErrors = []
while 1:
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
nestedParams = self._parse_template_parameter_list()
else:
nestedParams = None
self.skip_ws()
def _parse_template_paramter(self) -> ASTTemplateParam:
if self.skip_word('template'):
# declare a tenplate template parameter
nestedParams = self._parse_template_parameter_list()
else:
nestedParams = None

pos = self.pos
try:
# Unconstrained type parameter or template type parameter
key = None
self.skip_ws()
if self.skip_word_and_ws('typename'):
key = 'typename'
elif self.skip_word_and_ws('class'):
key = 'class'
elif nestedParams:
self.fail("Expected 'typename' or 'class' after "
"template template parameter list.")
if key:
# declare a type or template type parameter
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
else:
identifier = None
self.skip_ws()
if not parameterPack and self.skip_string('='):
default = self._parse_type(named=False, outer=None)
else:
default = None
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
parameterPack, default)
if nestedParams:
# template type
templateParams.append(
ASTTemplateParamTemplateType(nestedParams, data))
else:
# type
templateParams.append(ASTTemplateParamType(data))
else:
# declare a non-type parameter, or constrained type parameter
pos = self.pos
try:
param = self._parse_type_with_init('maybe', 'templateParam')
templateParams.append(ASTTemplateParamNonType(param))
except DefinitionError as e:
msg = "If non-type template parameter or constrained template parameter"
prevErrors.append((e, msg))
self.pos = pos
self.fail("Expected 'typename' or 'class' in tbe "
"beginning of template type parameter.")
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
else:
identifier = None
self.skip_ws()
if not parameterPack and self.skip_string('='):
default = self._parse_type(named=False, outer=None)
else:
default = None
if self.current_char not in ',>':
self.fail('Expected "," or ">" after (template) type parameter.')
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
parameterPack, default)
if nestedParams:
return ASTTemplateParamTemplateType(nestedParams, data)
else:
return ASTTemplateParamType(data)
except DefinitionError as eType:
if nestedParams:
raise
try:
# non-type parameter or constrained type parameter
self.pos = pos
param = self._parse_type_with_init('maybe', 'templateParam')
return ASTTemplateParamNonType(param)
except DefinitionError as eNonType:
self.pos = pos
header = "Error when parsing template parameter."
errs = []
errs.append(
(eType, "If unconstrained type parameter or template type parameter"))
errs.append(
(eNonType, "If constrained type parameter or non-type parameter"))
raise self._make_multi_error(errs, header)

def _parse_template_parameter_list(self) -> ASTTemplateParams:
# only: '<' parameter-list '>'
# we assume that 'template' has just been parsed
templateParams = [] # type: List[ASTTemplateParam]
self.skip_ws()
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
while 1:
pos = self.pos
err = None
try:
param = self._parse_template_paramter()
templateParams.append(param)
except DefinitionError as eParam:
self.pos = pos
err = eParam
self.skip_ws()
if self.skip_string('>'):
return ASTTemplateParams(templateParams)
elif self.skip_string(','):
prevErrors = []
continue
else:
header = "Error in template parameter list."
errs = []
if err:
errs.append((err, "If parameter"))
try:
self.fail('Expected "=", ",", or ">".')
self.fail('Expected "," or ">".')
except DefinitionError as e:
prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header)
errs.append((e, "If no parameter"))
print(errs)
raise self._make_multi_error(errs, header)

def _parse_template_introduction(self) -> ASTTemplateIntroduction:
pos = self.pos
Expand Down
11 changes: 11 additions & 0 deletions tests/test_domain_cpp.py
Expand Up @@ -760,6 +760,7 @@ def test_templates():
check('class', "template<typename T = Test> {key}A", {2: "I0E1A"})

check('class', "template<template<typename> typename T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> class T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> {key}A", {2: "II0EDpE1A"})
check('class', "template<template<typename> typename...> {key}A", {2: "II0EDpE1A"})
Expand All @@ -770,6 +771,16 @@ def test_templates():
check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
check('class', "template<int = 42> {key}A", {2: "I_iE1A"})

check('class', "template<typename A<B>::C> {key}A", {2: "I_N1AI1BE1CEE1A"})
check('class', "template<typename A<B>::C = 42> {key}A", {2: "I_N1AI1BE1CEE1A"})
# from #7944
check('function', "template<typename T, "
"typename std::enable_if<!has_overloaded_addressof<T>::value, bool>::type = false"
"> constexpr T *static_addressof(T &ref)",
{2: "I0_NSt9enable_ifIX!has_overloaded_addressof<T>::valueEbE4typeEE16static_addressofR1T",
3: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofR1T",
4: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofP1TR1T"})

check('class', "template<> {key}A<NS::B<>>", {2: "IE1AIN2NS1BIEEE"})

# from #2058
Expand Down

0 comments on commit a99039b

Please sign in to comment.