pyo3_stub_gen/generate/
member.rs

1use crate::generate::docstring::normalize_docstring;
2use crate::{generate::*, type_info::*, TypeInfo};
3use std::{
4    borrow::Cow,
5    collections::HashSet,
6    fmt::{self},
7};
8
9/// Definition of a class member.
10#[derive(Debug, Clone, PartialEq)]
11pub struct MemberDef {
12    pub name: &'static str,
13    pub r#type: TypeInfo,
14    pub doc: &'static str,
15    pub default: Option<String>,
16    pub deprecated: Option<DeprecatedInfo>,
17}
18
19impl Import for MemberDef {
20    fn import(&self) -> HashSet<ImportRef> {
21        let mut import = self.r#type.import.clone();
22        // Add typing_extensions import if deprecated
23        if self.deprecated.is_some() {
24            import.insert("typing_extensions".into());
25        }
26        import
27    }
28}
29
30impl From<&MemberInfo> for MemberDef {
31    fn from(info: &MemberInfo) -> Self {
32        let doc = if info.doc.is_empty() {
33            ""
34        } else {
35            Box::leak(normalize_docstring(info.doc).into_boxed_str())
36        };
37
38        Self {
39            name: info.name,
40            r#type: (info.r#type)(),
41            doc,
42            default: info.default.map(|f| f()),
43            deprecated: info.deprecated.clone(),
44        }
45    }
46}
47
48impl fmt::Display for MemberDef {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        let indent = indent();
51        // Constants cannot have deprecated decorators in Python syntax
52        // Log a warning if deprecated is present but will be ignored
53        if let Some(_deprecated) = &self.deprecated {
54            log::warn!(
55                "Ignoring #[deprecated] on constant '{}': Python constants cannot have decorators. \
56                Consider using a function instead if deprecation is needed.",
57                self.name
58            );
59        }
60        write!(f, "{indent}{}: {}", self.name, self.r#type)?;
61        if let Some(default) = &self.default {
62            write!(f, " = {default}")?;
63        }
64        writeln!(f)?;
65        docstring::write_docstring(f, self.doc, indent)?;
66        Ok(())
67    }
68}
69
70impl MemberDef {
71    /// Format member with module-qualified type names
72    ///
73    /// This method uses the target module context to qualify type identifiers
74    /// within compound type expressions based on their source modules.
75    pub fn fmt_for_module(
76        &self,
77        target_module: &str,
78        f: &mut fmt::Formatter,
79        indent: &str,
80    ) -> fmt::Result {
81        // Constants cannot have deprecated decorators in Python syntax
82        // Log a warning if deprecated is present but will be ignored
83        if let Some(_deprecated) = &self.deprecated {
84            log::warn!(
85                "Ignoring #[deprecated] on constant '{}': Python constants cannot have decorators. \
86                Consider using a function instead if deprecation is needed.",
87                self.name
88            );
89        }
90        let qualified_type = self.r#type.qualified_for_module(target_module);
91        write!(f, "{indent}{}: {}", self.name, qualified_type)?;
92        if let Some(default) = &self.default {
93            write!(f, " = {default}")?;
94        }
95        writeln!(f)?;
96        docstring::write_docstring(f, self.doc, indent)?;
97        Ok(())
98    }
99}
100
101pub struct GetterDisplay<'a> {
102    pub member: &'a MemberDef,
103    pub target_module: &'a str,
104}
105
106pub struct SetterDisplay<'a> {
107    pub member: &'a MemberDef,
108    pub target_module: &'a str,
109}
110
111impl fmt::Display for GetterDisplay<'_> {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        let indent = indent();
114        let qualified_type = self.member.r#type.qualified_for_module(self.target_module);
115        // Add deprecated decorator if present
116        if let Some(deprecated) = &self.member.deprecated {
117            writeln!(f, "{indent}{deprecated}")?;
118        }
119        write!(
120            f,
121            "{indent}@property\n{indent}def {}(self) -> {}:",
122            self.member.name, qualified_type
123        )?;
124        let doc = if let Some(default) = &self.member.default {
125            if default == "..." {
126                Cow::Borrowed(self.member.doc)
127            } else {
128                Cow::Owned(format!(
129                    "{}\n```python\ndefault = {default}\n```",
130                    self.member.doc
131                ))
132            }
133        } else {
134            Cow::Borrowed(self.member.doc)
135        };
136        if !doc.is_empty() {
137            writeln!(f)?;
138            let double_indent = format!("{indent}{indent}");
139            docstring::write_docstring(f, &doc, &double_indent)
140        } else {
141            writeln!(f, " ...")
142        }
143    }
144}
145
146impl fmt::Display for SetterDisplay<'_> {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        let indent = indent();
149        let qualified_type = self.member.r#type.qualified_for_module(self.target_module);
150        // Write setter decorator first, then deprecated decorator
151        writeln!(f, "{indent}@{}.setter", self.member.name)?;
152        if let Some(deprecated) = &self.member.deprecated {
153            writeln!(f, "{indent}{deprecated}")?;
154        }
155        write!(
156            f,
157            "{indent}def {}(self, value: {}) -> None:",
158            self.member.name, qualified_type
159        )?;
160        let doc = if let Some(default) = &self.member.default {
161            if default == "..." {
162                Cow::Borrowed(self.member.doc)
163            } else {
164                Cow::Owned(format!(
165                    "{}\n```python\ndefault = {default}\n```",
166                    self.member.doc
167                ))
168            }
169        } else {
170            Cow::Borrowed(self.member.doc)
171        };
172        if !doc.is_empty() {
173            writeln!(f)?;
174            let double_indent = format!("{indent}{indent}");
175            docstring::write_docstring(f, &doc, &double_indent)
176        } else {
177            writeln!(f, " ...")
178        }
179    }
180}