Merge pull request #8330 from sphinx-doc/3.2.x_to_3.x

Merge 3.2.x to 3.x
This commit is contained in:
Takeshi KOMIYA 2020-10-24 17:34:11 +09:00 committed by GitHub
commit 624f693719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 49 deletions

View File

@ -24,9 +24,6 @@ jobs:
env:
- TOXENV=du15
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
- python: 'nightly'
env:
- TOXENV=du16
- language: node_js
node_js: '10.7'

View File

@ -84,6 +84,11 @@ Bugs fixed
* #8188: C, add missing items to internal object types dictionary,
e.g., preventing intersphinx from resolving them.
* 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

@ -44,7 +44,7 @@ extras_require = {
'lint': [
'flake8>=3.5.0',
'flake8-import-order',
'mypy>=0.780',
'mypy>=0.790',
'docutils-stubs',
],
'test': [

View File

@ -10,9 +10,8 @@
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 typing import cast
from docutils import nodes
from docutils.nodes import Element, Node, TextElement, system_message
@ -47,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',
@ -636,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 '...'
@ -1149,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
@ -1191,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))
@ -1242,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))
@ -1342,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
@ -1359,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:
@ -1413,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:
@ -2988,7 +3009,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()
@ -3011,7 +3032,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':
@ -3158,10 +3179,6 @@ class CObject(ObjectDescription):
self.state.document.note_explicit_target(signode)
domain = cast(CDomain, self.env.get_domain('c'))
if name not in domain.objects:
domain.objects[name] = (domain.env.docname, newestId, self.objtype)
if 'noindexentry' not in self.options:
indexText = self.get_index_text(name)
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
@ -3681,10 +3698,6 @@ class CDomain(Domain):
'objects': {}, # fullname -> docname, node_id, objtype
} # type: Dict[str, Union[Symbol, Dict[str, Tuple[str, str, str]]]]
@property
def objects(self) -> Dict[str, Tuple[str, str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
def clear_doc(self, docname: str) -> None:
if Symbol.debug_show_tree:
print("clear_doc:", docname)
@ -3700,9 +3713,6 @@ class CDomain(Domain):
print(self.data['root_symbol'].dump(1))
print("\tafter end")
print("clear_doc end:", docname)
for fullname, (fn, _id, _l) in list(self.objects.items()):
if fn == docname:
del self.objects[fullname]
def process_doc(self, env: BuildEnvironment, docname: str,
document: nodes.document) -> None:
@ -3788,8 +3798,18 @@ class CDomain(Domain):
return []
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
for refname, (docname, node_id, objtype) in list(self.objects.items()):
yield (refname, refname, objtype, docname, node_id, 1)
rootSymbol = self.data['root_symbol']
for symbol in rootSymbol.get_all_symbols():
if symbol.declaration is None:
continue
assert symbol.docname
fullNestedName = symbol.get_full_nested_name()
name = str(fullNestedName).lstrip('.')
dispname = fullNestedName.get_display_string().lstrip('.')
objectType = symbol.declaration.objectType
docname = symbol.docname
newestId = symbol.declaration.get_newest_id()
yield (name, dispname, objectType, docname, newestId, 1)
def setup(app: Sphinx) -> Dict[str, Any]:

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'
@ -4107,7 +4107,7 @@ class Symbol:
Symbol.debug_print("self:")
print(self.to_string(Symbol.debug_indent + 1), end="")
Symbol.debug_print("nestedName: ", nestedName)
Symbol.debug_print("templateDecls: ", templateDecls)
Symbol.debug_print("templateDecls: ", ",".join(str(t) for t in templateDecls))
Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists)
Symbol.debug_print("ancestorLookupType:", ancestorLookupType)
Symbol.debug_print("templateShorthand: ", templateShorthand)
@ -4231,7 +4231,7 @@ class Symbol:
Symbol.debug_indent += 1
Symbol.debug_print("_add_symbols:")
Symbol.debug_indent += 1
Symbol.debug_print("tdecls:", templateDecls)
Symbol.debug_print("tdecls:", ",".join(str(t) for t in templateDecls))
Symbol.debug_print("nn: ", nestedName)
Symbol.debug_print("decl: ", declaration)
Symbol.debug_print("doc: ", docname)
@ -4360,6 +4360,11 @@ class Symbol:
if Symbol.debug_lookup:
Symbol.debug_print("candId:", candId)
for symbol in withDecl:
# but all existing must be functions as well,
# otherwise we declare it to be a duplicate
if symbol.declaration.objectType != 'function':
handleDuplicateDeclaration(symbol, candSymbol)
# (not reachable)
oldId = symbol.declaration.get_newest_id()
if Symbol.debug_lookup:
Symbol.debug_print("oldId: ", oldId)
@ -4370,7 +4375,11 @@ class Symbol:
# if there is an empty symbol, fill that one
if len(noDecl) == 0:
if Symbol.debug_lookup:
Symbol.debug_print("no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA
Symbol.debug_print("no match, no empty")
if candSymbol is not None:
Symbol.debug_print("result is already created candSymbol")
else:
Symbol.debug_print("result is makeCandSymbol()")
Symbol.debug_indent -= 2
if candSymbol is not None:
return candSymbol
@ -6814,10 +6823,12 @@ class CPPObject(ObjectDescription):
parentSymbol = env.temp_data['cpp:parent_symbol']
parentDecl = parentSymbol.declaration
if parentDecl is not None and parentDecl.objectType == 'function':
logger.warning("C++ declarations inside functions are not supported." +
" Parent function is " +
str(parentSymbol.get_full_nested_name()),
location=self.get_source_info())
msg = "C++ declarations inside functions are not supported." \
" Parent function: {}\nDirective name: {}\nDirective arg: {}"
logger.warning(msg.format(
str(parentSymbol.get_full_nested_name()),
self.name, self.arguments[0]
), location=self.get_source_info())
name = _make_phony_error_name()
symbol = parentSymbol.add_name(name)
env.temp_data['cpp:last_symbol'] = symbol

View File

@ -1764,7 +1764,7 @@ class TypeVarDocumenter(DataDocumenter):
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return isinstance(member, TypeVar) and isattr # type: ignore
return isinstance(member, TypeVar) and isattr
def add_directive_header(self, sig: str) -> None:
self.options = Options(self.options)

View File

@ -297,8 +297,8 @@ class IndexBuilder:
frozen.get('envversion') != self.env.version:
raise ValueError('old format')
index2fn = frozen['docnames']
self._filenames = dict(zip(index2fn, frozen['filenames'])) # type: ignore
self._titles = dict(zip(index2fn, frozen['titles'])) # type: ignore
self._filenames = dict(zip(index2fn, frozen['filenames']))
self._titles = dict(zip(index2fn, frozen['titles']))
def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]:
rv = {}
@ -359,13 +359,13 @@ class IndexBuilder:
def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]]
for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)):
for k, v in mapping.items(): # type: ignore
for k, v in mapping.items():
if len(v) == 1:
fn, = v
if fn in fn2index:
rv[k] = fn2index[fn] # type: ignore
rv[k] = fn2index[fn]
else:
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) # type: ignore # NOQA
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index])
return rvs
def freeze(self) -> Dict[str, Any]:

