diff --git a/CHANGES b/CHANGES index 2826469670e..50008f51980 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,7 @@ Features added * #8095: napoleon: Add :confval:`napoleon_preprocess_types` to enable the type preprocessor for numpy style docstrings +* #8114: C and C++, parse function attributes after parameters and qualifiers. Bugs fixed ---------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 65786b5de05..d8ccc2e3daf 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -32,7 +32,7 @@ from sphinx.transforms.post_transforms import ReferencesResolver from sphinx.util import logging from sphinx.util.cfamily import ( - NoOldIdError, ASTBaseBase, ASTBaseParenExprList, + NoOldIdError, ASTBaseBase, ASTAttribute, ASTBaseParenExprList, verify_description_mode, StringifyTransform, BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral, identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re, @@ -652,8 +652,9 @@ def describe_signature(self, signode: Any, mode: str, class ASTParameters(ASTBase): - def __init__(self, args: List[ASTFunctionParameter]) -> None: + def __init__(self, args: List[ASTFunctionParameter], attrs: List[ASTAttribute]) -> None: self.args = args + self.attrs = attrs @property def function_params(self) -> List[ASTFunctionParameter]: @@ -669,6 +670,9 @@ def _stringify(self, transform: StringifyTransform) -> str: first = False res.append(str(a)) res.append(')') + for attr in self.attrs: + res.append(' ') + res.append(transform(attr)) return ''.join(res) def describe_signature(self, signode: TextElement, mode: str, @@ -683,6 +687,9 @@ def describe_signature(self, signode: TextElement, mode: str, arg.describe_signature(param, 'markType', env, symbol=symbol) paramlist += param signode += paramlist + for attr in self.attrs: + signode += nodes.Text(' ') + attr.describe_signature(signode) class ASTDeclSpecsSimple(ASTBaseBase): @@ -2572,7 +2579,15 @@ def _parse_parameters(self, paramMode: str) -> ASTParameters: self.fail( 'Expecting "," or ")" in parameters, ' 'got "%s".' % self.current_char) - return ASTParameters(args) + + attrs = [] + while True: + attr = self._parse_attribute() + if attr is None: + break + attrs.append(attr) + + return ASTParameters(args, attrs) def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple: """Just parse the simple ones.""" diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 0b3c02e83f1..92d5784275a 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1879,7 +1879,8 @@ def describe_signature(self, signode: TextElement, mode: str, class ASTParametersQualifiers(ASTBase): def __init__(self, args: List[ASTFunctionParameter], volatile: bool, const: bool, refQual: str, exceptionSpec: ASTNoexceptSpec, trailingReturn: "ASTType", - override: bool, final: bool, initializer: str) -> None: + override: bool, final: bool, attrs: List[ASTAttribute], + initializer: str) -> None: self.args = args self.volatile = volatile self.const = const @@ -1888,6 +1889,7 @@ def __init__(self, args: List[ASTFunctionParameter], volatile: bool, const: bool self.trailingReturn = trailingReturn self.override = override self.final = final + self.attrs = attrs self.initializer = initializer @property @@ -1947,6 +1949,9 @@ def _stringify(self, transform: StringifyTransform) -> str: res.append(' final') if self.override: res.append(' override') + for attr in self.attrs: + res.append(' ') + res.append(transform(attr)) if self.initializer: res.append(' = ') res.append(self.initializer) @@ -1988,6 +1993,9 @@ def _add_text(signode: TextElement, text: str) -> None: _add_anno(signode, 'final') if self.override: _add_anno(signode, 'override') + for attr in self.attrs: + signode += nodes.Text(' ') + attr.describe_signature(signode) if self.initializer: _add_text(signode, '= ' + str(self.initializer)) @@ -5709,6 +5717,13 @@ def _parse_parameters_and_qualifiers(self, paramMode: str) -> ASTParametersQuali override = self.skip_word_and_ws( 'override') # they can be permuted + attrs = [] + while True: + attr = self._parse_attribute() + if attr is None: + break + attrs.append(attr) + self.skip_ws() initializer = None if self.skip_string('='): @@ -5725,7 +5740,7 @@ def _parse_parameters_and_qualifiers(self, paramMode: str) -> ASTParametersQuali return ASTParametersQualifiers( args, volatile, const, refQual, exceptionSpec, trailingReturn, - override, final, initializer) + override, final, attrs, initializer) def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple: """Just parse the simple ones.""" diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index a67b2a0add2..0edea128c50 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -391,7 +391,7 @@ def _parse_balanced_token_seq(self, end: List[str]) -> str: % startPos) return self.definition[startPos:self.pos] - def _parse_attribute(self) -> ASTAttribute: + def _parse_attribute(self) -> Optional[ASTAttribute]: self.skip_ws() # try C++11 style startPos = self.pos diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 71bf251e933..b6f72287ee7 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -497,17 +497,16 @@ def test_attributes(): parse('member', 'paren_attr({]}) int f') # position: decl specs - check('function', 'static inline __attribute__(()) void f()', - {1: 'f'}, + check('function', 'static inline __attribute__(()) void f()', {1: 'f'}, output='__attribute__(()) static inline void f()') - check('function', '[[attr1]] [[attr2]] void f()', - {1: 'f'}, - output='[[attr1]] [[attr2]] void f()') + check('function', '[[attr1]] [[attr2]] void f()', {1: 'f'}) # position: declarator check('member', 'int *[[attr]] i', {1: 'i'}) check('member', 'int *const [[attr]] volatile i', {1: 'i'}, output='int *[[attr]] volatile const i') check('member', 'int *[[attr]] *i', {1: 'i'}) + # position: parameters + check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f'}) # issue michaeljones/breathe#500 check('function', 'LIGHTGBM_C_EXPORT int LGBM_BoosterFree(int handle)', diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 513205cabca..558d6991199 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -938,15 +938,15 @@ def test_attributes(): check('function', 'static inline __attribute__(()) void f()', {1: 'f', 2: '1fv'}, output='__attribute__(()) static inline void f()') - check('function', '[[attr1]] [[attr2]] void f()', - {1: 'f', 2: '1fv'}, - output='[[attr1]] [[attr2]] void f()') + check('function', '[[attr1]] [[attr2]] void f()', {1: 'f', 2: '1fv'}) # position: declarator check('member', 'int *[[attr]] i', {1: 'i__iP', 2: '1i'}) check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'}, output='int *[[attr]] volatile const i') check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'}) check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'}) + # position: parameters and qualifiers + check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f', 2: '1fv'}) def test_xref_parsing():