pyo3_stub_gen/docgen/
ir.rs

1//! Intermediate representation for documentation generation
2
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6use crate::docgen::config::DocGenConfig;
7
8/// Root documentation package structure
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct DocPackage {
11    pub name: String,
12    pub modules: BTreeMap<String, DocModule>,
13    /// Maps item FQN to the public module where it's exported
14    pub export_map: BTreeMap<String, String>,
15    /// Documentation generation configuration
16    pub config: DocGenConfig,
17}
18
19/// A single module's documentation
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct DocModule {
22    pub name: String,
23    pub doc: String,
24    pub items: Vec<DocItem>,
25    pub submodules: Vec<String>,
26}
27
28/// A reference to a submodule
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct DocSubmodule {
31    pub name: String,
32    pub doc: String,
33    pub fqn: String,
34}
35
36/// A documented item (function, class, type alias, etc.)
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(tag = "kind")]
39pub enum DocItem {
40    Function(DocFunction),
41    Class(DocClass),
42    TypeAlias(DocTypeAlias),
43    Variable(DocVariable),
44    Module(DocSubmodule),
45}
46
47/// A function with all its overload signatures
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct DocFunction {
50    pub name: String,
51    /// MyST-formatted docstring
52    pub doc: String,
53    /// ALL overload cases
54    pub signatures: Vec<DocSignature>,
55    pub is_async: bool,
56    pub deprecated: Option<DeprecatedInfo>,
57}
58
59/// A single function signature
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct DocSignature {
62    pub parameters: Vec<DocParameter>,
63    pub return_type: Option<DocTypeExpr>,
64}
65
66/// A function parameter
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DocParameter {
69    pub name: String,
70    pub type_: DocTypeExpr,
71    pub default: Option<DocDefaultValue>,
72}
73
74/// A type alias definition
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct DocTypeAlias {
77    pub name: String,
78    pub doc: String,
79    pub definition: DocTypeExpr,
80}
81
82/// A module-level variable
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct DocVariable {
85    pub name: String,
86    pub doc: String,
87    pub type_: Option<DocTypeExpr>,
88}
89
90/// A class definition
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct DocClass {
93    pub name: String,
94    pub doc: String,
95    pub bases: Vec<DocTypeExpr>,
96    pub methods: Vec<DocFunction>,
97    pub attributes: Vec<DocAttribute>,
98    pub deprecated: Option<DeprecatedInfo>,
99}
100
101/// A class attribute
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct DocAttribute {
104    pub name: String,
105    pub doc: String,
106    pub type_: Option<DocTypeExpr>,
107}
108
109/// Type expression with separate display and link target
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct DocTypeExpr {
112    /// Display text (stripped of module prefixes): "ClassA"
113    pub display: String,
114    /// Where to link (FQN after Haddock resolution)
115    pub link_target: Option<LinkTarget>,
116    /// Generic parameters (recursively)
117    pub children: Vec<DocTypeExpr>,
118}
119
120/// Default value that may contain type references
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(tag = "kind")]
123pub enum DocDefaultValue {
124    /// Simple literal (no type references): "42", "None", etc.
125    Simple { value: String },
126    /// Expression containing type references: "C.C1", "SomeClass()"
127    Expression(DocDefaultExpression),
128}
129
130/// Default value expression with embedded type references
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct DocDefaultExpression {
133    /// Full expression text: "C.C1"
134    pub display: String,
135    /// Identified type references within expression
136    pub type_refs: Vec<DocTypeRef>,
137}
138
139/// Type reference within a default value expression
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct DocTypeRef {
142    /// Full reference text: "C.C1"
143    pub text: String,
144    /// Character offset in display string
145    pub offset: usize,
146    /// Can link to class or class attribute
147    pub link_target: Option<LinkTarget>,
148}
149
150/// Link target information
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct LinkTarget {
153    /// Fully qualified name (can be "module.Class" or "module.Class.attribute")
154    pub fqn: String,
155    /// Module where the item is documented (after Haddock resolution)
156    pub doc_module: String,
157    /// Item kind
158    pub kind: ItemKind,
159    /// Attribute name for attribute-level links (e.g., "C1" for enum variant)
160    pub attribute: Option<String>,
161}
162
163/// Kind of documented item
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
165pub enum ItemKind {
166    Class,
167    Function,
168    TypeAlias,
169    Variable,
170    Module,
171}
172
173/// Deprecation information
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct DeprecatedInfo {
176    pub since: Option<String>,
177    pub note: Option<String>,
178}
179
180impl DocPackage {
181    /// Normalize all Vec fields for deterministic JSON output
182    pub fn normalize(&mut self) {
183        for module in self.modules.values_mut() {
184            module.normalize();
185        }
186    }
187}
188
189impl DocModule {
190    fn normalize(&mut self) {
191        // Sort items by type priority and then by name for deterministic output
192        self.items.sort_by(|a, b| {
193            use DocItem::*;
194            match (a, b) {
195                (Function(f1), Function(f2)) => f1.name.cmp(&f2.name),
196                (Class(c1), Class(c2)) => c1.name.cmp(&c2.name),
197                (TypeAlias(t1), TypeAlias(t2)) => t1.name.cmp(&t2.name),
198                (Variable(v1), Variable(v2)) => v1.name.cmp(&v2.name),
199                (Module(m1), Module(m2)) => m1.name.cmp(&m2.name),
200                // Sort by type priority if types differ, then by name
201                _ => Self::item_type_priority(a)
202                    .cmp(&Self::item_type_priority(b))
203                    .then_with(|| Self::item_name(a).cmp(Self::item_name(b))),
204            }
205        });
206
207        // Sort submodules alphabetically
208        self.submodules.sort();
209
210        // Normalize nested items
211        for item in &mut self.items {
212            match item {
213                DocItem::Class(c) => c.normalize(),
214                DocItem::Function(f) => f.normalize(),
215                _ => {}
216            }
217        }
218    }
219
220    fn item_type_priority(item: &DocItem) -> u8 {
221        match item {
222            DocItem::Module(_) => 0,
223            DocItem::Class(_) => 1,
224            DocItem::Function(_) => 2,
225            DocItem::TypeAlias(_) => 3,
226            DocItem::Variable(_) => 4,
227        }
228    }
229
230    fn item_name(item: &DocItem) -> &str {
231        match item {
232            DocItem::Function(f) => &f.name,
233            DocItem::Class(c) => &c.name,
234            DocItem::TypeAlias(t) => &t.name,
235            DocItem::Variable(v) => &v.name,
236            DocItem::Module(m) => &m.name,
237        }
238    }
239}
240
241impl DocClass {
242    fn normalize(&mut self) {
243        // Sort methods by name
244        self.methods.sort_by(|a, b| a.name.cmp(&b.name));
245
246        // Sort attributes by name
247        self.attributes.sort_by(|a, b| a.name.cmp(&b.name));
248
249        // Sort bases by display name
250        self.bases.sort_by(|a, b| a.display.cmp(&b.display));
251
252        // Normalize methods
253        for method in &mut self.methods {
254            method.normalize();
255        }
256    }
257}
258
259impl DocFunction {
260    fn normalize(&mut self) {
261        // Sort signatures by parameter count, then by parameter names for consistent overload ordering
262        self.signatures.sort_by(|a, b| {
263            a.parameters.len().cmp(&b.parameters.len()).then_with(|| {
264                // Compare parameter names lexicographically
265                for (p1, p2) in a.parameters.iter().zip(b.parameters.iter()) {
266                    match p1.name.cmp(&p2.name) {
267                        std::cmp::Ordering::Equal => continue,
268                        other => return other,
269                    }
270                }
271                std::cmp::Ordering::Equal
272            })
273        });
274    }
275}