pyo3_macros_backend/
utils.rs1use crate::attributes::{CrateAttribute, RenamingRule};
2use proc_macro2::{Span, TokenStream};
3use quote::{quote, ToTokens};
4use std::ffi::CString;
5use syn::spanned::Spanned;
6use syn::{punctuated::Punctuated, Token};
7
8macro_rules! err_spanned {
10 ($span:expr => $msg:expr) => {
11 syn::Error::new($span, $msg)
12 };
13}
14
15macro_rules! bail_spanned {
17 ($span:expr => $msg:expr) => {
18 return Err(err_spanned!($span => $msg))
19 };
20}
21
22macro_rules! ensure_spanned {
25 ($condition:expr, $span:expr => $msg:expr) => {
26 if !($condition) {
27 bail_spanned!($span => $msg);
28 }
29 };
30 ($($condition:expr, $span:expr => $msg:expr;)*) => {
31 if let Some(e) = [$(
32 (!($condition)).then(|| err_spanned!($span => $msg)),
33 )*]
34 .into_iter()
35 .flatten()
36 .reduce(|mut acc, e| {
37 acc.combine(e);
38 acc
39 }) {
40 return Err(e);
41 }
42 };
43}
44
45pub fn is_python(ty: &syn::Type) -> bool {
47 match unwrap_ty_group(ty) {
48 syn::Type::Path(typath) => typath
49 .path
50 .segments
51 .last()
52 .map(|seg| seg.ident == "Python")
53 .unwrap_or(false),
54 _ => false,
55 }
56}
57
58pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
60 if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
61 let seg = path.segments.last().filter(|s| s.ident == "Option")?;
62 if let syn::PathArguments::AngleBracketed(params) = &seg.arguments {
63 if let syn::GenericArgument::Type(ty) = params.args.first()? {
64 return Some(ty);
65 }
66 }
67 }
68 None
69}
70
71#[derive(Clone)]
73pub struct LitCStr {
74 lit: CString,
75 span: Span,
76 pyo3_path: PyO3CratePath,
77}
78
79impl LitCStr {
80 pub fn new(lit: CString, span: Span, ctx: &Ctx) -> Self {
81 Self {
82 lit,
83 span,
84 pyo3_path: ctx.pyo3_path.clone(),
85 }
86 }
87
88 pub fn empty(ctx: &Ctx) -> Self {
89 Self {
90 lit: CString::new("").unwrap(),
91 span: Span::call_site(),
92 pyo3_path: ctx.pyo3_path.clone(),
93 }
94 }
95}
96
97impl quote::ToTokens for LitCStr {
98 fn to_tokens(&self, tokens: &mut TokenStream) {
99 if cfg!(c_str_lit) {
100 syn::LitCStr::new(&self.lit, self.span).to_tokens(tokens);
101 } else {
102 let pyo3_path = &self.pyo3_path;
103 let lit = self.lit.to_str().unwrap();
104 tokens.extend(quote::quote_spanned!(self.span => #pyo3_path::ffi::c_str!(#lit)));
105 }
106 }
107}
108
109#[derive(Clone)]
115pub struct PythonDoc(PythonDocKind);
116
117#[derive(Clone)]
118enum PythonDocKind {
119 LitCStr(LitCStr),
120 Tokens(TokenStream),
123}
124
125pub fn get_doc(
131 attrs: &[syn::Attribute],
132 mut text_signature: Option<String>,
133 ctx: &Ctx,
134) -> PythonDoc {
135 let Ctx { pyo3_path, .. } = ctx;
136 if let Some(text_signature) = &mut text_signature {
139 text_signature.push_str("\n--\n\n");
140 }
141
142 let mut parts = Punctuated::<TokenStream, Token![,]>::new();
143 let mut first = true;
144 let mut current_part = text_signature.unwrap_or_default();
145
146 for attr in attrs {
147 if attr.path().is_ident("doc") {
148 if let Ok(nv) = attr.meta.require_name_value() {
149 if !first {
150 current_part.push('\n');
151 } else {
152 first = false;
153 }
154 if let syn::Expr::Lit(syn::ExprLit {
155 lit: syn::Lit::Str(lit_str),
156 ..
157 }) = &nv.value
158 {
159 let doc_line = lit_str.value();
162 current_part.push_str(doc_line.strip_prefix(' ').unwrap_or(&doc_line));
163 } else {
164 parts.push(current_part.to_token_stream());
167 current_part.clear();
168 parts.push(nv.value.to_token_stream());
169 }
170 }
171 }
172 }
173
174 if !parts.is_empty() {
175 if !current_part.is_empty() {
177 parts.push(current_part.to_token_stream());
178 }
179
180 let mut tokens = TokenStream::new();
181
182 syn::Ident::new("concat", Span::call_site()).to_tokens(&mut tokens);
183 syn::token::Not(Span::call_site()).to_tokens(&mut tokens);
184 syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
185 parts.to_tokens(tokens);
186 syn::token::Comma(Span::call_site()).to_tokens(tokens);
187 });
188
189 PythonDoc(PythonDocKind::Tokens(
190 quote!(#pyo3_path::ffi::c_str!(#tokens)),
191 ))
192 } else {
193 let docs = CString::new(current_part).unwrap();
195 PythonDoc(PythonDocKind::LitCStr(LitCStr::new(
196 docs,
197 Span::call_site(),
198 ctx,
199 )))
200 }
201}
202
203impl quote::ToTokens for PythonDoc {
204 fn to_tokens(&self, tokens: &mut TokenStream) {
205 match &self.0 {
206 PythonDocKind::LitCStr(lit) => lit.to_tokens(tokens),
207 PythonDocKind::Tokens(toks) => toks.to_tokens(tokens),
208 }
209 }
210}
211
212pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
213 while let syn::Type::Group(g) = ty {
214 ty = &*g.elem;
215 }
216 ty
217}
218
219pub struct Ctx {
220 pub pyo3_path: PyO3CratePath,
222
223 pub output_span: Span,
226}
227
228impl Ctx {
229 pub(crate) fn new(attr: &Option<CrateAttribute>, signature: Option<&syn::Signature>) -> Self {
230 let pyo3_path = match attr {
231 Some(attr) => PyO3CratePath::Given(attr.value.0.clone()),
232 None => PyO3CratePath::Default,
233 };
234
235 let output_span = if let Some(syn::Signature {
236 output: syn::ReturnType::Type(_, output_type),
237 ..
238 }) = &signature
239 {
240 output_type.span()
241 } else {
242 Span::call_site()
243 };
244
245 Self {
246 pyo3_path,
247 output_span,
248 }
249 }
250}
251
252#[derive(Clone)]
253pub enum PyO3CratePath {
254 Given(syn::Path),
255 Default,
256}
257
258impl PyO3CratePath {
259 pub fn to_tokens_spanned(&self, span: Span) -> TokenStream {
260 match self {
261 Self::Given(path) => quote::quote_spanned! { span => #path },
262 Self::Default => quote::quote_spanned! { span => ::pyo3 },
263 }
264 }
265}
266
267impl quote::ToTokens for PyO3CratePath {
268 fn to_tokens(&self, tokens: &mut TokenStream) {
269 match self {
270 Self::Given(path) => path.to_tokens(tokens),
271 Self::Default => quote::quote! { ::pyo3 }.to_tokens(tokens),
272 }
273 }
274}
275
276pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String {
277 use heck::*;
278
279 match rule {
280 RenamingRule::CamelCase => name.to_lower_camel_case(),
281 RenamingRule::KebabCase => name.to_kebab_case(),
282 RenamingRule::Lowercase => name.to_lowercase(),
283 RenamingRule::PascalCase => name.to_upper_camel_case(),
284 RenamingRule::ScreamingKebabCase => name.to_shouty_kebab_case(),
285 RenamingRule::ScreamingSnakeCase => name.to_shouty_snake_case(),
286 RenamingRule::SnakeCase => name.to_snake_case(),
287 RenamingRule::Uppercase => name.to_uppercase(),
288 }
289}
290
291pub(crate) enum IdentOrStr<'a> {
292 Str(&'a str),
293 Ident(syn::Ident),
294}
295
296pub(crate) fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool {
297 has_attribute_with_namespace(attrs, None, &[ident])
298}
299
300pub(crate) fn has_attribute_with_namespace(
301 attrs: &[syn::Attribute],
302 crate_path: Option<&PyO3CratePath>,
303 idents: &[&str],
304) -> bool {
305 let mut segments = vec![];
306 if let Some(c) = crate_path {
307 match c {
308 PyO3CratePath::Given(paths) => {
309 for p in &paths.segments {
310 segments.push(IdentOrStr::Ident(p.ident.clone()));
311 }
312 }
313 PyO3CratePath::Default => segments.push(IdentOrStr::Str("pyo3")),
314 }
315 };
316 for i in idents {
317 segments.push(IdentOrStr::Str(i));
318 }
319
320 attrs.iter().any(|attr| {
321 segments
322 .iter()
323 .eq(attr.path().segments.iter().map(|v| &v.ident))
324 })
325}