android_xdl_derive 0.0.1

Derive macros for the android_xdl crate.
Documentation
use crate::internals::{Ctxt, ast, attr};
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use std::ffi::CString;
use syn::DeriveInput;

pub fn expand_derive(ast: &DeriveInput) -> syn::Result<TokenStream> {
    let ctxt = Ctxt::new();
    let Some(metadata) = ast::Metadata::from_ast(&ctxt, ast) else {
        return Err(ctxt.check().unwrap_err());
    };
    let ident = &metadata.ident;
    let (impl_generics, ty_generics, where_clause) = metadata.generics.split_for_impl();
    let symbols_load_clause = generate_symbols_load_clause(&ctxt, &metadata);
    let symbols_wrapper = generate_symbols_wrapper(&ctxt, &metadata);
    let tokens = quote! {
        impl #impl_generics ::android_xdl::wrapper::Symbols for #ident #ty_generics #where_clause {
            unsafe fn load_from(lib: &::android_xdl::raw::Library) -> ::std::result::Result<Self, ::android_xdl::Error> {
                Ok(Self { #symbols_load_clause })
            }
        }
        #[allow(dead_code)]
        impl #impl_generics #ident #ty_generics #where_clause {
            #symbols_wrapper
        }
    };
    ctxt.check()?;
    Ok(tokens)
}

