diff --git a/CHANGES b/CHANGES index 6d94cdd35..b210eaff7 100644 --- a/CHANGES +++ b/CHANGES @@ -41,16 +41,26 @@ Incompatible changes Deprecated ---------- +* The ``module`` argument of ``sphinx.ext.autosummary.generate. + find_autosummary_in_docstring()`` + Features added -------------- +* LaTeX: Make the ``toplevel_sectioning`` setting optional in LaTeX theme +* #7410: Allow to suppress "circular toctree references detected" warnings using + :confval:`suppress_warnings` +* C, added scope control directives, :rst:dir:`c:namespace`, + :rst:dir:`c:namespace-push`, and :rst:dir:`c:namespace-pop`. +* #7466: autosummary: headings in generated documents are not translated + Bugs fixed ---------- Testing -------- -Release 3.0.1 (in development) +Release 3.0.2 (in development) ============================== Dependencies @@ -65,12 +75,43 @@ Deprecated Features added -------------- +* C, parse attributes and add :confval:`c_id_attributes` + and :confval:`c_paren_attributes` to support user-defined attributes. + Bugs fixed ---------- +* #7461: py domain: fails with IndexError for empty tuple in type annotation +* #7461: autodoc: empty tuple in type annotation is not shown correctly + Testing -------- +Release 3.0.1 (released Apr 11, 2020) +===================================== + +Incompatible changes +-------------------- + +* #7418: std domain: :rst:dir:`term` role becomes case sensitive + +Bugs fixed +---------- + +* #7428: py domain: a reference to class ``None`` emits a nitpicky warning +* #7445: py domain: a return annotation ``None`` in the function signature is + not converted to a hyperlink when using intersphinx +* #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 +* #7451: autodoc: fails with AttributeError when an object returns non-string + object as a ``__doc__`` member +* #7423: crashed when giving a non-string object to logger +* #7479: html theme: Do not include xmlns attribute with HTML 5 doctype +* #7426: html theme: Escape some links in HTML templates + Release 3.0.0 (released Apr 06, 2020) ===================================== diff --git a/Makefile b/Makefile index eea632143..84b2000c2 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ clean-buildfiles: .PHONY: clean-mypyfiles clean-mypyfiles: - rm -rf **/.mypy_cache/ + find . -name '.mypy_cache' -exec rm -rf {} + .PHONY: style-check style-check: diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index ba21649b3..ffd3fe029 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -56,6 +56,12 @@ The following is a list of deprecated interfaces. - 6.0 - ``docutils.utils.smartyquotes`` + * - The ``module`` argument of + ``sphinx.ext.autosummary.generate.find_autosummary_in_docstring()`` + - 3.0 + - 5.0 + - N/A + * - ``desc_signature['first']`` - - 3.0 diff --git a/doc/extdev/i18n.rst b/doc/extdev/i18n.rst index a3232b857..75ef36cc5 100644 --- a/doc/extdev/i18n.rst +++ b/doc/extdev/i18n.rst @@ -35,25 +35,25 @@ In practice, you have to: :func:`sphinx.locale.get_translation` function, usually renamed ``_()``, e.g.: - .. code-block:: python - :caption: src/__init__.py + .. code-block:: python + :caption: src/__init__.py - from sphinx.locale import get_translation + from sphinx.locale import get_translation - MESSAGE_CATALOG_NAME = 'myextension' - _ = get_translation(MESSAGE_CATALOG_NAME) + MESSAGE_CATALOG_NAME = 'myextension' + _ = get_translation(MESSAGE_CATALOG_NAME) - translated_text = _('Hello Sphinx!') + translated_text = _('Hello Sphinx!') #. Set up your extension to be aware of its dedicated translations: - .. code-block:: python - :caption: src/__init__.py + .. code-block:: python + :caption: src/__init__.py - def setup(app): - package_dir = path.abspath(path.dirname(__file__)) - locale_dir = os.path.join(package_dir, 'locales') - app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) + def setup(app): + package_dir = path.abspath(path.dirname(__file__)) + locale_dir = os.path.join(package_dir, 'locales') + app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) #. Generate message catalog template ``*.pot`` file, usually in ``locale/`` source directory, for example via `Babel`_: diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 8c909e315..266da52b7 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/doc/usage/configuration.rst b/doc/usage/configuration.rst index 4d2a54e6c..4be7d4c75 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -313,6 +313,7 @@ General configuration * ``ref.doc`` * ``ref.python`` * ``misc.highlighting_failure`` + * ``toc.circular`` * ``toc.secnum`` * ``epub.unknown_project_files`` * ``autosectionlabel.*`` @@ -672,6 +673,7 @@ documentation on :ref:`intl` for details. Currently supported languages by Sphinx are: * ``ar`` -- Arabic + * ``bg`` -- Bulgarian * ``bn`` -- Bengali * ``ca`` -- Catalan * ``cak`` -- Kaqchikel @@ -690,6 +692,7 @@ documentation on :ref:`intl` for details. * ``fr`` -- French * ``he`` -- Hebrew * ``hi`` -- Hindi + * ``hi_IN`` -- Hindi (India) * ``hr`` -- Croatian * ``hu`` -- Hungarian * ``id`` -- Indonesian @@ -711,9 +714,13 @@ documentation on :ref:`intl` for details. * ``si`` -- Sinhala * ``sk`` -- Slovak * ``sl`` -- Slovenian + * ``sq`` -- Albanian * ``sr`` -- Serbian + * ``sr@latin`` -- Serbian (Latin) + * ``sr_RS`` -- Serbian (Cyrillic) * ``sv`` -- Swedish * ``ta`` -- Tamil + * ``te`` -- Telugu * ``tr`` -- Turkish * ``uk_UA`` -- Ukrainian * ``ur`` -- Urdu @@ -2470,6 +2477,30 @@ Options for the XML builder match any sequence of characters *including* slashes. +.. _c-config: + +Options for the C domain +------------------------ + +.. confval:: c_id_attributes + + A list of strings that the parser additionally should accept as attributes. + This can for example be used when attributes have been ``#define`` d for + portability. + + .. versionadded:: 3.0 + +.. confval:: c_paren_attributes + + A list of strings that the parser additionally should accept as attributes + with one argument. That is, if ``my_align_as`` is in the list, then + ``my_align_as(X)`` is parsed as an attribute for all strings ``X`` that have + balanced braces (``()``, ``[]``, and ``{}``). This can for example be used + when attributes have been ``#define`` d for portability. + + .. versionadded:: 3.0 + + .. _cpp-config: Options for the C++ domain diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 7a987be70..7a89e94e2 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -706,6 +706,77 @@ Inline Expressions and Types .. versionadded:: 3.0 +Namespacing +~~~~~~~~~~~ + +.. versionadded:: 3.1 + +The C language it self does not support namespacing, but it can sometimes be +useful to emulate it in documentation, e.g., to show alternate declarations. +The feature may also be used to document members of structs/unions/enums +separate from their parent declaration. + +The current scope can be changed using three namespace directives. They manage +a stack declarations where ``c:namespace`` resets the stack and changes a given +scope. + +The ``c:namespace-push`` directive changes the scope to a given inner scope +of the current one. + +The ``c:namespace-pop`` directive undoes the most recent +``c:namespace-push`` directive. + +.. rst:directive:: .. c:namespace:: scope specification + + Changes the current scope for the subsequent objects to the given scope, and + resets the namespace directive stack. Note that nested scopes can be + specified by separating with a dot, e.g.:: + + .. c:namespace:: Namespace1.Namespace2.SomeStruct.AnInnerStruct + + All subsequent objects will be defined as if their name were declared with + the scope prepended. The subsequent cross-references will be searched for + starting in the current scope. + + Using ``NULL`` or ``0`` as the scope will change to global scope. + +.. rst:directive:: .. c:namespace-push:: scope specification + + Change the scope relatively to the current scope. For example, after:: + + .. c:namespace:: A.B + + .. c:namespace-push:: C.D + + the current scope will be ``A.B.C.D``. + +.. rst:directive:: .. c:namespace-pop:: + + Undo the previous ``c:namespace-push`` directive (*not* just pop a scope). + For example, after:: + + .. c:namespace:: A.B + + .. c:namespace-push:: C.D + + .. c:namespace-pop:: + + the current scope will be ``A.B`` (*not* ``A.B.C``). + + If no previous ``c:namespace-push`` directive has been used, but only a + ``c:namespace`` directive, then the current scope will be reset to global + scope. That is, ``.. c:namespace:: A.B`` is equivalent to:: + + .. c:namespace:: NULL + + .. c:namespace-push:: A.B + +Configuration Variables +~~~~~~~~~~~~~~~~~~~~~~~ + +See :ref:`c-config`. + + .. _cpp-domain: The C++ Domain diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index 56f2735f0..d638639aa 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -66,19 +66,29 @@ class BuiltInTheme(Theme): class UserTheme(Theme): """A user defined LaTeX theme.""" + REQUIRED_CONFIG_KEYS = ['docclass', 'wrapperclass'] + OPTIONAL_CONFIG_KEYS = ['toplevel_sectioning'] + def __init__(self, name: str, filename: str) -> None: - self.name = name + super().__init__(name) self.config = configparser.RawConfigParser() self.config.read(path.join(filename)) - try: - self.docclass = self.config.get('theme', 'docclass') - self.wrapperclass = self.config.get('theme', 'wrapperclass') - self.toplevel_sectioning = self.config.get('theme', 'toplevel_sectioning') - except configparser.NoSectionError: - raise ThemeError(__('%r doesn\'t have "theme" setting') % filename) - except configparser.NoOptionError as exc: - raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0])) + for key in self.REQUIRED_CONFIG_KEYS: + try: + value = self.config.get('theme', key) + setattr(self, key, value) + except configparser.NoSectionError: + raise ThemeError(__('%r doesn\'t have "theme" setting') % filename) + except configparser.NoOptionError as exc: + raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0])) + + for key in self.OPTIONAL_CONFIG_KEYS: + try: + value = self.config.get('theme', key) + setattr(self, key, value) + except configparser.NoOptionError: + pass class ThemeFactory: diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index cf815bd04..c658572f5 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -35,6 +35,7 @@ from sphinx.util.cfamily import ( char_literal_re ) from sphinx.util.docfields import Field, TypedField +from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode logger = logging.getLogger(__name__) @@ -1990,6 +1991,14 @@ class DefinitionParser(BaseParser): def language(self) -> str: return 'C' + @property + def id_attributes(self): + return self.config.c_id_attributes + + @property + def paren_attributes(self): + return self.config.c_paren_attributes + def _parse_string(self) -> str: if self.current_char != '"': return None @@ -2009,66 +2018,6 @@ class DefinitionParser(BaseParser): self.pos += 1 return self.definition[startPos:self.pos] - def _parse_attribute(self) -> Any: - return None - # self.skip_ws() - # # try C++11 style - # startPos = self.pos - # if self.skip_string_and_ws('['): - # if not self.skip_string('['): - # self.pos = startPos - # else: - # # TODO: actually implement the correct grammar - # arg = self._parse_balanced_token_seq(end=[']']) - # if not self.skip_string_and_ws(']'): - # self.fail("Expected ']' in end of attribute.") - # if not self.skip_string_and_ws(']'): - # self.fail("Expected ']' in end of attribute after [[...]") - # return ASTCPPAttribute(arg) - # - # # try GNU style - # if self.skip_word_and_ws('__attribute__'): - # if not self.skip_string_and_ws('('): - # self.fail("Expected '(' after '__attribute__'.") - # if not self.skip_string_and_ws('('): - # self.fail("Expected '(' after '__attribute__('.") - # attrs = [] - # while 1: - # if self.match(identifier_re): - # name = self.matched_text - # self.skip_ws() - # if self.skip_string_and_ws('('): - # self.fail('Parameterized GNU style attribute not yet supported.') - # attrs.append(ASTGnuAttribute(name, None)) - # # TODO: parse arguments for the attribute - # if self.skip_string_and_ws(','): - # continue - # elif self.skip_string_and_ws(')'): - # break - # else: - # self.fail("Expected identifier, ')', or ',' in __attribute__.") - # if not self.skip_string_and_ws(')'): - # self.fail("Expected ')' after '__attribute__((...)'") - # return ASTGnuAttributeList(attrs) - # - # # try the simple id attributes defined by the user - # for id in self.config.cpp_id_attributes: - # if self.skip_word_and_ws(id): - # return ASTIdAttribute(id) - # - # # try the paren attributes defined by the user - # for id in self.config.cpp_paren_attributes: - # if not self.skip_string_and_ws(id): - # continue - # if not self.skip_string('('): - # self.fail("Expected '(' after user-defined paren-attribute.") - # arg = self._parse_balanced_token_seq(end=[')']) - # if not self.skip_string(')'): - # self.fail("Expected ')' to end user-defined paren-attribute.") - # return ASTParenAttribute(id, arg) - - return None - def _parse_literal(self) -> ASTLiteral: # -> integer-literal # | character-literal @@ -2928,6 +2877,9 @@ class DefinitionParser(BaseParser): assert False return ASTDeclaration(objectType, directiveType, declaration) + def parse_namespace_object(self) -> ASTNestedName: + return self._parse_nested_name() + def parse_xref_object(self) -> ASTNestedName: name = self._parse_nested_name() # if there are '()' left, just skip them @@ -3081,7 +3033,7 @@ class CObject(ObjectDescription): def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration: parentSymbol = self.env.temp_data['c:parent_symbol'] # type: Symbol - parser = DefinitionParser(sig, location=signode) + parser = DefinitionParser(sig, location=signode, config=self.env.config) try: ast = self.parse_definition(parser) parser.assert_end() @@ -3178,6 +3130,95 @@ class CTypeObject(CObject): object_type = 'type' +class CNamespaceObject(SphinxDirective): + """ + This directive is just to tell Sphinx that we're documenting stuff in + namespace foo. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} # type: Dict + + def run(self) -> List[Node]: + rootSymbol = self.env.domaindata['c']['root_symbol'] + if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): + symbol = rootSymbol + stack = [] # type: List[Symbol] + else: + parser = DefinitionParser(self.arguments[0], + location=self.get_source_info()) + try: + name = parser.parse_namespace_object() + parser.assert_end() + except DefinitionError as e: + logger.warning(e, location=self.get_source_info()) + name = _make_phony_error_name() + symbol = rootSymbol.add_name(name) + stack = [symbol] + self.env.temp_data['c:parent_symbol'] = symbol + self.env.temp_data['c:namespace_stack'] = stack + self.env.ref_context['c:parent_key'] = symbol.get_lookup_key() + return [] + + +class CNamespacePushObject(SphinxDirective): + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} # type: Dict + + def run(self) -> List[Node]: + if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): + return [] + parser = DefinitionParser(self.arguments[0], + location=self.get_source_info()) + try: + name = parser.parse_namespace_object() + parser.assert_end() + except DefinitionError as e: + logger.warning(e, location=self.get_source_info()) + name = _make_phony_error_name() + oldParent = self.env.temp_data.get('c:parent_symbol', None) + if not oldParent: + oldParent = self.env.domaindata['c']['root_symbol'] + symbol = oldParent.add_name(name) + stack = self.env.temp_data.get('c:namespace_stack', []) + stack.append(symbol) + self.env.temp_data['c:parent_symbol'] = symbol + self.env.temp_data['c:namespace_stack'] = stack + self.env.ref_context['c:parent_key'] = symbol.get_lookup_key() + return [] + + +class CNamespacePopObject(SphinxDirective): + has_content = False + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} # type: Dict + + def run(self) -> List[Node]: + stack = self.env.temp_data.get('c:namespace_stack', None) + if not stack or len(stack) == 0: + logger.warning("C namespace pop on empty stack. Defaulting to gobal scope.", + location=self.get_source_info()) + stack = [] + else: + stack.pop() + if len(stack) > 0: + symbol = stack[-1] + else: + symbol = self.env.domaindata['c']['root_symbol'] + self.env.temp_data['c:parent_symbol'] = symbol + self.env.temp_data['c:namespace_stack'] = stack + self.env.ref_context['cp:parent_key'] = symbol.get_lookup_key() + return [] + + class CXRefRole(XRefRole): def process_link(self, env: BuildEnvironment, refnode: Element, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: @@ -3214,7 +3255,8 @@ class CExprRole(SphinxRole): def run(self) -> Tuple[List[Node], List[system_message]]: text = self.text.replace('\n', ' ') - parser = DefinitionParser(text, location=self.get_source_info()) + parser = DefinitionParser(text, location=self.get_source_info(), + config=self.env.config) # attempt to mimic XRefRole classes, except that... classes = ['xref', 'c', self.class_type] try: @@ -3256,6 +3298,10 @@ class CDomain(Domain): 'enum': CEnumObject, 'enumerator': CEnumeratorObject, 'type': CTypeObject, + # scope control + 'namespace': CNamespaceObject, + 'namespace-push': CNamespacePushObject, + 'namespace-pop': CNamespacePopObject, } roles = { 'member': CXRefRole(), @@ -3344,7 +3390,7 @@ class CDomain(Domain): def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element) -> Tuple[Element, str]: - parser = DefinitionParser(target, location=node) + parser = DefinitionParser(target, location=node, config=env.config) try: name = parser.parse_xref_object() except DefinitionError as e: @@ -3401,6 +3447,8 @@ class CDomain(Domain): def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(CDomain) + app.add_config_value("c_id_attributes", [], 'env') + app.add_config_value("c_paren_attributes", [], 'env') return { 'version': 'builtin', diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 8787bc9ce..e09a56b06 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -21,7 +21,6 @@ from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.config import Config from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.environment import BuildEnvironment @@ -32,7 +31,7 @@ from sphinx.transforms import SphinxTransform from sphinx.transforms.post_transforms import ReferencesResolver from sphinx.util import logging from sphinx.util.cfamily import ( - NoOldIdError, ASTBaseBase, verify_description_mode, StringifyTransform, + NoOldIdError, ASTBaseBase, ASTAttribute, verify_description_mode, StringifyTransform, BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral, identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re, hex_literal_re, binary_literal_re, float_literal_re, @@ -769,89 +768,6 @@ class ASTNestedName(ASTBase): raise Exception('Unknown description mode: %s' % mode) -################################################################################ -# Attributes -################################################################################ - -class ASTAttribute(ASTBase): - def describe_signature(self, signode: TextElement) -> None: - raise NotImplementedError(repr(self)) - - -class ASTCPPAttribute(ASTAttribute): - def __init__(self, arg: str) -> None: - self.arg = arg - - def _stringify(self, transform: StringifyTransform) -> str: - return "[[" + self.arg + "]]" - - def describe_signature(self, signode: TextElement) -> None: - txt = str(self) - signode.append(nodes.Text(txt, txt)) - - -class ASTGnuAttribute(ASTBase): - def __init__(self, name: str, args: Any) -> None: - self.name = name - self.args = args - - def _stringify(self, transform: StringifyTransform) -> str: - res = [self.name] - if self.args: - res.append('(') - res.append(transform(self.args)) - res.append(')') - return ''.join(res) - - -class ASTGnuAttributeList(ASTAttribute): - def __init__(self, attrs: List[ASTGnuAttribute]) -> None: - self.attrs = attrs - - def _stringify(self, transform: StringifyTransform) -> str: - res = ['__attribute__(('] - first = True - for attr in self.attrs: - if not first: - res.append(', ') - first = False - res.append(transform(attr)) - res.append('))') - return ''.join(res) - - def describe_signature(self, signode: TextElement) -> None: - txt = str(self) - signode.append(nodes.Text(txt, txt)) - - -class ASTIdAttribute(ASTAttribute): - """For simple attributes defined by the user.""" - - def __init__(self, id: str) -> None: - self.id = id - - def _stringify(self, transform: StringifyTransform) -> str: - return self.id - - def describe_signature(self, signode: TextElement) -> None: - signode.append(nodes.Text(self.id, self.id)) - - -class ASTParenAttribute(ASTAttribute): - """For paren attributes defined by the user.""" - - def __init__(self, id: str, arg: str) -> None: - self.id = id - self.arg = arg - - def _stringify(self, transform: StringifyTransform) -> str: - return self.id + '(' + self.arg + ')' - - def describe_signature(self, signode: TextElement) -> None: - txt = str(self) - signode.append(nodes.Text(txt, txt)) - - ################################################################################ # Expressions ################################################################################ @@ -4300,18 +4216,73 @@ class Symbol: 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 +4297,14 @@ class Symbol: # 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": @@ -4608,16 +4583,18 @@ class DefinitionParser(BaseParser): _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename') - def __init__(self, definition: str, *, - location: Union[nodes.Node, Tuple[str, int]], - config: "Config") -> None: - super().__init__(definition, location=location) - self.config = config - @property def language(self) -> str: return 'C++' + @property + def id_attributes(self): + return self.config.cpp_id_attributes + + @property + def paren_attributes(self): + return self.config.cpp_paren_attributes + def _parse_string(self) -> str: if self.current_char != '"': return None @@ -4637,85 +4614,6 @@ class DefinitionParser(BaseParser): self.pos += 1 return self.definition[startPos:self.pos] - def _parse_balanced_token_seq(self, end: List[str]) -> str: - # TODO: add handling of string literals and similar - brackets = {'(': ')', '[': ']', '{': '}'} - startPos = self.pos - symbols = [] # type: List[str] - while not self.eof: - if len(symbols) == 0 and self.current_char in end: - break - if self.current_char in brackets.keys(): - symbols.append(brackets[self.current_char]) - elif len(symbols) > 0 and self.current_char == symbols[-1]: - symbols.pop() - elif self.current_char in ")]}": - self.fail("Unexpected '%s' in balanced-token-seq." % self.current_char) - self.pos += 1 - if self.eof: - self.fail("Could not find end of balanced-token-seq starting at %d." - % startPos) - return self.definition[startPos:self.pos] - - def _parse_attribute(self) -> ASTAttribute: - self.skip_ws() - # try C++11 style - startPos = self.pos - if self.skip_string_and_ws('['): - if not self.skip_string('['): - self.pos = startPos - else: - # TODO: actually implement the correct grammar - arg = self._parse_balanced_token_seq(end=[']']) - if not self.skip_string_and_ws(']'): - self.fail("Expected ']' in end of attribute.") - if not self.skip_string_and_ws(']'): - self.fail("Expected ']' in end of attribute after [[...]") - return ASTCPPAttribute(arg) - - # try GNU style - if self.skip_word_and_ws('__attribute__'): - if not self.skip_string_and_ws('('): - self.fail("Expected '(' after '__attribute__'.") - if not self.skip_string_and_ws('('): - self.fail("Expected '(' after '__attribute__('.") - attrs = [] - while 1: - if self.match(identifier_re): - name = self.matched_text - self.skip_ws() - if self.skip_string_and_ws('('): - self.fail('Parameterized GNU style attribute not yet supported.') - attrs.append(ASTGnuAttribute(name, None)) - # TODO: parse arguments for the attribute - if self.skip_string_and_ws(','): - continue - elif self.skip_string_and_ws(')'): - break - else: - self.fail("Expected identifier, ')', or ',' in __attribute__.") - if not self.skip_string_and_ws(')'): - self.fail("Expected ')' after '__attribute__((...)'") - return ASTGnuAttributeList(attrs) - - # try the simple id attributes defined by the user - for id in self.config.cpp_id_attributes: - if self.skip_word_and_ws(id): - return ASTIdAttribute(id) - - # try the paren attributes defined by the user - for id in self.config.cpp_paren_attributes: - if not self.skip_string_and_ws(id): - continue - if not self.skip_string('('): - self.fail("Expected '(' after user-defined paren-attribute.") - arg = self._parse_balanced_token_seq(end=[')']) - if not self.skip_string(')'): - self.fail("Expected ')' to end user-defined paren-attribute.") - return ASTParenAttribute(id, arg) - - return None - def _parse_literal(self) -> ASTLiteral: # -> integer-literal # | character-literal @@ -7116,7 +7014,6 @@ class CPPDomain(Domain): 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 +7027,11 @@ class CPPDomain(Domain): 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, @@ -7137,8 +7039,7 @@ class CPPDomain(Domain): # add parens again for those that could be functions if typ == 'any' or typ == 'func': target += '()' - parser = DefinitionParser(target, location=node, - config=env.config) + parser = DefinitionParser(target, location=node, config=env.config) try: ast, isShorthand = parser.parse_xref_object() except DefinitionError as e: diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 59c4b4f1e..11bf0ac4d 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -76,8 +76,13 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str), def _parse_annotation(annotation: str) -> List[Node]: """Parse type annotation.""" def make_xref(text: str) -> addnodes.pending_xref: + if text == 'None': + reftype = 'obj' + else: + reftype = 'class' + return pending_xref('', nodes.Text(text), - refdomain='py', reftype='class', reftarget=text) + refdomain='py', reftype=reftype, reftarget=text) def unparse(node: ast.AST) -> List[Node]: if isinstance(node, ast.Attribute): @@ -105,11 +110,16 @@ def _parse_annotation(annotation: str) -> List[Node]: result.append(addnodes.desc_sig_punctuation('', ']')) return result elif isinstance(node, ast.Tuple): - result = [] - for elem in node.elts: - result.extend(unparse(elem)) - result.append(addnodes.desc_sig_punctuation('', ', ')) - result.pop() + if node.elts: + result = [] + for elem in node.elts: + result.extend(unparse(elem)) + result.append(addnodes.desc_sig_punctuation('', ', ')) + result.pop() + else: + result = [addnodes.desc_sig_punctuation('', '('), + addnodes.desc_sig_punctuation('', ')')] + return result else: raise SyntaxError # unsupported syntax @@ -1318,7 +1328,7 @@ def builtin_resolver(app: Sphinx, env: BuildEnvironment, if node.get('refdomain') != 'py': return None - elif node.get('reftype') == 'obj' and node.get('reftarget') == 'None': + elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': return contnode elif node.get('reftype') in ('class', 'exc'): reftarget = node.get('reftarget') diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 590736e77..cae250b2a 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -303,7 +303,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() @@ -563,7 +563,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/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index 2156156c2..1d3320af2 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -152,7 +152,7 @@ class TocTree: logger.warning(__('circular toctree references ' 'detected, ignoring: %s <- %s'), ref, ' <- '.join(parents), - location=ref) + location=ref, type='toc', subtype='circular') continue refdoc = ref toc = self.env.tocs[ref].deepcopy() diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7ec39a40b..f62b0c1a8 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -556,6 +556,9 @@ class Documenter: isattr = False doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings) + if not isinstance(doc, str): + # Ignore non-string __doc__ + doc = None # if the member __doc__ is the same as self's __doc__, it's just # inherited and therefore not the member's doc @@ -1173,7 +1176,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return ret def format_args(self, **kwargs: Any) -> str: - if self.env.config.autodoc_typehints == 'none': + if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) # for classes, the relevant signature is the __init__ method's @@ -1429,7 +1432,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return ret def format_args(self, **kwargs: Any) -> str: - if self.env.config.autodoc_typehints == 'none': + if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) unwrapped = inspect.unwrap(self.object) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 6ff1fda0e..a4045ed28 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -33,7 +33,7 @@ import sphinx.locale from sphinx import __display_version__ from sphinx import package_dir from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.jinja2glue import BuiltinTemplateLoader @@ -120,6 +120,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline + if builder: + if builder.app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(builder.app.translator) # type: ignore + def exists(self, template_name: str) -> bool: """Check if template file exists.""" try: @@ -328,6 +333,10 @@ def find_autosummary_in_docstring(name: str, module: str = None, filename: str = See `find_autosummary_in_lines`. """ + if module: + warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', + RemovedInSphinx50Warning) + try: real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() diff --git a/sphinx/ext/autosummary/templates/autosummary/class.rst b/sphinx/ext/autosummary/templates/autosummary/class.rst index 8861b79a9..0f7d6f32e 100644 --- a/sphinx/ext/autosummary/templates/autosummary/class.rst +++ b/sphinx/ext/autosummary/templates/autosummary/class.rst @@ -8,7 +8,7 @@ .. automethod:: __init__ {% if methods %} - .. rubric:: Methods + .. rubric:: {{ _('Methods') }} .. autosummary:: {% for item in methods %} @@ -19,7 +19,7 @@ {% block attributes %} {% if attributes %} - .. rubric:: Attributes + .. rubric:: {{ _('Attributes') }} .. autosummary:: {% for item in attributes %} diff --git a/sphinx/ext/autosummary/templates/autosummary/module.rst b/sphinx/ext/autosummary/templates/autosummary/module.rst index 6ec89e05e..db3bee8b7 100644 --- a/sphinx/ext/autosummary/templates/autosummary/module.rst +++ b/sphinx/ext/autosummary/templates/autosummary/module.rst @@ -4,7 +4,7 @@ {% block functions %} {% if functions %} - .. rubric:: Functions + .. rubric:: {{ _('Functions') }} .. autosummary:: {% for item in functions %} @@ -15,7 +15,7 @@ {% block classes %} {% if classes %} - .. rubric:: Classes + .. rubric:: {{ _('Classes') }} .. autosummary:: {% for item in classes %} @@ -26,7 +26,7 @@ {% block exceptions %} {% if exceptions %} - .. rubric:: Exceptions + .. rubric:: {{ _('Exceptions') }} .. autosummary:: {% for item in exceptions %} diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index 4d8aa8955..fb2a7152d 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -114,7 +114,10 @@ def unparse(node: ast.AST) -> str: elif isinstance(node, ast.UnaryOp): return "%s %s" % (unparse(node.op), unparse(node.operand)) elif isinstance(node, ast.Tuple): - return ", ".join(unparse(e) for e in node.elts) + if node.elts: + return ", ".join(unparse(e) for e in node.elts) + else: + return "()" elif sys.version_info > (3, 6) and isinstance(node, ast.Constant): # this branch should be placed at last return repr(node.value) diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index cfd7ee38b..26c37b10e 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -14,17 +14,17 @@