diff --git a/doc/conf.py b/doc/conf.py index 42ceafb5e..9cf2f9b48 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -187,9 +187,16 @@ nitpick_ignore = { ('py:attr', 'srcline'), ('py:class', '_AutodocProcessDocstringListener'), ('py:class', '_ConfigRebuild'), # sphinx.application.Sphinx.add_config_value + # sphinx.application.Sphinx.add_html_math_renderer + ('py:class', '_MathsBlockRenderers'), + # sphinx.application.Sphinx.add_html_math_renderer + ('py:class', '_MathsInlineRenderers'), + ('py:class', '_NodeHandler'), # sphinx.application.Sphinx.add_enumerable_node + ('py:class', '_NodeHandlerPair'), # sphinx.application.Sphinx.add_node ('py:class', '_StrPath'), # sphinx.environment.BuildEnvironment.doc2path ('py:class', 'Element'), # sphinx.domains.Domain ('py:class', 'Documenter'), # sphinx.application.Sphinx.add_autodocumenter + ('py:class', 'Field'), # sphinx.application.Sphinx.add_object_type ('py:class', 'IndexEntry'), # sphinx.domains.IndexEntry ('py:class', 'Inliner'), # sphinx.util.docutils.SphinxRole.inliner ('py:class', 'Lexer'), # sphinx.application.Sphinx.add_lexer @@ -200,10 +207,10 @@ nitpick_ignore = { ('py:class', 'Path'), # sphinx.application.Sphinx.connect ('py:class', 'RoleFunction'), # sphinx.domains.Domain ('py:class', 'RSTState'), # sphinx.utils.parsing.nested_parse_to_nodes - ('py:class', 'Theme'), # sphinx.application.TemplateBridge ('py:class', 'SearchLanguage'), # sphinx.application.Sphinx.add_search_language ('py:class', 'StringList'), # sphinx.utils.parsing.nested_parse_to_nodes ('py:class', 'system_message'), # sphinx.utils.docutils.SphinxDirective + ('py:class', 'Theme'), # sphinx.application.TemplateBridge ('py:class', 'TitleGetter'), # sphinx.domains.Domain ('py:class', 'todo_node'), # sphinx.application.Sphinx.connect ('py:class', 'Transform'), # sphinx.application.Sphinx.add_transform diff --git a/pyproject.toml b/pyproject.toml index 4debbde3f..918aaaa3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,21 +231,14 @@ strict_optional = false [[tool.mypy.overrides]] module = [ - "sphinx.application", - "sphinx.config", "sphinx.domains", "sphinx.domains.c", "sphinx.domains.cpp", - "sphinx.events", "sphinx.ext.autodoc", "sphinx.ext.autodoc.importer", "sphinx.ext.doctest", "sphinx.ext.graphviz", "sphinx.ext.inheritance_diagram", - "sphinx.highlighting", - "sphinx.jinja2glue", - "sphinx.registry", - "sphinx.search", "sphinx.util.docfields", "sphinx.util.docutils", "sphinx.util.inspect", diff --git a/sphinx/application.py b/sphinx/application.py index 7067ff239..191f9935c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -55,9 +55,16 @@ if TYPE_CHECKING: from sphinx.ext.autodoc import Documenter, _AutodocProcessDocstringListener from sphinx.ext.todo import todo_node from sphinx.extension import Extension + from sphinx.registry import ( + _MathsBlockRenderers, + _MathsInlineRenderers, + _NodeHandler, + _NodeHandlerPair, + ) from sphinx.roles import XRefRole from sphinx.search import SearchLanguage from sphinx.theming import Theme + from sphinx.util.docfields import Field from sphinx.util.typing import RoleFunction, TitleGetter @@ -154,7 +161,7 @@ class Sphinx: outdir: str | os.PathLike[str], doctreedir: str | os.PathLike[str], buildername: str, - confoverrides: dict | None = None, + confoverrides: dict[str, Any] | None = None, status: IO[str] | None = sys.stdout, warning: IO[str] | None = sys.stderr, freshenv: bool = False, @@ -792,7 +799,9 @@ class Sphinx: ) -> int: ... # event interface - def connect(self, event: str, callback: Callable, priority: int = 500) -> int: + def connect( + self, event: str, callback: Callable[..., Any], priority: int = 500 + ) -> int: """Register *callback* to be called when *event* is emitted. For details on available core events and the arguments of callback @@ -831,7 +840,7 @@ class Sphinx: event: str, *args: Any, allowed_exceptions: tuple[type[Exception], ...] = (), - ) -> list: + ) -> list[Any]: """Emit *event* and pass *arguments* to the callback functions. Return the return values of all callbacks as a list. Do not emit core @@ -974,7 +983,7 @@ class Sphinx: self, node: type[Element], override: bool = False, - **kwargs: tuple[Callable, Callable | None], + **kwargs: _NodeHandlerPair, ) -> None: """Register a Docutils node class. @@ -1031,7 +1040,7 @@ class Sphinx: figtype: str, title_getter: TitleGetter | None = None, override: bool = False, - **kwargs: tuple[Callable, Callable], + **kwargs: tuple[_NodeHandler, _NodeHandler], ) -> None: """Register a Docutils node class as a numfig target. @@ -1254,10 +1263,11 @@ class Sphinx: directivename: str, rolename: str, indextemplate: str = '', - parse_node: Callable | None = None, + parse_node: Callable[[BuildEnvironment, str, addnodes.desc_signature], str] + | None = None, ref_nodeclass: type[nodes.TextElement] | None = None, objname: str = '', - doc_field_types: Sequence = (), + doc_field_types: Sequence[Field] = (), override: bool = False, ) -> None: """Register a new object type. @@ -1718,8 +1728,8 @@ class Sphinx: def add_html_math_renderer( self, name: str, - inline_renderers: tuple[Callable, Callable | None] | None = None, - block_renderers: tuple[Callable, Callable | None] | None = None, + inline_renderers: _MathsInlineRenderers | None = None, + block_renderers: _MathsBlockRenderers | None = None, ) -> None: """Register a math renderer for HTML. @@ -1838,7 +1848,7 @@ class TemplateBridge: msg = 'must be implemented in subclasses' raise NotImplementedError(msg) - def render_string(self, template: str, context: dict) -> str: + def render_string(self, template: str, context: dict[str, Any]) -> str: """Called by the builder to render a template given as a string with a specified context (a Python dictionary). """ diff --git a/sphinx/config.py b/sphinx/config.py index 8aae0cfde..7c1f64483 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -88,7 +88,7 @@ class ENUM: def __repr__(self) -> str: return f'ENUM({", ".join(sorted(map(repr, self._candidates)))})' - def match(self, value: str | list | tuple) -> bool: + def match(self, value: str | Sequence[str | bool | None]) -> bool: if isinstance(value, frozenset | list | set | tuple): return all(item in self._candidates for item in value) return value in self._candidates @@ -345,7 +345,7 @@ class Config: def read( cls: type[Config], confdir: str | os.PathLike[str], - overrides: dict | None = None, + overrides: dict[str, Any] | None = None, tags: Tags | None = None, ) -> Config: """Create a Config object from configuration file.""" @@ -532,7 +532,7 @@ class Config: return (value for value in self if value.rebuild == rebuild) return (value for value in self if value.rebuild in rebuild) - def __getstate__(self) -> dict: + def __getstate__(self) -> dict[str, Any]: """Obtains serializable data for pickling.""" # remove potentially pickling-problematic values from config __dict__ = { @@ -569,7 +569,7 @@ class Config: return __dict__ - def __setstate__(self, state: dict) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: self._overrides = {} self._options = { name: _Opt(real_value, rebuild, ()) diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index 6e4b6ee51..ac5c10776 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -31,21 +31,18 @@ if TYPE_CHECKING: _IndexEntryCategoryKey, ] _IndexEntryMap: TypeAlias = dict[str, _IndexEntry] - _Index: TypeAlias = list[ + + # Used by ``create_index()`` for 'the real index' + _RealIndexEntry: TypeAlias = tuple[ + str, tuple[ - str, - list[ - tuple[ - str, - tuple[ - _IndexEntryTargets, - list[tuple[str, _IndexEntryTargets]], - _IndexEntryCategoryKey, - ], - ] - ], - ] + _IndexEntryTargets, + list[tuple[str, _IndexEntryTargets]], + _IndexEntryCategoryKey, + ], ] + _RealIndexEntries: TypeAlias = list[_RealIndexEntry] + _Index: TypeAlias = list[tuple[str, _RealIndexEntries]] logger = logging.getLogger(__name__) diff --git a/sphinx/events.py b/sphinx/events.py index b06d938f7..571ad1432 100644 --- a/sphinx/events.py +++ b/sphinx/events.py @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) class EventListener(NamedTuple): id: int - handler: Callable + handler: Callable[..., Any] priority: int @@ -364,7 +364,7 @@ class EventManager: priority: int, ) -> int: ... - def connect(self, name: str, callback: Callable, priority: int) -> int: + def connect(self, name: str, callback: Callable[..., Any], priority: int) -> int: """Connect a handler to specific event.""" if name not in self.events: raise ExtensionError(__('Unknown event name: %s') % name) @@ -386,7 +386,7 @@ class EventManager: name: str, *args: Any, allowed_exceptions: tuple[type[Exception], ...] = (), - ) -> list: + ) -> list[Any]: """Emit a Sphinx event.""" # not every object likes to be repr()'d (think # random stuff coming via autodoc) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index dd1002bf8..5e1277f6c 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -129,7 +129,7 @@ class PygmentsBridge: else: return get_style_by_name(stylename) - def get_formatter(self, **kwargs: Any) -> Formatter: + def get_formatter(self, **kwargs: Any) -> Formatter[str]: kwargs.update(self.formatter_args) return self.formatter(**kwargs) @@ -137,7 +137,7 @@ class PygmentsBridge: self, source: str, lang: str, - opts: dict | None = None, + opts: dict[str, Any] | None = None, force: bool = False, location: Any = None, ) -> Lexer: @@ -180,7 +180,7 @@ class PygmentsBridge: self, source: str, lang: str, - opts: dict | None = None, + opts: dict[str, Any] | None = None, force: bool = False, location: Any = None, **kwargs: Any, diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 80476c8bf..15a3391b4 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -24,6 +24,10 @@ if TYPE_CHECKING: from jinja2.environment import Environment from sphinx.builders import Builder + from sphinx.environment.adapters.indexentries import ( + _RealIndexEntries, + _RealIndexEntry, + ) from sphinx.theming import Theme @@ -57,7 +61,9 @@ def _todim(val: int | str) -> str: return val # type: ignore[return-value] -def _slice_index(values: list, slices: int) -> Iterator[list]: +def _slice_index( + values: _RealIndexEntries, slices: int +) -> Iterator[list[_RealIndexEntry]]: seq = values.copy() length = 0 for value in values: diff --git a/sphinx/registry.py b/sphinx/registry.py index 0cd94a7cd..ce52a03b3 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -23,7 +23,7 @@ from sphinx.util.logging import prefixed_warnings if TYPE_CHECKING: import os from collections.abc import Callable, Iterator, Mapping, Sequence - from typing import Any + from typing import Any, TypeAlias from docutils import nodes from docutils.core import Publisher @@ -32,18 +32,34 @@ if TYPE_CHECKING: from docutils.parsers.rst import Directive from docutils.transforms import Transform + from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config from sphinx.domains import Domain, Index from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc import Documenter + from sphinx.util.docfields import Field from sphinx.util.typing import ( ExtensionMetadata, RoleFunction, TitleGetter, _ExtensionSetupFunc, ) + from sphinx.writers.html5 import HTML5Translator + + # visit/depart function + # the parameters should be (SphinxTranslator, Element) + # or any subtype of either, but mypy rejects this. + _NodeHandler: TypeAlias = Callable[[Any, Any], None] + _NodeHandlerPair: TypeAlias = tuple[_NodeHandler, _NodeHandler | None] + + _MathsRenderer: TypeAlias = Callable[[HTML5Translator, nodes.math], None] + _MathsBlockRenderer: TypeAlias = Callable[[HTML5Translator, nodes.math_block], None] + _MathsInlineRenderers: TypeAlias = tuple[_MathsRenderer, _MathsRenderer | None] + _MathsBlockRenderers: TypeAlias = tuple[ + _MathsBlockRenderer, _MathsBlockRenderer | None + ] logger = logging.getLogger(__name__) @@ -96,9 +112,13 @@ class SphinxComponentRegistry: #: HTML inline and block math renderers #: a dict of name -> tuple of visit function and depart function self.html_inline_math_renderers: dict[ - str, tuple[Callable, Callable | None] + str, + _MathsInlineRenderers, + ] = {} + self.html_block_math_renderers: dict[ + str, + _MathsBlockRenderers, ] = {} - self.html_block_math_renderers: dict[str, tuple[Callable, Callable | None]] = {} #: HTML assets self.html_assets_policy: str = 'per_page' @@ -128,9 +148,7 @@ class SphinxComponentRegistry: #: custom handlers for translators #: a dict of builder name -> dict of node name -> visitor and departure functions - self.translation_handlers: dict[ - str, dict[str, tuple[Callable, Callable | None]] - ] = {} + self.translation_handlers: dict[str, dict[str, _NodeHandlerPair]] = {} #: additional transforms; list of transforms self.transforms: list[type[Transform]] = [] @@ -255,10 +273,11 @@ class SphinxComponentRegistry: directivename: str, rolename: str, indextemplate: str = '', - parse_node: Callable | None = None, + parse_node: Callable[[BuildEnvironment, str, addnodes.desc_signature], str] + | None = None, ref_nodeclass: type[TextElement] | None = None, objname: str = '', - doc_field_types: Sequence = (), + doc_field_types: Sequence[Field] = (), override: bool = False, ) -> None: logger.debug( @@ -372,9 +391,7 @@ class SphinxComponentRegistry: self.translators[name] = translator def add_translation_handlers( - self, - node: type[Element], - **kwargs: tuple[Callable, Callable | None], + self, node: type[Element], **kwargs: _NodeHandlerPair ) -> None: logger.debug('[app] adding translation_handlers: %r, %r', node, kwargs) for builder_name, handlers in kwargs.items(): @@ -482,8 +499,8 @@ class SphinxComponentRegistry: def add_html_math_renderer( self, name: str, - inline_renderers: tuple[Callable, Callable | None] | None, - block_renderers: tuple[Callable, Callable | None] | None, + inline_renderers: _MathsInlineRenderers | None, + block_renderers: _MathsBlockRenderers | None, ) -> None: logger.debug( '[app] adding html_math_renderer: %s, %r, %r', diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 0093b76be..0cc1c6112 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -22,12 +22,23 @@ from sphinx.util.index_entries import split_index_msg if TYPE_CHECKING: from collections.abc import Callable, Iterable - from typing import IO, Any + from typing import Any, Protocol, TypeVar from docutils.nodes import Node from sphinx.environment import BuildEnvironment + _T_co = TypeVar('_T_co', covariant=True) + _T_contra = TypeVar('_T_contra', contravariant=True) + + class _ReadableStream(Protocol[_T_co]): + def read(self, n: int = ..., /) -> _T_co: ... + def readline(self, n: int = ..., /) -> _T_co: ... + + class _WritableStream(Protocol[_T_contra]): + def write(self, s: _T_contra, /) -> object: ... + + _NON_MINIFIED_JS_PATH = Path(package_dir, 'search', 'non-minified-js') _MINIFIED_JS_PATH = Path(package_dir, 'search', 'minified-js') @@ -173,10 +184,10 @@ class _JavaScriptIndex: raise ValueError(msg) return json.loads(data) - def dump(self, data: Any, f: IO[str]) -> None: + def dump(self, data: Any, f: _WritableStream[str]) -> None: f.write(self.dumps(data)) - def load(self, f: IO[str]) -> Any: + def load(self, f: _ReadableStream[str]) -> Any: return self.loads(f.read()) @@ -310,7 +321,7 @@ class IndexBuilder: self.js_scorer_code = '' self.js_splitter_code = '' - def load(self, stream: IO, format: Any) -> None: + def load(self, stream: _ReadableStream[str | bytes], format: Any) -> None: """Reconstruct from frozen data.""" if isinstance(format, str): format = self.formats[format] @@ -346,7 +357,9 @@ class IndexBuilder: self._title_mapping = load_terms(frozen['titleterms']) # no need to load keywords/objtypes - def dump(self, stream: IO, format: Any) -> None: + def dump( + self, stream: _WritableStream[str] | _WritableStream[bytes], format: Any + ) -> None: """Dump the frozen index to a stream.""" if isinstance(format, str): format = self.formats[format] diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 402175a84..6c9c8cfee 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -962,7 +962,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] else: node['classes'].append('field-odd') - def visit_math(self, node: Element, math_env: str = '') -> None: + def visit_math(self, node: nodes.math, math_env: str = '') -> None: self._has_maths_elements = True # see validate_math_renderer @@ -970,14 +970,14 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] visit, _ = self.builder.app.registry.html_inline_math_renderers[name] visit(self, node) - def depart_math(self, node: Element, math_env: str = '') -> None: + def depart_math(self, node: nodes.math, math_env: str = '') -> None: # see validate_math_renderer name: str = self.builder.math_renderer_name # type: ignore[assignment] _, depart = self.builder.app.registry.html_inline_math_renderers[name] if depart: depart(self, node) - def visit_math_block(self, node: Element, math_env: str = '') -> None: + def visit_math_block(self, node: nodes.math_block, math_env: str = '') -> None: self._has_maths_elements = True # see validate_math_renderer @@ -985,7 +985,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] visit, _ = self.builder.app.registry.html_block_math_renderers[name] visit(self, node) - def depart_math_block(self, node: Element, math_env: str = '') -> None: + def depart_math_block(self, node: nodes.math_block, math_env: str = '') -> None: # see validate_math_renderer name: str = self.builder.math_renderer_name # type: ignore[assignment] _, depart = self.builder.app.registry.html_block_math_renderers[name] diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index a4f80c8bb..f30eff2bf 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -2452,14 +2452,14 @@ class LaTeXTranslator(SphinxTranslator): def depart_system_message(self, node: Element) -> None: self.body.append(CR) - def visit_math(self, node: Element) -> None: + def visit_math(self, node: nodes.math) -> None: if self.in_title: self.body.append(r'\protect\(%s\protect\)' % node.astext()) else: self.body.append(r'\(%s\)' % node.astext()) raise nodes.SkipNode - def visit_math_block(self, node: Element) -> None: + def visit_math_block(self, node: nodes.math_block) -> None: if node.get('label'): label = f'equation:{node["docname"]}:{node["label"]}' else: diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 8441c7e44..63900584d 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -476,14 +476,14 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator): # type: ignore[mi def depart_inline(self, node: Element) -> None: pass - def visit_math(self, node: Element) -> None: + def visit_math(self, node: nodes.math) -> None: pass - def depart_math(self, node: Element) -> None: + def depart_math(self, node: nodes.math) -> None: pass - def visit_math_block(self, node: Element) -> None: + def visit_math_block(self, node: nodes.math_block) -> None: self.visit_centered(node) - def depart_math_block(self, node: Element) -> None: + def depart_math_block(self, node: nodes.math_block) -> None: self.depart_centered(node) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index aaf4a3703..3bf8b913f 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1581,11 +1581,11 @@ class TexinfoTranslator(SphinxTranslator): def depart_pending_xref(self, node: Element) -> None: pass - def visit_math(self, node: Element) -> None: + def visit_math(self, node: nodes.math) -> None: self.body.append('@math{' + self.escape_arg(node.astext()) + '}') raise nodes.SkipNode - def visit_math_block(self, node: Element) -> None: + def visit_math_block(self, node: nodes.math_block) -> None: if node.get('label'): self.add_anchor(node['label'], node) self.body.append( diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 6190a64f5..7ef6e808e 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -1327,14 +1327,14 @@ class TextTranslator(SphinxTranslator): self.end_state(wrap=False) raise nodes.SkipNode - def visit_math(self, node: Element) -> None: + def visit_math(self, node: nodes.math) -> None: pass - def depart_math(self, node: Element) -> None: + def depart_math(self, node: nodes.math) -> None: pass - def visit_math_block(self, node: Element) -> None: + def visit_math_block(self, node: nodes.math_block) -> None: self.new_state() - def depart_math_block(self, node: Element) -> None: + def depart_math_block(self, node: nodes.math_block) -> None: self.end_state()