fn generate_symbols_load_clause(cx: &Ctxt, meta: &ast::Metadata) -> TokenStream {
    let mut tokens = TokenStream::new();
    for field in &meta.fields {
        match skip_type_group(field.ty) {
            syn::Type::BareFn(_) | syn::Type::Reference(_) | syn::Type::Ptr(_) => {
                let ident = &field.ident;
                let expr = generate_symbols_load_expr(field, meta);
                tokens.extend(quote!(#ident: #expr?,))
            }
            syn::Type::Path(ty) if get_option_inner_type(ty).is_some() => {
                let ident = &field.ident;
                let expr = generate_symbols_load_expr(field, meta);
                tokens.extend(quote!(#ident: #expr.ok(),))
            }
            _ => {
                cx.error_spanned_by(
                    field.ty,
                    format!(
                        "unsupported type: `{}`, expected one of function, reference, pointer",
                        field.ty.into_token_stream()
                    ),
                );
            }
        }
    }
    tokens
}

fn generate_symbols_load_expr(field: &ast::Field, meta: &ast::Metadata) -> TokenStream {
    let symbols = if field.attrs.symbols.is_empty() {
        let implicit = &field.attrs.implicit;
        let ast_implicit = &meta.attrs.implicit;
        let symbol = field.ident.to_string();
        let symbol = implicit
            .rename
            .or(ast_implicit.rename)
            .apply_to_field(&symbol);

        let spec = attr::SymbolSpec {
            name: attr::LitString::String(symbol),
            debug: implicit.debug.or(ast_implicit.debug).unwrap_or(false),
        };
        &[spec]
    } else {
        field.attrs.symbols.as_slice()
    };
    symbols
        .iter()
        .map(|spec| {
            let symbol = match &spec.name {
                attr::LitString::String(name) => {
                    let mut symbol = name.to_owned();
                    if let Some(prefix) = &meta.attrs.symbol.prefix {
                        symbol = format!("{}{}", prefix.trim(), symbol);
                    }
                    if let Some(suffix) = &meta.attrs.symbol.suffix {
                        symbol = format!("{}{}", symbol, suffix.trim());
                    }
                    unsafe { CString::from_vec_unchecked(symbol.into_bytes()) }
                }
                attr::LitString::CString(name) => name.to_owned(),
            };
            let logger = field.attrs.logger.unwrap_or(meta.attrs.logger);
            (symbol, spec.debug, logger)
        })
        .map(|(symbol, debug, logger)| {
            let ident = format_ident!("{}", if debug { "debug_symbol" } else { "symbol" });
            let mut expr = quote! {
                lib.#ident(#symbol, None)
            };
            if logger {
                expr.extend(quote! {
                    .inspect(|symbol| {
                        ::log::trace!("Symbol `{}` loaded at {:p}", #symbol.to_string_lossy(), *symbol)
                    })
                    .inspect_err(|e| ::log::warn!("{}", e))
                });
            }
            expr
        })
        .reduce(|acc, expr| quote!(#acc.or_else(|_| #expr)))
        .unwrap()
}

fn generate_symbols_wrapper(cx: &Ctxt, meta: &ast::Metadata) -> TokenStream {
    let mut tokens = TokenStream::new();
    for field in meta.fields.iter() {
        let wrapper = match skip_type_group(field.ty) {
            syn::Type::BareFn(ty) => generate_function_wrapper(&field.ident, ty),
            syn::Type::Reference(ty) => Some(generate_reference_wrapper(&field.ident, ty)),
            syn::Type::Ptr(_) => None,
            syn::Type::Path(ty) => match get_option_inner_type(ty) {
                Some(syn::Type::BareFn(ty)) => generate_optional_function_wrapper(&field.ident, ty),
                Some(syn::Type::Reference(ty)) => {
                    Some(generate_optional_reference_wrapper(&field.ident, ty))
                }
                Some(syn::Type::Ptr(_)) => None,
                _ => {
                    cx.error_spanned_by(
                        field.ty,
                        format!("unsupported type: `{}`", field.ty.into_token_stream()),
                    );
                    None
                }
            },
            _ => {
                cx.error_spanned_by(
                    field.ty,
                    format!("unsupported type: `{}`", field.ty.into_token_stream()),
                );
                None
            }
        };
        if let Some(wrapper) = wrapper {
            tokens.extend(wrapper);
        }
    }
    tokens
}

fn generate_function_wrapper(ident: &syn::Ident, fn_ty: &syn::TypeBareFn) -> Option<TokenStream> {
    match fn_ty.variadic {
        None => {
            let return_type = &fn_ty.output;
            let unsafety = fn_ty.unsafety;
            let args_name = get_bera_fn_arg_idents(fn_ty);
            let args_type = fn_ty.inputs.iter().map(|arg| &arg.ty);
            Some(quote! {
                #[inline]
                pub #unsafety fn #ident(&self, #(#args_name: #args_type),*) #return_type {
                    #unsafety { (self.#ident)(#(#args_name),*) }
                }
            })
        }
        Some(_) => None,
    }
}

fn generate_reference_wrapper(ident: &syn::Ident, ref_ty: &syn::TypeReference) -> TokenStream {
    let ty = &ref_ty.elem;
    let mut_acc = ref_ty.mutability.map(|_| {
        let mut_ident = format_ident!("mut_{ident}");
        quote! {
            #[inline]
            pub fn #mut_ident(&mut self) -> &mut #ty {
                self.#ident
            }
        }
    });
    quote! {
        #[inline]
        pub fn #ident(&self) -> & #ty {
            self.#ident
        }
        #mut_acc
    }
}

fn generate_optional_function_wrapper(
    ident: &syn::Ident,
    fn_ty: &syn::TypeBareFn,
) -> Option<TokenStream> {
    match fn_ty.variadic {
        None => {
            let return_type = match &fn_ty.output {
                syn::ReturnType::Default => quote!(-> ::core::option::Option<()>),
                syn::ReturnType::Type(_, ty) => quote!(-> ::core::option::Option<#ty>),
            };
            let unsafety = fn_ty.unsafety;
            let args_name = get_bera_fn_arg_idents(fn_ty);
            let args_type = fn_ty.inputs.iter().map(|arg| &arg.ty);
            let has_ident = format_ident!("has_{ident}");
            Some(quote! {
                #[inline]
                pub #unsafety fn #ident(&self, #(#args_name: #args_type),*) #return_type {
                    #unsafety { self.#ident.map(|f| f(#(#args_name),*)) }
                }
                #[inline]
                pub fn #has_ident(&self) -> bool {
                    self.#ident.is_some()
                }
            })
        }
        Some(_) => None,
    }
}

fn generate_optional_reference_wrapper(
    ident: &syn::Ident,
    ref_ty: &syn::TypeReference,
) -> TokenStream {
    let ty = &ref_ty.elem;
    match ref_ty.mutability {
        Some(_) => quote! {
            #[inline]
            pub fn #ident(&mut self) -> ::core::option::Option<&mut #ty> {
                if let Some(&mut ref mut val) = self.#ident {
                    Some(val)
                } else {
                    None
                }
            }
        },
        None => quote! {
            #[inline]
            pub fn #ident(&self) -> ::core::option::Option<& #ty> {
                self.#ident
            }
        },
    }
}

fn get_bera_fn_arg_idents(fn_ty: &syn::TypeBareFn) -> Vec<syn::Ident> {
    let args = fn_ty.inputs.iter().enumerate();
    let args = args.map(|(i, arg)| match &arg.name {
        None => format_ident!("arg{i}"),
        Some((ident, _)) => ident.to_owned(),
    });
    args.collect()
}

fn skip_type_group(ty: &syn::Type) -> &syn::Type {
    match ty {
        syn::Type::Group(group) => skip_type_group(&group.elem),
        _ => ty,
    }
}

fn get_option_inner_type(ty: &syn::TypePath) -> Option<&syn::Type> {
    ty.path
        .segments
        .last()
        .filter(|segment| segment.ident == "Option")
        .map(|segment| &segment.arguments)
        .and_then(|args| match args {
            syn::PathArguments::AngleBracketed(generics) => match generics.args.len() {
                1 => generics.args.first(),
                _ => None,
            },
            _ => None,
        })
        .and_then(|generic| match generic {
            syn::GenericArgument::Type(ty) => Some(ty),
            _ => None,
        })
}