pyo3_stub_gen/generate/
function.rs

1use crate::generate::docstring::normalize_docstring;
2use crate::stub_type::ImportRef;
3use crate::{generate::*, rule_name::RuleName, type_info::*, TypeInfo};
4use itertools::Itertools;
5use std::fmt;
6
7/// Definition of a Python function.
8#[derive(Debug, Clone, PartialEq)]
9pub struct FunctionDef {
10    pub name: &'static str,
11    pub parameters: Parameters,
12    pub r#return: TypeInfo,
13    pub doc: &'static str,
14    pub is_async: bool,
15    pub deprecated: Option<DeprecatedInfo>,
16    pub type_ignored: Option<IgnoreTarget>,
17    pub is_overload: bool,
18    /// Source file location for deterministic ordering
19    pub file: &'static str,
20    pub line: u32,
21    pub column: u32,
22    /// Index for ordering multiple functions from the same macro invocation
23    pub index: usize,
24}
25
26impl Import for FunctionDef {
27    fn import(&self) -> HashSet<ImportRef> {
28        let mut import = self.r#return.import.clone();
29        import.extend(self.parameters.import());
30        // Add typing_extensions import if deprecated
31        if self.deprecated.is_some() {
32            import.insert("typing_extensions".into());
33        }
34        import
35    }
36}
37
38impl From<&PyFunctionInfo> for FunctionDef {
39    fn from(info: &PyFunctionInfo) -> Self {
40        let doc = if info.doc.is_empty() {
41            ""
42        } else {
43            Box::leak(normalize_docstring(info.doc).into_boxed_str())
44        };
45
46        Self {
47            name: info.name,
48            parameters: Parameters::from_infos(info.parameters),
49            r#return: (info.r#return)(),
50            doc,
51            is_async: info.is_async,
52            deprecated: info.deprecated.clone(),
53            type_ignored: info.type_ignored,
54            is_overload: info.is_overload,
55            file: info.file,
56            line: info.line,
57            column: info.column,
58            index: info.index,
59        }
60    }
61}
62
63impl fmt::Display for FunctionDef {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        // Add deprecated decorator if present
66        if let Some(deprecated) = &self.deprecated {
67            writeln!(f, "{deprecated}")?;
68        }
69
70        let async_ = if self.is_async { "async " } else { "" };
71        write!(
72            f,
73            "{async_}def {}({}) -> {}:",
74            self.name, self.parameters, self.r#return
75        )?;
76
77        // Calculate type: ignore comment once
78        let type_ignore_comment = if let Some(target) = &self.type_ignored {
79            match target {
80                IgnoreTarget::All => Some("  # type: ignore".to_string()),
81                IgnoreTarget::Specified(rules) => {
82                    let rules_str = rules
83                        .iter()
84                        .map(|r| {
85                            let result = r.parse::<RuleName>().unwrap();
86                            if let RuleName::Custom(custom) = &result {
87                                log::warn!("Unknown custom rule name '{custom}' used in type ignore. Ensure this is intended.");
88                            }
89                            result
90                        })
91                        .join(",");
92                    Some(format!("  # type: ignore[{rules_str}]"))
93                }
94            }
95        } else {
96            None
97        };
98
99        let doc = self.doc;
100        if !doc.is_empty() {
101            // Add type: ignore comment for functions with docstrings
102            if let Some(comment) = &type_ignore_comment {
103                write!(f, "{comment}")?;
104            }
105            writeln!(f)?;
106            docstring::write_docstring(f, self.doc, indent())?;
107        } else {
108            write!(f, " ...")?;
109            // Add type: ignore comment for functions without docstrings
110            if let Some(comment) = &type_ignore_comment {
111                write!(f, "{comment}")?;
112            }
113            writeln!(f)?;
114        }
115        writeln!(f)?;
116        Ok(())
117    }
118}
119
120impl FunctionDef {
121    /// Resolve all ModuleRef::Default to actual module name.
122    /// Called after construction, before formatting.
123    pub fn resolve_default_modules(&mut self, default_module_name: &str) {
124        // Resolve all parameter types
125        for param in &mut self.parameters.positional_only {
126            param.type_info.resolve_default_module(default_module_name);
127        }
128        for param in &mut self.parameters.positional_or_keyword {
129            param.type_info.resolve_default_module(default_module_name);
130        }
131        for param in &mut self.parameters.keyword_only {
132            param.type_info.resolve_default_module(default_module_name);
133        }
134        if let Some(varargs) = &mut self.parameters.varargs {
135            varargs
136                .type_info
137                .resolve_default_module(default_module_name);
138        }
139        if let Some(varkw) = &mut self.parameters.varkw {
140            varkw.type_info.resolve_default_module(default_module_name);
141        }
142        self.r#return.resolve_default_module(default_module_name);
143    }
144}
145
146impl FunctionDef {
147    /// Format function with module-qualified type names
148    ///
149    /// This method uses the target module context to qualify type identifiers
150    /// within compound type expressions based on their source modules.
151    pub fn fmt_for_module(&self, target_module: &str, f: &mut fmt::Formatter) -> fmt::Result {
152        // Add deprecated decorator if present
153        if let Some(deprecated) = &self.deprecated {
154            writeln!(f, "{deprecated}")?;
155        }
156
157        let async_ = if self.is_async { "async " } else { "" };
158        let params_str = self.parameters.fmt_for_module(target_module);
159        let return_type = self.r#return.qualified_for_module(target_module);
160
161        write!(
162            f,
163            "{async_}def {}({}) -> {}:",
164            self.name, params_str, return_type
165        )?;
166
167        // Calculate type: ignore comment once
168        let type_ignore_comment = if let Some(target) = &self.type_ignored {
169            match target {
170                IgnoreTarget::All => Some("  # type: ignore".to_string()),
171                IgnoreTarget::Specified(rules) => {
172                    let rules_str = rules
173                        .iter()
174                        .map(|r| {
175                            let result = r.parse::<RuleName>().unwrap();
176                            if let RuleName::Custom(custom) = &result {
177                                log::warn!("Unknown custom rule name '{custom}' used in type ignore. Ensure this is intended.");
178                            }
179                            result
180                        })
181                        .join(",");
182                    Some(format!("  # type: ignore[{rules_str}]"))
183                }
184            }
185        } else {
186            None
187        };
188
189        let doc = self.doc;
190        if !doc.is_empty() {
191            // Add type: ignore comment for functions with docstrings
192            if let Some(comment) = &type_ignore_comment {
193                write!(f, "{comment}")?;
194            }
195            writeln!(f)?;
196            docstring::write_docstring(f, self.doc, indent())?;
197        } else {
198            write!(f, " ...")?;
199            // Add type: ignore comment for functions without docstrings
200            if let Some(comment) = &type_ignore_comment {
201                write!(f, "{comment}")?;
202            }
203            writeln!(f)?;
204        }
205        writeln!(f)?;
206        Ok(())
207    }
208}