Skip to main content

sphinx_ultra/py/
mod.rs

1//! The Python domain (M2 wave 4.5).
2//!
3//! This module root carries the parse-time configuration bundle that the py
4//! object directives, the signature parser and the `fix_parens` xref roles
5//! read. The directives themselves land on top of this file in the next
6//! task; nothing here walks a doctree.
7
8pub mod annotations;
9pub mod arglist;
10pub mod expr;
11pub mod pycode;
12
13#[cfg(test)]
14mod tests;
15
16/// The object-signature / py-domain configuration the *read phase* consumes,
17/// lifted out of [`crate::config::BuildConfig`] so the parser depends on ten
18/// values instead of the whole build configuration.
19///
20/// Sphinx registers these across two files — `sphinx/config.py:248-281` for
21/// the four domain-agnostic keys and `sphinx/directives/__init__.py:370-372`
22/// for `strip_signature_backslash`, `sphinx/domains/python/__init__.py:
23/// 1105-1122` for the `python_*` family — and every one of them is rebuild
24/// category `'env'` (research spec §7), i.e. a read-phase input whose change
25/// invalidates every parsed document.
26///
27/// `modindex_common_prefix` is deliberately *not* here: it is consumed by the
28/// python module index at write time, not by the parse layer.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct PySigConfig {
31    /// `maximum_signature_line_length` (`config.py:279-281`), the global
32    /// wrap threshold shared by the py/js/c/cpp domains. See [`Self::max_len`].
33    pub maximum_signature_line_length: Option<i64>,
34    /// `python_maximum_signature_line_length`
35    /// (`domains/python/__init__.py:1108-1113`), the py-domain override.
36    pub python_maximum_signature_line_length: Option<i64>,
37    /// `python_trailing_comma_in_multi_line_signatures`
38    /// (`domains/python/__init__.py:1114-1119`): the
39    /// `multi_line_trailing_comma` attribute on a wrapped
40    /// `desc_parameterlist`.
41    pub python_trailing_comma_in_multi_line_signatures: bool,
42    /// `python_display_short_literal_types`
43    /// (`domains/python/__init__.py:1120-1122`): render `Literal['a', 'b']`
44    /// as `'a' | 'b'`.
45    pub python_display_short_literal_types: bool,
46    /// `python_use_unqualified_type_names`
47    /// (`domains/python/__init__.py:1105-1107`): emit a
48    /// `pending_xref_condition` pair so the resolver can show the last
49    /// dotted segment of a resolved annotation.
50    pub python_use_unqualified_type_names: bool,
51    /// `toc_object_entries` (`config.py:250`): whether object descriptions
52    /// get `_toc_name`/`_toc_parts` and therefore TOC entries at all.
53    pub toc_object_entries: bool,
54    /// `toc_object_entries_show_parents` (`config.py:251-253`), an
55    /// `ENUM('domain', 'all', 'hide')`. Kept as the raw string because
56    /// Sphinx only *warns* about an unrecognised value and carries it
57    /// through unchanged — see [`crate::config::BuildConfig::validate`].
58    pub toc_object_entries_show_parents: String,
59    /// `add_function_parentheses` (`config.py:248`), consumed by
60    /// `XRefRole.update_title_and_target` for the `fix_parens` roles
61    /// (`:py:func:`, `:py:meth:`) and by the `_toc_name` parens gate.
62    pub add_function_parentheses: bool,
63    /// `add_module_names` (`config.py:249`): whether a signature's module
64    /// prefix is rendered in `desc_addname`.
65    pub add_module_names: bool,
66    /// `strip_signature_backslash` (`directives/__init__.py:370-372`):
67    /// strip backslashes from a signature before it is measured and parsed.
68    pub strip_signature_backslash: bool,
69}
70
71impl Default for PySigConfig {
72    /// Sphinx 9.1.0's own defaults, probe-verified (task-2 brief, "Probe
73    /// outcomes"), so a parse with no project behind it behaves like a
74    /// default Sphinx project.
75    fn default() -> Self {
76        Self {
77            maximum_signature_line_length: None,
78            python_maximum_signature_line_length: None,
79            python_trailing_comma_in_multi_line_signatures: true,
80            python_display_short_literal_types: false,
81            python_use_unqualified_type_names: false,
82            toc_object_entries: true,
83            toc_object_entries_show_parents: "domain".to_string(),
84            add_function_parentheses: true,
85            add_module_names: true,
86            strip_signature_backslash: false,
87        }
88    }
89}
90
91impl PySigConfig {
92    /// The resolved wrap threshold `PyObject.handle_signature` computes
93    /// (`domains/python/_object.py:291-295`):
94    ///
95    /// ```python
96    /// max_len = (
97    ///     self.config.python_maximum_signature_line_length
98    ///     or self.config.maximum_signature_line_length
99    ///     or 0
100    /// )
101    /// ```
102    ///
103    /// `or` tests **truthiness**, not `None`-ness. A py-specific `0` is
104    /// therefore *not* "wrap everything": it is falsy and falls through to
105    /// the global key (research spec §1.2, probe C4 — python=0, global=1
106    /// wraps at 1). The `> max_len > 0` guard at the call site is what makes
107    /// a resolved 0 mean "feature off".
108    pub fn max_len(&self) -> i64 {
109        fn truthy(value: Option<i64>) -> Option<i64> {
110            value.filter(|v| *v != 0)
111        }
112        truthy(self.python_maximum_signature_line_length)
113            .or_else(|| truthy(self.maximum_signature_line_length))
114            .unwrap_or(0)
115    }
116}
117
118impl From<&crate::config::BuildConfig> for PySigConfig {
119    fn from(config: &crate::config::BuildConfig) -> Self {
120        Self {
121            maximum_signature_line_length: config.maximum_signature_line_length,
122            python_maximum_signature_line_length: config.python_maximum_signature_line_length,
123            python_trailing_comma_in_multi_line_signatures: config
124                .python_trailing_comma_in_multi_line_signatures,
125            python_display_short_literal_types: config.python_display_short_literal_types,
126            python_use_unqualified_type_names: config.python_use_unqualified_type_names,
127            toc_object_entries: config.toc_object_entries,
128            toc_object_entries_show_parents: config.toc_object_entries_show_parents.clone(),
129            add_function_parentheses: config.add_function_parentheses,
130            add_module_names: config.add_module_names,
131            strip_signature_backslash: config.strip_signature_backslash,
132        }
133    }
134}