Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++, fix non-type template parameter parsing #8037

Merged
merged 1 commit into from Aug 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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