pyo3_stub_gen/generate/
method.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::{collections::HashSet, fmt};
6
7pub use crate::type_info::MethodType;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct MethodDef {
12 pub name: &'static str,
13 pub parameters: Parameters,
14 pub r#return: TypeInfo,
15 pub doc: &'static str,
16 pub r#type: MethodType,
17 pub is_async: bool,
18 pub deprecated: Option<DeprecatedInfo>,
19 pub type_ignored: Option<IgnoreTarget>,
20 pub is_overload: bool,
22}
23
24impl Import for MethodDef {
25 fn import(&self) -> HashSet<ImportRef> {
26 let mut import = self.r#return.import.clone();
27 import.extend(self.parameters.import());
28 if self.deprecated.is_some() {
30 import.insert("typing_extensions".into());
31 }
32 import
33 }
34}
35
36impl From<&MethodInfo> for MethodDef {
37 fn from(info: &MethodInfo) -> Self {
38 let doc = if info.doc.is_empty() {
39 ""
40 } else {
41 Box::leak(normalize_docstring(info.doc).into_boxed_str())
42 };
43
44 Self {
45 name: info.name,
46 parameters: Parameters::from_infos(info.parameters),
47 r#return: (info.r#return)(),
48 doc,
49 r#type: info.r#type,
50 is_async: info.is_async,
51 deprecated: info.deprecated.clone(),
52 type_ignored: info.type_ignored,
53 is_overload: info.is_overload,
54 }
55 }
56}
57
58impl fmt::Display for MethodDef {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 let indent = indent();
61 let async_ = if self.is_async { "async " } else { "" };
62
63 if let Some(deprecated) = &self.deprecated {
65 writeln!(f, "{indent}{deprecated}")?;
66 }
67
68 let params_str = if self.parameters.is_empty() {
69 String::new()
70 } else {
71 format!(", {}", self.parameters)
72 };
73
74 match self.r#type {
75 MethodType::Static => {
76 writeln!(f, "{indent}@staticmethod")?;
77 write!(f, "{indent}{async_}def {}({})", self.name, self.parameters)?;
78 }
79 MethodType::Class | MethodType::New => {
80 if self.r#type == MethodType::Class {
81 writeln!(f, "{indent}@classmethod")?;
83 }
84 write!(f, "{indent}{async_}def {}(cls{})", self.name, params_str)?;
85 }
86 MethodType::Instance => {
87 write!(f, "{indent}{async_}def {}(self{})", self.name, params_str)?;
88 }
89 }
90 write!(f, " -> {}:", self.r#return)?;
91
92 let type_ignore_comment = if let Some(target) = &self.type_ignored {
94 match target {
95 IgnoreTarget::All => Some(" # type: ignore".to_string()),
96 IgnoreTarget::Specified(rules) => {
97 let rules_str = rules
98 .iter()
99 .map(|r| {
100 let result = r.parse::<RuleName>().unwrap();
101 if let RuleName::Custom(custom) = &result {
102 log::warn!("Unknown custom rule name '{custom}' used in type ignore. Ensure this is intended.");
103 }
104 result
105 })
106 .join(",");
107 Some(format!(" # type: ignore[{rules_str}]"))
108 }
109 }
110 } else {
111 None
112 };
113
114 let doc = self.doc;
115 if !doc.is_empty() {
116 if let Some(comment) = &type_ignore_comment {
118 write!(f, "{comment}")?;
119 }
120 writeln!(f)?;
121 let double_indent = format!("{indent}{indent}");
122 docstring::write_docstring(f, self.doc, &double_indent)?;
123 } else {
124 write!(f, " ...")?;
125 if let Some(comment) = &type_ignore_comment {
127 write!(f, "{comment}")?;
128 }
129 writeln!(f)?;
130 }
131 Ok(())
132 }
133}
134
135impl MethodDef {
136 pub fn fmt_for_module(
141 &self,
142 target_module: &str,
143 f: &mut fmt::Formatter,
144 indent: &str,
145 ) -> fmt::Result {
146 let async_ = if self.is_async { "async " } else { "" };
147
148 if let Some(deprecated) = &self.deprecated {
150 writeln!(f, "{indent}{deprecated}")?;
151 }
152
153 let params_str = self.parameters.fmt_for_module(target_module);
154 let return_type = self.r#return.qualified_for_module(target_module);
155
156 let params_with_separator = if params_str.is_empty() {
157 String::new()
158 } else {
159 format!(", {}", params_str)
160 };
161
162 match self.r#type {
163 MethodType::Static => {
164 writeln!(f, "{indent}@staticmethod")?;
165 write!(f, "{indent}{async_}def {}({})", self.name, params_str)?;
166 }
167 MethodType::Class | MethodType::New => {
168 if self.r#type == MethodType::Class {
169 writeln!(f, "{indent}@classmethod")?;
171 }
172 write!(
173 f,
174 "{indent}{async_}def {}(cls{})",
175 self.name, params_with_separator
176 )?;
177 }
178 MethodType::Instance => {
179 write!(
180 f,
181 "{indent}{async_}def {}(self{})",
182 self.name, params_with_separator
183 )?;
184 }
185 }
186 write!(f, " -> {}:", return_type)?;
187
188 let type_ignore_comment = if let Some(target) = &self.type_ignored {
190 match target {
191 IgnoreTarget::All => Some(" # type: ignore".to_string()),
192 IgnoreTarget::Specified(rules) => {
193 let rules_str = rules
194 .iter()
195 .map(|r| {
196 let result = r.parse::<RuleName>().unwrap();
197 if let RuleName::Custom(custom) = &result {
198 log::warn!("Unknown custom rule name '{custom}' used in type ignore. Ensure this is intended.");
199 }
200 result
201 })
202 .join(",");
203 Some(format!(" # type: ignore[{rules_str}]"))
204 }
205 }
206 } else {
207 None
208 };
209
210 let doc = self.doc;
211 if !doc.is_empty() {
212 if let Some(comment) = &type_ignore_comment {
214 write!(f, "{comment}")?;
215 }
216 writeln!(f)?;
217 let double_indent = format!("{indent}{indent}");
218 docstring::write_docstring(f, self.doc, &double_indent)?;
219 } else {
220 write!(f, " ...")?;
221 if let Some(comment) = &type_ignore_comment {
223 write!(f, "{comment}")?;
224 }
225 writeln!(f)?;
226 }
227 Ok(())
228 }
229}