diff --git a/CHANGES b/CHANGES index 37d8d6a633a..c0370d82d30 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Dependencies Incompatible changes -------------------- +* #7418: std domain: :rst:dir:`term` role becomes case sensitive + Deprecated ---------- @@ -17,8 +19,12 @@ Bugs fixed ---------- * #7428: py domain: a reference to class ``None`` emits a nitpicky warning +* #7418: std domain: duplication warning for glossary terms is case insensitive +* #7438: C++, fix merging overloaded functions in parallel builds. +* #7422: autodoc: fails with ValueError when using autodoc_mock_imports * #7435: autodoc: ``autodoc_typehints='description'`` doesn't suppress typehints in signature for classes/methods +* #7423: crashed when giving a non-string object to logger Testing -------- diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 8c909e31504..266da52b73a 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -27,7 +27,7 @@ Discovery of builders by entry point .. versionadded:: 1.6 -:term:`Builder` extensions can be discovered by means of `entry points`_ so +:term:`builder` extensions can be discovered by means of `entry points`_ so that they do not have to be listed in the :confval:`extensions` configuration value. diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dbb6d1a39c2..8180384dab3 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4300,18 +4300,73 @@ def merge_with(self, other: "Symbol", docnames: List[str], Symbol.debug_indent += 1 Symbol.debug_print("merge_with:") assert other is not None + + def unconditionalAdd(self, otherChild): + # TODO: hmm, should we prune by docnames? + self._children.append(otherChild) + otherChild.parent = self + otherChild._assert_invariants() + + if Symbol.debug_lookup: + Symbol.debug_indent += 1 for otherChild in other._children: - ourChild = self._find_first_named_symbol( + if Symbol.debug_lookup: + Symbol.debug_print("otherChild:\n", otherChild.to_string(Symbol.debug_indent)) + Symbol.debug_indent += 1 + if otherChild.isRedeclaration: + unconditionalAdd(self, otherChild) + if Symbol.debug_lookup: + Symbol.debug_print("isRedeclaration") + Symbol.debug_indent -= 1 + continue + candiateIter = self._find_named_symbols( identOrOp=otherChild.identOrOp, templateParams=otherChild.templateParams, templateArgs=otherChild.templateArgs, templateShorthand=False, matchSelf=False, - recurseInAnon=False, correctPrimaryTemplateArgs=False) + recurseInAnon=False, correctPrimaryTemplateArgs=False, + searchInSiblings=False) + candidates = list(candiateIter) + + if Symbol.debug_lookup: + Symbol.debug_print("raw candidate symbols:", len(candidates)) + symbols = [s for s in candidates if not s.isRedeclaration] + if Symbol.debug_lookup: + Symbol.debug_print("non-duplicate candidate symbols:", len(symbols)) + + if len(symbols) == 0: + unconditionalAdd(self, otherChild) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 + continue + + ourChild = None + if otherChild.declaration is None: + if Symbol.debug_lookup: + Symbol.debug_print("no declaration in other child") + ourChild = symbols[0] + else: + queryId = otherChild.declaration.get_newest_id() + if Symbol.debug_lookup: + Symbol.debug_print("queryId: ", queryId) + for symbol in symbols: + if symbol.declaration is None: + if Symbol.debug_lookup: + Symbol.debug_print("empty candidate") + # if in the end we have non matching, but have an empty one, + # then just continue with that + ourChild = symbol + continue + candId = symbol.declaration.get_newest_id() + if Symbol.debug_lookup: + Symbol.debug_print("candidate:", candId) + if candId == queryId: + ourChild = symbol + break + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 if ourChild is None: - # TODO: hmm, should we prune by docnames? - self._children.append(otherChild) - otherChild.parent = self - otherChild._assert_invariants() + unconditionalAdd(self, otherChild) continue if otherChild.declaration and otherChild.docname in docnames: if not ourChild.declaration: @@ -4326,10 +4381,14 @@ def merge_with(self, other: "Symbol", docnames: List[str], # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to # just ignore it, right? - pass + # Hmm, only on duplicate declarations, right? + msg = "Internal C++ domain error during symbol merging.\n" + msg += "ourChild:\n" + ourChild.to_string(1) + msg += "\notherChild:\n" + otherChild.to_string(1) + logger.warning(msg, location=otherChild.docname) ourChild.merge_with(otherChild, docnames, env) if Symbol.debug_lookup: - Symbol.debug_indent -= 1 + Symbol.debug_indent -= 2 def add_name(self, nestedName: ASTNestedName, templatePrefix: ASTTemplateDeclarationPrefix = None) -> "Symbol": @@ -7116,7 +7175,6 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: print("\tother:") print(otherdata['root_symbol'].dump(1)) print("\tother end") - print("merge_domaindata end") self.data['root_symbol'].merge_with(otherdata['root_symbol'], docnames, self.env) @@ -7130,6 +7188,11 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: logger.warning(msg, location=docname) else: ourNames[name] = docname + if Symbol.debug_show_tree: + print("\tresult:") + print(self.data['root_symbol'].dump(1)) + print("\tresult end") + print("merge_domaindata end") def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index d820cfe5c5b..52546fb4c7b 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -305,7 +305,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index term['ids'].append(node_id) std = cast(StandardDomain, env.get_domain('std')) - std.note_object('term', termtext.lower(), node_id, location=term) + std.note_object('term', termtext, node_id, location=term) # add an index entry too indexnode = addnodes.index() @@ -565,7 +565,7 @@ class StandardDomain(Domain): # links to tokens in grammar productions 'token': TokenXRefRole(), # links to terms in glossary - 'term': XRefRole(lowercase=True, innernodeclass=nodes.inline, + 'term': XRefRole(innernodeclass=nodes.inline, warn_dangling=True), # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline, diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 570ea9df677..64c1568f61c 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -17,7 +17,7 @@ import warnings from functools import partial, partialmethod from inspect import ( # NOQA - Parameter, isclass, ismethod, ismethoddescriptor, unwrap + Parameter, isclass, ismethod, ismethoddescriptor ) from io import StringIO from typing import Any, Callable, Mapping, List, Tuple @@ -116,6 +116,15 @@ def getargspec(func: Callable) -> Any: kwonlyargs, kwdefaults, annotations) +def unwrap(obj: Any) -> Any: + """Get an original object from wrapped object (wrapped functions).""" + try: + return inspect.unwrap(obj) + except ValueError: + # might be a mock object + return obj + + def unwrap_all(obj: Any) -> Any: """ Get an original object from wrapped object (unwrapping partials, wrapped @@ -217,7 +226,7 @@ def isattributedescriptor(obj: Any) -> bool: return True elif isdescriptor(obj): # non data descriptor - unwrapped = inspect.unwrap(obj) + unwrapped = unwrap(obj) if isfunction(unwrapped) or isbuiltin(unwrapped) or inspect.ismethod(unwrapped): # attribute must not be either function, builtin and method return False diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index fbf161ec038..53053faaf5d 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -412,7 +412,7 @@ def filter(self, record: logging.LogRecord) -> bool: message = record.msg # use record.msg itself if location: - raise SphinxWarning(location + ":" + message) + raise SphinxWarning(location + ":" + str(message)) else: raise SphinxWarning(message) else: diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index aa1d29eb114..0032c18ef38 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -99,7 +99,7 @@ def test_glossary(app): text = (".. glossary::\n" "\n" " term1\n" - " term2\n" + " TERM2\n" " description\n" "\n" " term3 : classifier\n" @@ -114,7 +114,7 @@ def test_glossary(app): assert_node(doctree, ( [glossary, definition_list, ([definition_list_item, ([term, ("term1", index)], - [term, ("term2", + [term, ("TERM2", index)], definition)], [definition_list_item, ([term, ("term3", @@ -127,7 +127,7 @@ def test_glossary(app): assert_node(doctree[0][0][0][0][1], entries=[("single", "term1", "term-term1", "main", None)]) assert_node(doctree[0][0][0][1][1], - entries=[("single", "term2", "term-term2", "main", None)]) + entries=[("single", "TERM2", "term-TERM2", "main", None)]) assert_node(doctree[0][0][0][2], [definition, nodes.paragraph, "description"]) assert_node(doctree[0][0][1][0][1], @@ -143,7 +143,7 @@ def test_glossary(app): # index objects = list(app.env.get_domain("std").get_objects()) assert ("term1", "term1", "term", "index", "term-term1", -1) in objects - assert ("term2", "term2", "term", "index", "term-term2", -1) in objects + assert ("TERM2", "TERM2", "term", "index", "term-TERM2", -1) in objects assert ("term3", "term3", "term", "index", "term-term3", -1) in objects assert ("term4", "term4", "term", "index", "term-term4", -1) in objects diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index 337782d8718..36034cd92b4 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -48,6 +48,14 @@ def test_info_and_warning(app, status, warning): assert 'message5' in warning.getvalue() +def test_Exception(app, status, warning): + logging.setup(app, status, warning) + logger = logging.getLogger(__name__) + + logger.info(Exception) + assert "" in status.getvalue() + + def test_verbosity_filter(app, status, warning): # verbosity = 0: INFO app.verbosity = 0