pyo3_derive_backend/
utils.rs1use proc_macro2::Span;
3use proc_macro2::TokenStream;
4use std::fmt::Display;
5
6pub fn print_err(msg: String, t: TokenStream) {
7 println!("Error: {} in '{}'", msg, t.to_string());
8}
9
10pub fn is_python(ty: &syn::Type) -> bool {
12 match ty {
13 syn::Type::Path(ref typath) => typath
14 .path
15 .segments
16 .last()
17 .map(|seg| seg.ident == "Python")
18 .unwrap_or(false),
19 _ => false,
20 }
21}
22
23pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
25 if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
26 let seg = path.segments.last().filter(|s| s.ident == "Option")?;
27 if let syn::PathArguments::AngleBracketed(params) = &seg.arguments {
28 if let syn::GenericArgument::Type(ty) = params.args.first()? {
29 return Some(ty);
30 }
31 }
32 }
33 None
34}
35
36pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
37 attr.path.is_ident("text_signature")
38}
39
40fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
41 attr: &syn::Attribute,
42 python_name: &T,
43) -> syn::Result<Option<syn::LitStr>> {
44 if !is_text_signature_attr(attr) {
45 return Ok(None);
46 }
47 let python_name_str = python_name.to_string();
48 let python_name_str = python_name_str
49 .rsplit('.')
50 .next()
51 .map(str::trim)
52 .filter(|v| !v.is_empty())
53 .ok_or_else(|| {
54 syn::Error::new_spanned(
55 &python_name,
56 format!("failed to parse python name: {}", python_name),
57 )
58 })?;
59 match attr.parse_meta()? {
60 syn::Meta::NameValue(syn::MetaNameValue {
61 lit: syn::Lit::Str(lit),
62 ..
63 }) => {
64 let value = lit.value();
65 if value.starts_with('(') && value.ends_with(')') {
66 Ok(Some(syn::LitStr::new(
67 &(python_name_str.to_owned() + &value),
68 lit.span(),
69 )))
70 } else {
71 Err(syn::Error::new_spanned(
72 lit,
73 "text_signature must start with \"(\" and end with \")\"",
74 ))
75 }
76 }
77 meta => Err(syn::Error::new_spanned(
78 meta,
79 "text_signature must be of the form #[text_signature = \"\"]",
80 )),
81 }
82}
83
84pub fn parse_text_signature_attrs<T: Display + quote::ToTokens + ?Sized>(
85 attrs: &mut Vec<syn::Attribute>,
86 python_name: &T,
87) -> syn::Result<Option<syn::LitStr>> {
88 let mut text_signature = None;
89 let mut attrs_out = Vec::with_capacity(attrs.len());
90 for attr in attrs.drain(..) {
91 if let Some(value) = parse_text_signature_attr(&attr, python_name)? {
92 if text_signature.is_some() {
93 return Err(syn::Error::new_spanned(
94 attr,
95 "text_signature attribute already specified previously",
96 ));
97 } else {
98 text_signature = Some(value);
99 }
100 } else {
101 attrs_out.push(attr);
102 }
103 }
104 *attrs = attrs_out;
105 Ok(text_signature)
106}
107
108pub fn get_doc(
110 attrs: &[syn::Attribute],
111 text_signature: Option<syn::LitStr>,
112 null_terminated: bool,
113) -> syn::Result<syn::LitStr> {
114 let mut doc = String::new();
115 let mut span = Span::call_site();
116
117 if let Some(text_signature) = text_signature {
118 span = text_signature.span();
120 doc.push_str(&text_signature.value());
121 doc.push_str("\n--\n\n");
122 }
123
124 let mut separator = "";
125 let mut first = true;
126
127 for attr in attrs.iter() {
128 if let Ok(syn::Meta::NameValue(ref metanv)) = attr.parse_meta() {
129 if metanv.path.is_ident("doc") {
130 if let syn::Lit::Str(ref litstr) = metanv.lit {
131 if first {
132 first = false;
133 span = litstr.span();
134 }
135 let d = litstr.value();
136 doc.push_str(separator);
137 if d.starts_with(' ') {
138 doc.push_str(&d[1..d.len()]);
139 } else {
140 doc.push_str(&d);
141 };
142 separator = "\n";
143 } else {
144 return Err(syn::Error::new_spanned(metanv, "Invalid doc comment"));
145 }
146 }
147 }
148 }
149
150 if null_terminated {
151 doc.push('\0');
152 }
153
154 Ok(syn::LitStr::new(&doc, span))
155}