C, fix links to function parameters

This commit is contained in:
Jakob Lykke Andersen 2020-10-13 12:30:13 +02:00
parent 488f4a6ac4
commit 15251574a9
5 changed files with 72 additions and 9 deletions

View File

@ -21,6 +21,8 @@ Bugs fixed
* C, fix anon objects in intersphinx.
* #8270, C++, properly reject functions as duplicate declarations if a
non-function declaration of the same name already exists.
* C, fix references to function parameters.
Link to the function instead of a non-existing anchor.
Testing

View File

@ -10,7 +10,7 @@
import re
from typing import (
Any, Callable, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union
Any, Callable, cast, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union
)
from docutils import nodes
@ -46,6 +46,11 @@ from sphinx.util.nodes import make_refnode
logger = logging.getLogger(__name__)
T = TypeVar('T')
DeclarationType = Union[
"ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator",
"ASTType", "ASTTypeWithInit", "ASTMacro",
]
# https://en.cppreference.com/w/c/keyword
_keywords = [
'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double',
@ -635,6 +640,10 @@ class ASTFunctionParameter(ASTBase):
self.arg = arg
self.ellipsis = ellipsis
def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
# the anchor will be our parent
return symbol.parent.declaration.get_id(version, prefixed=False)
def _stringify(self, transform: StringifyTransform) -> str:
if self.ellipsis:
return '...'
@ -1148,6 +1157,9 @@ class ASTType(ASTBase):
def name(self) -> ASTNestedName:
return self.decl.name
def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
return symbol.get_full_nested_name().get_id(version)
@property
def function_params(self) -> List[ASTFunctionParameter]:
return self.decl.function_params
@ -1190,6 +1202,9 @@ class ASTTypeWithInit(ASTBase):
def name(self) -> ASTNestedName:
return self.type.name
def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
return self.type.get_id(version, objectType, symbol)
def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.type))
@ -1241,6 +1256,9 @@ class ASTMacro(ASTBase):
def name(self) -> ASTNestedName:
return self.ident
def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
return symbol.get_full_nested_name().get_id(version)
def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.ident))
@ -1341,7 +1359,8 @@ class ASTEnumerator(ASTBase):
class ASTDeclaration(ASTBaseBase):
def __init__(self, objectType: str, directiveType: str, declaration: Any,
def __init__(self, objectType: str, directiveType: str,
declaration: Union[DeclarationType, ASTFunctionParameter],
semicolon: bool = False) -> None:
self.objectType = objectType
self.directiveType = directiveType
@ -1358,18 +1377,20 @@ class ASTDeclaration(ASTBaseBase):
@property
def name(self) -> ASTNestedName:
return self.declaration.name
decl = cast(DeclarationType, self.declaration)
return decl.name
@property
def function_params(self) -> List[ASTFunctionParameter]:
if self.objectType != 'function':
return None
return self.declaration.function_params
decl = cast(ASTType, self.declaration)
return decl.function_params
def get_id(self, version: int, prefixed: bool = True) -> str:
if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
id_ = self.symbol.get_full_nested_name().get_id(version)
id_ = self.declaration.get_id(version, self.objectType, self.symbol)
if prefixed:
return _id_prefix[version] + id_
else:
@ -1412,7 +1433,8 @@ class ASTDeclaration(ASTBaseBase):
elif self.objectType == 'enumerator':
mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ')
elif self.objectType == 'type':
prefix = self.declaration.get_type_declaration_prefix()
decl = cast(ASTType, self.declaration)
prefix = decl.get_type_declaration_prefix()
prefix += ' '
mainDeclNode += addnodes.desc_annotation(prefix, prefix)
else:
@ -2982,7 +3004,7 @@ class DefinitionParser(BaseParser):
def parse_pre_v3_type_definition(self) -> ASTDeclaration:
self.skip_ws()
declaration = None # type: Any
declaration = None # type: DeclarationType
if self.skip_word('struct'):
typ = 'struct'
declaration = self._parse_struct()
@ -3005,7 +3027,7 @@ class DefinitionParser(BaseParser):
'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
declaration = None # type: Any
declaration = None # type: DeclarationType
if objectType == 'member':
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':

View File

@ -1836,7 +1836,7 @@ class ASTFunctionParameter(ASTBase):
# this is not part of the normal name mangling in C++
if symbol:
# the anchor will be our parent
return symbol.parent.declaration.get_id(version, prefixed=None)
return symbol.parent.declaration.get_id(version, prefixed=False)
# else, do the usual
if self.ellipsis:
return 'z'

View File

@ -0,0 +1,5 @@
.. c:function:: void f(int i)
- :c:var:`i`
- :c:var:`f.i`

View File

@ -9,6 +9,8 @@
"""
import pytest
from xml.etree import ElementTree
from sphinx import addnodes
from sphinx.addnodes import desc
from sphinx.domains.c import DefinitionParser, DefinitionError
@ -529,6 +531,25 @@ def filter_warnings(warning, file):
return res
def extract_role_links(app, filename):
t = (app.outdir / filename).read_text()
lis = [l for l in t.split('\n') if l.startswith("<li")]
entries = []
for l in lis:
li = ElementTree.fromstring(l)
aList = list(li.iter('a'))
assert len(aList) == 1
a = aList[0]
target = a.attrib['href'].lstrip('#')
title = a.attrib['title']
assert len(a) == 1
code = a[0]
assert code.tag == 'code'
text = ''.join(code.itertext())
entries.append((target, title, text))
return entries
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
def test_build_domain_c(app, status, warning):
app.builder.build_all()
@ -562,6 +583,19 @@ def test_build_domain_c_semicolon(app, status, warning):
assert len(ws) == 0
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
def test_build_function_param_target(app, warning):
# the anchor for function parameters should be the function
app.builder.build_all()
ws = filter_warnings(warning, "function_param_target")
assert len(ws) == 0
entries = extract_role_links(app, "function_param_target.html")
assert entries == [
('c.f', 'i', 'i'),
('c.f', 'f.i', 'f.i'),
]
def _get_obj(app, queryName):
domain = app.env.get_domain('c')
for name, dispname, objectType, docname, anchor, prio in domain.get_objects():