diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 35641ec8c..1e5eb57a0 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -1272,10 +1272,12 @@ class ASTEnumerator(ASTBase): class ASTDeclaration(ASTBaseBase): - def __init__(self, objectType: str, directiveType: str, declaration: Any) -> None: + def __init__(self, objectType: str, directiveType: str, declaration: Any, + semicolon: bool = False) -> None: self.objectType = objectType self.directiveType = directiveType self.declaration = declaration + self.semicolon = semicolon self.symbol = None # type: Symbol # set by CObject._add_enumerator_to_parent @@ -1304,7 +1306,10 @@ class ASTDeclaration(ASTBaseBase): return self.get_id(_max_id, True) def _stringify(self, transform: StringifyTransform) -> str: - return transform(self.declaration) + res = transform(self.declaration) + if self.semicolon: + res += ';' + return res def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", options: Dict) -> None: @@ -1340,6 +1345,8 @@ class ASTDeclaration(ASTBaseBase): else: assert False self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol) + if self.semicolon: + mainDeclNode += nodes.Text(';') class SymbolLookupResult: @@ -2742,7 +2749,7 @@ class DefinitionParser(BaseParser): declSpecs = self._parse_decl_specs(outer=outer, typed=False) decl = self._parse_declarator(named=True, paramMode=outer, typed=False) - self.assert_end() + self.assert_end(allowSemicolon=True) except DefinitionError as exUntyped: desc = "If just a name" prevErrors.append((exUntyped, desc)) @@ -2875,7 +2882,12 @@ class DefinitionParser(BaseParser): declaration = self._parse_type(named=True, outer='type') else: assert False - return ASTDeclaration(objectType, directiveType, declaration) + if objectType != 'macro': + self.skip_ws() + semicolon = self.skip_string(';') + else: + semicolon = False + return ASTDeclaration(objectType, directiveType, declaration, semicolon) def parse_namespace_object(self) -> ASTNestedName: return self._parse_nested_name() diff --git a/tests/roots/test-domain-c/semicolon.rst b/tests/roots/test-domain-c/semicolon.rst new file mode 100644 index 000000000..14ba17756 --- /dev/null +++ b/tests/roots/test-domain-c/semicolon.rst @@ -0,0 +1,10 @@ +.. c:member:: int member; +.. c:var:: int var; +.. c:function:: void f(); +.. .. c:macro:: NO_SEMICOLON; +.. c:struct:: Struct; +.. c:union:: Union; +.. c:enum:: Enum; +.. c:enumerator:: Enumerator; +.. c:type:: Type; +.. c:type:: int TypeDef; diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 9003532e1..f85a0e62e 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -27,10 +27,8 @@ def parse(name, string): return ast -def check(name, input, idDict, output=None): +def _check(name, input, idDict, output): # first a simple check of the AST - if output is None: - output = input ast = parse(name, input) res = str(ast) if res != output: @@ -77,6 +75,16 @@ def check(name, input, idDict, output=None): raise DefinitionError("") +def check(name, input, idDict, output=None): + if output is None: + output = input + # First, check without semicolon + _check(name, input, idDict, output) + if name != 'macro': + # Second, check with semicolon + _check(name, input + ' ;', idDict, output + ';') + + def test_expressions(): def exprCheck(expr, output=None): class Config: @@ -469,8 +477,9 @@ def test_build_domain_c(app, status, warning): ws = filter_warnings(warning, "index") assert len(ws) == 0 + @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c(app, status, warning): +def test_build_domain_c_namespace(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "namespace") assert len(ws) == 0 @@ -478,6 +487,7 @@ def test_build_domain_c(app, status, warning): for id_ in ('NS.NSVar', 'NULLVar', 'ZeroVar', 'NS2.NS3.NS2NS3Var', 'PopVar'): assert 'id="c.{}"'.format(id_) in t + @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) def test_build_domain_c_anon_dup_decl(app, status, warning): app.builder.build_all() @@ -487,6 +497,13 @@ def test_build_domain_c_anon_dup_decl(app, status, warning): assert "WARNING: c:identifier reference target not found: @b" in ws[1] +@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) +def test_build_domain_c_semicolon(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "semicolon") + assert len(ws) == 0 + + def test_cfunction(app): text = (".. c:function:: PyObject* " "PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")