View File

@ -57,7 +57,7 @@ Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]]
def is_system_TypeVar(typ: Any) -> bool:
"""Check *typ* is system defined TypeVar."""
modname = getattr(typ, '__module__', '')
return modname == 'typing' and isinstance(typ, TypeVar) # type: ignore
return modname == 'typing' and isinstance(typ, TypeVar)
def stringify(annotation: Any) -> str:
@ -68,7 +68,7 @@ def stringify(annotation: Any) -> str:
return annotation[1:-2]
else:
return annotation
elif isinstance(annotation, TypeVar): # type: ignore
elif isinstance(annotation, TypeVar):
return annotation.__name__
elif not annotation:
return repr(annotation)

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,26 @@ 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():
if name == queryName:
return (docname, anchor, objectType)
return (queryName, "not", "found")
def test_cfunction(app):
text = (".. c:function:: PyObject* "
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
@ -569,8 +610,7 @@ def test_cfunction(app):
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="c", objtype="function", noindex=False)
domain = app.env.get_domain('c')
entry = domain.objects.get('PyType_GenericAlloc')
entry = _get_obj(app, 'PyType_GenericAlloc')
assert entry == ('index', 'c.PyType_GenericAlloc', 'function')
@ -580,8 +620,7 @@ def test_cmember(app):
assert_node(doctree[1], addnodes.desc, desctype="member",
domain="c", objtype="member", noindex=False)
domain = app.env.get_domain('c')
entry = domain.objects.get('PyTypeObject.tp_bases')
entry = _get_obj(app, 'PyTypeObject.tp_bases')
assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member')
@ -591,9 +630,8 @@ def test_cvar(app):
assert_node(doctree[1], addnodes.desc, desctype="var",
domain="c", objtype="var", noindex=False)
domain = app.env.get_domain('c')
entry = domain.objects.get('PyClass_Type')
assert entry == ('index', 'c.PyClass_Type', 'var')
entry = _get_obj(app, 'PyClass_Type')
assert entry == ('index', 'c.PyClass_Type', 'member')
def test_noindexentry(app):

View File

@ -1236,3 +1236,18 @@ def test_noindexentry(app):
assert_node(doctree, (addnodes.index, desc, addnodes.index, desc))
assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C++ function)', '_CPPv41fv', '', None)])
assert_node(doctree[2], addnodes.index, entries=[])
def test_mix_decl_duplicate(app, warning):
# Issue 8270
text = (".. cpp:struct:: A\n"
".. cpp:function:: void A()\n"
".. cpp:struct:: A\n")
restructuredtext.parse(app, text)
ws = warning.getvalue().split("\n")
assert len(ws) == 5
assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[0]
assert "Declaration is 'void A()'." in ws[1]
assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[2]
assert "Declaration is 'A'." in ws[3]
assert ws[4] == ""