pyo3_stub_gen/generate/
function.rs1use 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#[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 pub file: &'static str,
20 pub line: u32,
21 pub column: u32,
22 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 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 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 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 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 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 pub fn resolve_default_modules(&mut self, default_module_name: &str) {
124 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 pub fn fmt_for_module(&self, target_module: &str, f: &mut fmt::Formatter) -> fmt::Result {
152 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 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 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 if let Some(comment) = &type_ignore_comment {
201 write!(f, "{comment}")?;
202 }
203 writeln!(f)?;
204 }
205 writeln!(f)?;
206 Ok(())
207 }
208}