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    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
108    pub is_property: bool,
109    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
110    pub is_readonly: bool,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub deprecated: Option<DeprecatedInfo>,
113}
114
115/// Type expression with separate display and link target
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct DocTypeExpr {
118    /// Display text (stripped of module prefixes): "ClassA"
119    pub display: String,
120    /// Where to link (FQN after Haddock resolution)
121    pub link_target: Option<LinkTarget>,
122    /// Generic parameters (recursively)
123    pub children: Vec<DocTypeExpr>,
124}
125
126/// Default value that may contain type references
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(tag = "kind")]
129pub enum DocDefaultValue {
130    /// Simple literal (no type references): "42", "None", etc.
131    Simple { value: String },
132    /// Expression containing type references: "C.C1", "SomeClass()"
133    Expression(DocDefaultExpression),
134}
135
136/// Default value expression with embedded type references
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct DocDefaultExpression {
139    /// Full expression text: "C.C1"
140    pub display: String,
141    /// Identified type references within expression
142    pub type_refs: Vec<DocTypeRef>,
143}
144
145/// Type reference within a default value expression
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct DocTypeRef {
148    /// Full reference text: "C.C1"
149    pub text: String,
150    /// Character offset in display string
151    pub offset: usize,
152    /// Can link to class or class attribute
153    pub link_target: Option<LinkTarget>,
154}
155
156/// Link target information
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct LinkTarget {
159    /// Fully qualified name (can be "module.Class" or "module.Class.attribute")
160    pub fqn: String,
161    /// Module where the item is documented (after Haddock resolution)
162    pub doc_module: String,
163    /// Item kind
164    pub kind: ItemKind,
165    /// Attribute name for attribute-level links (e.g., "C1" for enum variant)
166    pub attribute: Option<String>,
167}
168
169/// Kind of documented item
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
171pub enum ItemKind {
172    Class,
173    Function,
174    TypeAlias,
175    Variable,
176    Module,
177}
178
179/// Deprecation information
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct DeprecatedInfo {
182    pub since: Option<String>,
183    pub note: Option<String>,
184}
185
186impl DocPackage {
187    /// Normalize all Vec fields for deterministic JSON output
188    pub fn normalize(&mut self) {
189        for module in self.modules.values_mut() {
190            module.normalize();
191        }
192    }
193}
194
195impl DocModule {
196    fn normalize(&mut self) {
197        // Sort items by type priority and then by name for deterministic output
198        self.items.sort_by(|a, b| {
199            use DocItem::*;
200            match (a, b) {
201                (Function(f1), Function(f2)) => f1.name.cmp(&f2.name),
202                (Class(c1), Class(c2)) => c1.name.cmp(&c2.name),
203                (TypeAlias(t1), TypeAlias(t2)) => t1.name.cmp(&t2.name),
204                (Variable(v1), Variable(v2)) => v1.name.cmp(&v2.name),
205                (Module(m1), Module(m2)) => m1.name.cmp(&m2.name),
206                // Sort by type priority if types differ, then by name
207                _ => Self::item_type_priority(a)
208                    .cmp(&Self::item_type_priority(b))
209                    .then_with(|| Self::item_name(a).cmp(Self::item_name(b))),
210            }
211        });
212
213        // Sort submodules alphabetically
214        self.submodules.sort();
215
216        // Normalize nested items
217        for item in &mut self.items {
218            match item {
219                DocItem::Class(c) => c.normalize(),
220                DocItem::Function(f) => f.normalize(),
221                _ => {}
222            }
223        }
224    }
225
226    fn item_type_priority(item: &DocItem) -> u8 {
227        match item {
228            DocItem::Module(_) => 0,
229            DocItem::Class(_) => 1,
230            DocItem::Function(_) => 2,
231            DocItem::TypeAlias(_) => 3,
232            DocItem::Variable(_) => 4,
233        }
234    }
235
236    fn item_name(item: &DocItem) -> &str {
237        match item {
238            DocItem::Function(f) => &f.name,
239            DocItem::Class(c) => &c.name,
240            DocItem::TypeAlias(t) => &t.name,
241            DocItem::Variable(v) => &v.name,
242            DocItem::Module(m) => &m.name,
243        }
244    }
245}
246
247impl DocClass {
248    fn normalize(&mut self) {
249        // Sort methods by name
250        self.methods.sort_by(|a, b| a.name.cmp(&b.name));
251
252        // Sort attributes by name
253        self.attributes.sort_by(|a, b| a.name.cmp(&b.name));
254
255        // Sort bases by display name
256        self.bases.sort_by(|a, b| a.display.cmp(&b.display));
257
258        // Normalize methods
259        for method in &mut self.methods {
260            method.normalize();
261        }
262    }
263}
264
265impl DocFunction {
266    fn normalize(&mut self) {
267        // Sort signatures by parameter count, then by parameter names for consistent overload ordering
268        self.signatures.sort_by(|a, b| {
269            a.parameters.len().cmp(&b.parameters.len()).then_with(|| {
270                // Compare parameter names lexicographically
271                for (p1, p2) in a.parameters.iter().zip(b.parameters.iter()) {
272                    match p1.name.cmp(&p2.name) {
273                        std::cmp::Ordering::Equal => continue,
274                        other => return other,
275                    }
276                }
277                std::cmp::Ordering::Equal
278            })
279        });
280    }
281}