use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
use crate::attributes::CrateAttribute;
macro_rules! err_spanned {
($span:expr => $msg:expr) => {
syn::Error::new($span, $msg)
};
}
macro_rules! bail_spanned {
($span:expr => $msg:expr) => {
return Err(err_spanned!($span => $msg))
};
}
macro_rules! ensure_spanned {
($condition:expr, $span:expr => $msg:expr) => {
if !($condition) {
bail_spanned!($span => $msg);
}
}
}
pub fn is_python(ty: &syn::Type) -> bool {
match unwrap_ty_group(ty) {
syn::Type::Path(typath) => typath
.path
.segments
.last()
.map(|seg| seg.ident == "Python")
.unwrap_or(false),
_ => false,
}
}
pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
let seg = path.segments.last().filter(|s| s.ident == "Option")?;
if let syn::PathArguments::AngleBracketed(params) = &seg.arguments {
if let syn::GenericArgument::Type(ty) = params.args.first()? {
return Some(ty);
}
}
}
None
}
#[derive(Clone)]
pub struct PythonDoc(TokenStream);
pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) -> PythonDoc {
if let Some(text_signature) = &mut text_signature {
text_signature.push_str("\n--\n\n");
}
let mut parts = Punctuated::<TokenStream, Token![,]>::new();
let mut first = true;
let mut current_part = text_signature.unwrap_or_default();
for attr in attrs {
if attr.path.is_ident("doc") {
if let Ok(DocArgs {
_eq_token,
token_stream,
}) = syn::parse2(attr.tokens.clone())
{
if !first {
current_part.push('\n');
} else {
first = false;
}
if let Ok(syn::Lit::Str(lit_str)) = syn::parse2(token_stream.clone()) {
let doc_line = lit_str.value();
current_part.push_str(doc_line.strip_prefix(' ').unwrap_or(&doc_line));
} else {
parts.push(current_part.to_token_stream());
current_part.clear();
parts.push(token_stream);
}
}
}
}
if !parts.is_empty() {
if !current_part.is_empty() {
parts.push(current_part.to_token_stream());
}
let mut tokens = TokenStream::new();
syn::Ident::new("concat", Span::call_site()).to_tokens(&mut tokens);
syn::token::Bang(Span::call_site()).to_tokens(&mut tokens);
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
parts.to_tokens(tokens);
syn::token::Comma(Span::call_site()).to_tokens(tokens);
syn::LitStr::new("\0", Span::call_site()).to_tokens(tokens);
});
PythonDoc(tokens)
} else {
current_part.push('\0');
PythonDoc(current_part.to_token_stream())
}
}
impl quote::ToTokens for PythonDoc {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}
struct DocArgs {
_eq_token: syn::Token![=],
token_stream: TokenStream,
}
impl syn::parse::Parse for DocArgs {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let this = Self {
_eq_token: input.parse()?,
token_stream: input.parse()?,
};
ensure_spanned!(input.is_empty(), input.span() => "expected end of doc attribute");
Ok(this)
}
}
pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
if let Some(asyncness) = &sig.asyncness {
bail_spanned!(
asyncness.span() => "`async fn` is not yet supported for Python functions.\n\n\
Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and \
Python. For more information, see https://github.com/PyO3/pyo3/issues/1632"
);
};
Ok(())
}
pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
while let syn::Type::Group(g) = ty {
ty = &*g.elem;
}
ty
}
pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path {
attr.as_ref()
.map(|p| p.value.0.clone())
.unwrap_or_else(|| syn::parse_str("::pyo3").unwrap())
}