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}