pyo3_stub_gen/generate/
function.rs

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