pyo3_derive_backend/
utils.rs

1// Copyright (c) 2017-present PyO3 Project and Contributors
2use 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
10/// Check if the given type `ty` is `pyo3::Python`.
11pub 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
23/// If `ty` is Option<T>, return `Some(T)`, else None.
24pub 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
108// FIXME(althonos): not sure the docstring formatting is on par here.
109pub 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        // create special doc string lines to set `__text_signature__`
119        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}