From a99039b13b88085c30db9fe4bc85256ca5718096 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 2 Aug 2020 10:34:51 +0200 Subject: [PATCH] C++, fix non-type template parameter parsing Fixes sphinx-doc/sphinx#7944 --- CHANGES | 2 + sphinx/domains/cpp.py | 126 +++++++++++++++++++++++---------------- tests/test_domain_cpp.py | 11 ++++ 3 files changed, 87 insertions(+), 52 deletions(-) diff --git a/CHANGES b/CHANGES index 2b54e9170be..d6b81af3289 100644 --- a/CHANGES +++ b/CHANGES @@ -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 -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 2348fb76be7..46708b846db 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -6250,23 +6250,18 @@ 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'): @@ -6274,52 +6269,79 @@ def _parse_template_parameter_list(self) -> ASTTemplateParams: 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 diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 28e437219ac..118227cd799 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -760,6 +760,7 @@ def test_templates(): check('class', "template {key}A", {2: "I0E1A"}) check('class', "template typename T> {key}A", {2: "II0E0E1A"}) + check('class', "template class T> {key}A", {2: "II0E0E1A"}) check('class', "template typename> {key}A", {2: "II0E0E1A"}) check('class', "template typename ...T> {key}A", {2: "II0EDpE1A"}) check('class', "template typename...> {key}A", {2: "II0EDpE1A"}) @@ -770,6 +771,16 @@ def test_templates(): check('class', "template {key}A", {2: "I_iE1A"}) check('class', "template {key}A", {2: "I_iE1A"}) + check('class', "template::C> {key}A", {2: "I_N1AI1BE1CEE1A"}) + check('class', "template::C = 42> {key}A", {2: "I_N1AI1BE1CEE1A"}) + # from #7944 + check('function', "template::value, bool>::type = false" + "> constexpr T *static_addressof(T &ref)", + {2: "I0_NSt9enable_ifIX!has_overloaded_addressof::valueEbE4typeEE16static_addressofR1T", + 3: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofR1T", + 4: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofP1TR1T"}) + check('class', "template<> {key}A>", {2: "IE1AIN2NS1BIEEE"}) # from #2058