diff options
author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2020-10-24 17:34:11 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-24 17:34:11 +0900 |
commit | 624f6937194e1acfe7311faf6e27e370c3118e55 (patch) | |
tree | 7fbdafa1fae22a39e6532fc5965551286777352f | |
parent | 0476e1cea93b587e3c0ab295aa20b2b9f0f81a34 (diff) | |
parent | 2a77eb07d008e004348ee67a759263754dfc66c0 (diff) | |
download | sphinx-git-624f6937194e1acfe7311faf6e27e370c3118e55.tar.gz |
Merge pull request #8330 from sphinx-doc/3.2.x_to_3.x
Merge 3.2.x to 3.x
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | sphinx/domains/c.py | 64 | ||||
-rw-r--r-- | sphinx/domains/cpp.py | 27 | ||||
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 2 | ||||
-rw-r--r-- | sphinx/search/__init__.py | 10 | ||||
-rw-r--r-- | sphinx/util/typing.py | 4 | ||||
-rw-r--r-- | tests/roots/test-domain-c/function_param_target.rst | 5 | ||||
-rw-r--r-- | tests/test_domain_c.py | 52 | ||||
-rw-r--r-- | tests/test_domain_cpp.py | 15 |
11 files changed, 140 insertions, 49 deletions
diff --git a/.travis.yml b/.travis.yml index d73be03ec..47a8e7c7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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' @@ -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 @@ -44,7 +44,7 @@ extras_require = { 'lint': [ 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.780', + 'mypy>=0.790', 'docutils-stubs', ], 'test': [ diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 304c871ea..fafe827af 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -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]: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7b10f8166..0e996532f 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index b68fc73c9..978fd5df8 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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) diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 4534dd333..048b333d5 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -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]: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 8eca67220..76438889b 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -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) diff --git a/tests/roots/test-domain-c/function_param_target.rst b/tests/roots/test-domain-c/function_param_target.rst new file mode 100644 index 000000000..05de01445 --- /dev/null +++ b/tests/roots/test-domain-c/function_param_target.rst @@ -0,0 +1,5 @@ +.. c:function:: void f(int i) + + - :c:var:`i` + +- :c:var:`f.i` diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index b6f72287e..43d71f74e 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -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): diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 558d69911..ec7a2b262 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -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] == ""
\ No newline at end of file |