Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/diplomat/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 43 kB image not shown  

Quelle  lib.rs

  Sprache: Rust
 

//! The [Diplomat](https://rust-diplomat.github.io/diplomat/) macro crate.
//!
//! This crate provides the `#[diplomat::bridge]` proc macro, which can be used to
//! generate FFI hooks for use with bindings generated by the `diplomat-tool` library.
//!
//! Be sure to also include the [`diplomat_runtime`](https://docs.rs/diplomat_runtime) crate
//! when using the `#[diplomat::bridge]` macro.

use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::*;

use diplomat_core::ast::{self, StdlibOrDiplomat};

mod enum_convert;
mod transparent_convert;

fn cfgs_to_stream(attrs: &[Attribute]) -> proc_macro2::TokenStream {
    attrs
        .iter()
        .fold(quote!(), |prev, attr| quote!(#prev #attr))
}

fn param_ty(param_ty: &ast::TypeName) -> syn::Type {
    match ¶m_ty {
        ast::TypeName::StrReference(lt @ Some(_lt), encoding, _) => {
            // At the param boundary we MUST use FFI-safe diplomat slice types,
            // not Rust stdlib types (which are not FFI-safe and must be converted)
            encoding.get_diplomat_slice_type(lt)
        }
        ast::TypeName::StrReference(None, encoding, _) => encoding.get_diplomat_slice_type(&None),
        ast::TypeName::StrSlice(encoding, _) => {
            // At the param boundary we MUST use FFI-safe diplomat slice types,
            // not Rust stdlib types (which are not FFI-safe and must be converted)
            let inner = encoding.get_diplomat_slice_type(&Some(ast::Lifetime::Anonymous));
            syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatSlice<#inner>)
        }
        ast::TypeName::PrimitiveSlice(ltmt, prim, _) => {
            // At the param boundary we MUST use FFI-safe diplomat slice types,
            // not Rust stdlib types (which are not FFI-safe and must be converted)
            prim.get_diplomat_slice_type(ltmt)
        }
        ast::TypeName::Option(..) if !param_ty.is_ffi_safe() => {
            param_ty.ffi_safe_version().to_syn()
        }
        _ => param_ty.to_syn(),
    }
}

fn param_conversion(
    name: &ast::Ident,
    param_type: &ast::TypeName,
    cast_to: Option<&syn::Type>,
) -> Option<proc_macro2::TokenStream> {
    match ¶m_type {
        // conversion only needed for slices that are specified as Rust types rather than diplomat_runtime types
        ast::TypeName::StrReference(.., StdlibOrDiplomat::Stdlib)
        | ast::TypeName::StrSlice(.., StdlibOrDiplomat::Stdlib)
        | ast::TypeName::PrimitiveSlice(.., StdlibOrDiplomat::Stdlib)
        | ast::TypeName::Result(..) => Some(if let Some(cast_to) = cast_to {
            quote!(let #name#cast_to = #name.into();)
        } else {
            quote!(let #name = #name.into();)
        }),
        // Convert Option<struct/enum/primitive> and DiplomatOption<opaque>
        // simplify the check by just checking is_ffi_safe()
        ast::TypeName::Option(inner, _stdlib) => {
            let mut tokens = TokenStream::new();

            if !param_type.is_ffi_safe() {
                let inner_ty = inner.ffi_safe_version().to_syn();
                tokens.extend(quote!(let #name : Option<#inner_ty> = #name.into();));
            }
            if !inner.is_ffi_safe() {
                tokens.extend(quote!(let #name = #name.map(|v| v.into());));
            }

            if !tokens.is_empty() {
                Some(tokens)
            } else {
                None
            }
        }
        ast::TypeName::Function(in_types, out_type, mutability) => {
            let cb_wrap_ident = &name;
            let mut cb_param_list = vec![];
            let mut cb_params_and_types_list = vec![];
            let mut cb_arg_type_list = vec![];
            let mut all_params_conversion = vec![];
            for (index, in_ty) in in_types.iter().enumerate() {
                let param_ident_str = format!("arg{index}");
                let orig_type = in_ty.to_syn();
                let param_converted_type = param_ty(in_ty);
                if let Some(conversion) = param_conversion(
                    &ast::Ident::from(param_ident_str.clone()),
                    in_ty,
                    Some(¶m_converted_type),
                ) {
                    all_params_conversion.push(conversion);
                }
                let param_ident = Ident::new(¶m_ident_str, Span::call_site());
                cb_arg_type_list.push(param_converted_type);
                cb_params_and_types_list.push(quote!(#param_ident#orig_type));
                cb_param_list.push(param_ident);
            }

            let (ret_type, conversion) = if !out_type.is_ffi_safe() {
                (out_type.ffi_safe_version(), quote! { .into() })
            } else {
                (*out_type.clone(), TokenStream::new())
            };

            let cb_ret_type = ret_type.to_syn();

            let mutability = match mutability {
                ast::Mutability::Immutable => quote!(const),
                ast::Mutability::Mutable => quote!(mut),
            };
            let tokens = quote! {
                let #cb_wrap_ident = move | #(#cb_params_and_types_list,)* | unsafe {
                    #(#all_params_conversion)*
                    let _ = &#cb_wrap_ident// Force the lambda to capture the full object, see https://doc.rust-lang.org/edition-guide/rust-2021/disjoint-capture-in-closures.html
                    std::mem::transmute::<unsafe extern "C" fn (*mut c_void, ...) -> #cb_ret_typeunsafe extern "C" fn (*#mutability c_void, #(#cb_arg_type_list,)*) -> #cb_ret_type>
                        (#cb_wrap_ident.run_callback)(#cb_wrap_ident.data, #(#cb_param_list,)*) #conversion
                };
            };
            Some(parse2(tokens).unwrap())
        }
        _ => None,
    }
}

fn gen_custom_vtable(custom_trait: &ast::Trait, custom_trait_vtable_type: &Ident) -> Item {
    let mut method_sigs: Vec<proc_macro2::TokenStream> = vec![];
    method_sigs.push(quote!(
        pub destructor: Option<unsafe extern "C" fn(*const c_void)>,
        pub size: usize,
        pub alignment: usize,
    ));
    for m in &custom_trait.methods {
        // TODO check that this is the right conversion, it might be the wrong direction
        let mut param_types: Vec<syn::Type> = m.params.iter().map(|p| param_ty(&p.ty)).collect();
        let method_name = Ident::new(&format!("run_{}_callback", m.name), Span::call_site());
        let return_tokens = match &m.output_type {
            Some(ret_ty) => {
                let conv_ret_ty = ret_ty.to_syn();
                quote!( -> #conv_ret_ty)
            }
            None => {
                quote! {}
            }
        };
        param_types.insert(0, syn::parse_quote!(*const c_void));
        method_sigs.push(quote!(
            pub #method_nameunsafe extern "C" fn (#(#param_types),*) #return_tokens,

        ));
    }
    syn::parse_quote!(
        #[repr(C)]
        #[allow(deprecated)]
        pub struct #custom_trait_vtable_type {
            #(#method_sigs)*
        }
    )
}

fn gen_custom_trait_impl(custom_trait: &ast::Trait, custom_trait_struct_name: &Ident) -> Item {
    let mut methods: Vec<Item> = vec![];
    for m in &custom_trait.methods {
        let param_names: Vec<proc_macro2::TokenStream> = m
            .params
            .iter()
            .map(|p| {
                let p_name = &p.name;
                quote! {, #p_name}
            })
            .collect();
        let mut all_params_conversion = vec![];
        let mut param_names_and_types: Vec<proc_macro2::TokenStream> = m
            .params
            .iter()
            .map(|p| {
                let orig_type = p.ty.to_syn();
                let p_ty = param_ty(&p.ty);
                if let Some(conversion) = param_conversion(&p.name.clone(), &p.ty, Some(&p_ty)) {
                    all_params_conversion.push(conversion);
                }
                let p_name = &p.name;
                quote!(#p_name : #orig_type)
            })
            .collect();
        let method_name = &m.name;
        let (return_tokens, end_token) = match &m.output_type {
            Some(ret_ty) => {
                let conv_ret_ty = ret_ty.to_syn();
                (quote!( -> #conv_ret_ty), quote! {})
            }
            None => (quote! {}, quote! {;}),
        };
        if let Some(self_param) = &m.self_param {
            let mut self_modifier = quote! {};
            if let Some((lifetime, mutability)) = &self_param.reference {
                let lifetime_mod = if *lifetime == ast::Lifetime::Anonymous {
                    quote! { & }
                } else {
                    let prime = "'".to_string();
                    let lifetime = lifetime.to_syn();
                    quote! { & #prime #lifetime }
                };
                let mutability_mod = if *mutability == ast::Mutability::Mutable {
                    quote! {mut}
                } else {
                    quote! {}
                };
                self_modifier = quote! { #lifetime_mod #mutability_mod }
            }
            param_names_and_types.insert(0, quote!(#self_modifier self));
        }

        let lifetimes = {
            let lifetime_env = &m.lifetimes;
            if lifetime_env.is_empty() {
                quote! {}
            } else {
                quote! { <#lifetime_env> }
            }
        };
        let runner_method_name =
            Ident::new(&format!("run_{method_name}_callback"), Span::call_site());
        methods.push(syn::Item::Fn(syn::parse_quote!(
            fn #method_name #lifetimes (#(#param_names_and_types),*) #return_tokens {
                unsafe {
                    #(#all_params_conversion)*
                    ((self.vtable).#runner_method_name)(self.data #(#param_names)*)#end_token
                }
            }

        )));
    }
    let trait_name = &custom_trait.name;
    syn::parse_quote!(
        impl #trait_name for #custom_trait_struct_name {
            #(#methods)*
        }
    )
}

fn gen_custom_type_method(strct: &ast::CustomType, m: &ast::Method) -> Item {
    let self_ident = Ident::new(strct.name().as_str(), Span::call_site());
    let method_ident = Ident::new(m.name.as_str(), Span::call_site());
    let extern_ident = Ident::new(m.abi_name.as_str(), Span::call_site());
    gen_custom_function(FuncGen {
        self_ident: Some(self_ident),
        func_ident: method_ident,
        extern_ident,
        self_param: m.self_param.as_ref(),
        params: &m.params,
        return_type: m.return_type.as_ref(),
        lifetime_env: &m.lifetime_env,
        attrs: &m.attrs,
    })
}

struct FuncGen<'a> {
    self_ident: Option<Ident>,
    func_ident: Ident,
    extern_ident: Ident,
    self_param: Option<&'a crate::ast::SelfParam>,
    params: &'a [crate::ast::Param],
    return_type: Option<&'a crate::ast::TypeName>,
    lifetime_env: &'a crate::ast::LifetimeEnv,
    attrs: &'a crate::ast::Attrs,
}

fn gen_custom_function(func_info: FuncGen) -> Item {
    let mut all_params = vec![];

    let mut all_params_conversion = vec![];
    let mut all_params_names = vec![];
    func_info.params.iter().for_each(|p| {
        let ty = param_ty(&p.ty);
        let name = &p.name;
        all_params_names.push(name);
        all_params.push(syn::parse_quote!(#name#ty));
        if let Some(conversion) = param_conversion(&p.name, &p.ty, None) {
            all_params_conversion.push(conversion);
        }
    });

    let this_ident = Pat::Ident(PatIdent {
        attrs: vec![],
        by_ref: None,
        mutability: None,
        ident: Ident::new("this", Span::call_site()),
        subpat: None,
    });

    if let Some(self_param) = func_info.self_param {
        all_params.insert(
            0,
            FnArg::Typed(PatType {
                attrs: vec![],
                pat: Box::new(this_ident.clone()),
                colon_token: syn::token::Colon(Span::call_site()),
                ty: Box::new(self_param.to_typename().to_syn()),
            }),
        );
    }

    let lifetimes = {
        let lifetime_env = func_info.lifetime_env;
        if lifetime_env.is_empty() {
            quote! {}
        } else {
            quote! { <#lifetime_env> }
        }
    };

    let func_ident = func_info.func_ident;

    let method_invocation = if func_info.self_param.is_some() {
        quote! { #this_ident.#func_ident }
    } else if let Some(self_ident) = func_info.self_ident {
        quote! { #self_ident::#func_ident }
    } else {
        quote! { #func_ident }
    };

    let (return_tokens, maybe_into) = if let Some(return_type) = func_info.return_type {
        if let ast::TypeName::Result(ok, err, StdlibOrDiplomat::Stdlib) = return_type {
            let ok = ok.to_syn();
            let err = err.to_syn();
            (
                quote! { -> diplomat_runtime::DiplomatResult<#ok#err> },
                quote! { .into() },
            )
        } else if let ast::TypeName::StrReference(_, _, StdlibOrDiplomat::Stdlib)
        | ast::TypeName::StrSlice(.., StdlibOrDiplomat::Stdlib)
        | ast::TypeName::PrimitiveSlice(_, _, StdlibOrDiplomat::Stdlib) = return_type
        {
            let return_type_syn = return_type.ffi_safe_version().to_syn();
            (quote! { -> #return_type_syn }, quote! { .into() })
        } else if let ast::TypeName::Ordering = return_type {
            let return_type_syn = return_type.to_syn();
            (quote! { -> #return_type_syn }, quote! { as i8 })
        } else if let ast::TypeName::Option(ty, is_std_option) = return_type {
            match ty.as_ref() {
                // pass by reference, Option becomes null
                ast::TypeName::Box(..) | ast::TypeName::Reference(..) => {
                    let return_type_syn = return_type.to_syn();
                    let conversion = if *is_std_option == StdlibOrDiplomat::Stdlib {
                        quote! {}
                    } else {
                        quote! {.into()}
                    };
                    (quote! { -> #return_type_syn }, conversion)
                }
                // anything else goes through DiplomatResult
                _ => {
                    let ty = ty.to_syn();
                    let conversion = if *is_std_option == StdlibOrDiplomat::Stdlib {
                        quote! { .ok_or(()).into() }
                    } else {
                        quote! {}
                    };
                    (
                        quote! { -> diplomat_runtime::DiplomatResult<#ty, ()> },
                        conversion,
                    )
                }
            }
        } else {
            let return_type_syn = return_type.to_syn();
            (quote! { -> #return_type_syn }, quote! {})
        }
    } else {
        (quote! {}, quote! {})
    };

    let write_flushes = func_info
        .params
        .iter()
        .filter(|p| p.is_write())
        .map(|p| {
            let p = &p.name;
            quote! { #p.flush(); }
        })
        .collect::<Vec<_>>();

    let extern_ident = func_info.extern_ident;

    let cfg = cfgs_to_stream(&func_info.attrs.cfg);
    if write_flushes.is_empty() {
        Item::Fn(syn::parse_quote! {
            #[no_mangle]
            #cfg
            #[allow(deprecated)]
            extern "C" fn #extern_ident #lifetimes(#(#all_params),*) #return_tokens {
                #(#all_params_conversion)*
                #method_invocation(#(#all_params_names),*) #maybe_into
            }
        })
    } else {
        Item::Fn(syn::parse_quote! {
            #[no_mangle]
            #cfg
            #[allow(deprecated)]
            extern "C" fn #extern_ident #lifetimes(#(#all_params),*) #return_tokens {
                #(#all_params_conversion)*
                let ret = #method_invocation(#(#all_params_names),*);
                #(#write_flushes)*
                ret #maybe_into
            }
        })
    }
}

struct AttributeInfo {
    repr: bool,
    opaque: bool,
    #[allow(unused)]
    is_out: bool,
}

impl AttributeInfo {
    fn extract(attrs: &mut Vec<Attribute>) -> Self {
        let mut repr = false;
        let mut opaque = false;
        let mut is_out = false;
        attrs.retain(|attr| {
            let ident = &attr.path().segments.iter().next().unwrap().ident;
            if ident == "repr" {
                repr = true;
                // don't actually extract repr attrs, just detect them
                return true;
            } else if ident == "diplomat" {
                if attr.path().segments.len() == 2 {
                    let seg = &attr.path().segments.iter().nth(1).unwrap().ident;
                    if seg == "opaque" {
                        opaque = true;
                        return false;
                    } else if seg == "out" {
                        is_out = true;
                        return false;
                    } else if seg == "rust_link"
                        || seg == "out"
                        || seg == "attr"
                        || seg == "abi_rename"
                        || seg == "demo"
                    {
                        // diplomat-tool reads these, not diplomat::bridge.
                        // throw them away so rustc doesn't complain about unknown attributes
                        return false;
                    } else if seg == "enum_convert" || seg == "transparent_convert" {
                        // diplomat::bridge doesn't read this, but it's handled separately
                        // as an attribute
                        return true;
                    } else if seg == "config" {
                        panic!("#[diplomat::config] is restricted to top level types in lib.rs.");
                    } else {
                        panic!("Only #[diplomat::opaque] and #[diplomat::rust_link] are supported: {seg:?}")
                    }
                } else {
                    panic!("#[diplomat::foo] attrs have a single-segment path name")
                }
            }
            true
        });

        Self {
            repr,
            opaque,
            is_out,
        }
    }
}

fn gen_bridge(mut input: ItemMod) -> ItemMod {
    let module = ast::Module::from_syn(&input, true);
    // Clean out any diplomat attributes so Rust doesn't get mad
    let _attrs = AttributeInfo::extract(&mut input.attrs);
    let (brace, mut new_contents) = input.content.unwrap();

    new_contents.push(parse2(quote! { use diplomat_runtime::*; }).unwrap());
    new_contents.push(parse2(quote! { use core::ffi::c_void; }).unwrap());

    new_contents.iter_mut().for_each(|c| match c {
        Item::Struct(s) => {
            let info = AttributeInfo::extract(&mut s.attrs);

            if !info.opaque {
                // This is validated by HIR, but it's also nice to validate it in the macro so that there
                // are early error messages
                for field in s.fields.iter_mut() {
                    let _attrs = AttributeInfo::extract(&mut field.attrs);
                    let ty = ast::TypeName::from_syn(&field.ty, None);
                    if !ty.is_ffi_safe() {
                        let ffisafe = ty.ffi_safe_version();
                        panic!("Found non-FFI safe type inside struct: {ty}, try {ffisafe}");
                    }
                }
            }

            // Normal opaque types don't need repr(transparent) because the inner type is
            // never referenced. #[diplomat::transparent_convert] handles adding repr(transparent)
            // on its own
            if !info.opaque {
                let repr = if !info.repr {
                    quote!(#[repr(C)])
                } else {
                    quote!()
                };

                *s = syn::parse_quote! {
                    #repr
                    #s
                }
            }
        }

        Item::Enum(e) => {
            let info = AttributeInfo::extract(&mut e.attrs);

            for v in &mut e.variants {
                let info = AttributeInfo::extract(&mut v.attrs);
                if info.opaque {
                    panic!("#[diplomat::opaque] not allowed on enum variants");
                }
            }

            // Normal opaque types don't need repr(transparent) because the inner type is
            // never referenced.
            if !info.opaque {
                *e = syn::parse_quote! {
                    #[repr(C)]
                    #[derive(Clone, Copy)]
                    #e
                };
            }
        }

        Item::Impl(i) => {
            for item in &mut i.items {
                if let syn::ImplItem::Fn(ref mut m) = *item {
                    let info = AttributeInfo::extract(&mut m.attrs);
                    if info.opaque {
                        panic!("#[diplomat::opaque] not allowed on methods")
                    }
                    for i in m.sig.inputs.iter_mut() {
                        let _attrs = match i {
                            syn::FnArg::Receiver(s) => AttributeInfo::extract(&mut s.attrs),
                            syn::FnArg::Typed(t) => AttributeInfo::extract(&mut t.attrs),
                        };
                    }
                }
            }
        }
        _ => (),
    });

    for custom_type in module.declared_types.values() {
        custom_type.methods().iter().for_each(|m| {
            let gen_m = gen_custom_type_method(custom_type, m);
            new_contents.push(gen_m);
        });

        if let ast::CustomType::Opaque(opaque) = custom_type {
            let destroy_ident = Ident::new(opaque.dtor_abi_name.as_str(), Span::call_site());

            let type_ident = custom_type.name().to_syn();

            let (lifetime_defs, lifetimes) = if let Some(lifetime_env) = custom_type.lifetimes() {
                (
                    quote! { <#lifetime_env> },
                    lifetime_env.lifetimes_to_tokens(),
                )
            } else {
                (quote! {}, quote! {})
            };

            let cfg = cfgs_to_stream(&custom_type.attrs().cfg);

            // for now, body is empty since all we need to do is drop the box
            // TODO(#13): change to take a `*mut` and handle DST boxes appropriately
            new_contents.push(Item::Fn(syn::parse_quote! {
                #[no_mangle]
                #cfg
                #[allow(deprecated)]
                extern "C" fn #destroy_ident #lifetime_defs(this: Box<#type_ident #lifetimes>) {}
            }));
        }
    }

    for custom_trait in module.declared_traits.values() {
        let custom_trait_name = Ident::new(
            &format!("DiplomatTraitStruct_{}", custom_trait.name),
            Span::call_site(),
        );
        let custom_trait_vtable_type =
            Ident::new(&format!("{}_VTable", custom_trait.name), Span::call_site());

        // vtable
        new_contents.push(gen_custom_vtable(custom_trait, &custom_trait_vtable_type));

        // trait struct
        new_contents.push(syn::parse_quote! {
            #[repr(C)]
            pub struct #custom_trait_name {
                data: *const c_void,
                pub vtable: #custom_trait_vtable_type,
            }
        });
        if custom_trait.is_send {
            new_contents.push(syn::parse_quote! {
                unsafe impl std::marker::Send for #custom_trait_name {}
            });
        }
        if custom_trait.is_sync {
            new_contents.push(syn::parse_quote! {
                unsafe impl std::marker::Sync for #custom_trait_name {}
            });
        }

        // trait struct wrapper for all methods
        new_contents.push(gen_custom_trait_impl(custom_trait, &custom_trait_name));

        // destructor
        new_contents.push(syn::parse_quote! {
            impl Drop for #custom_trait_name {
                fn drop(&mut self) {
                    if let Some(destructor) = self.vtable.destructor {
                        unsafe {
                            (destructor)(self.data);
                        }
                    }
                }
            }
        })
    }

    for func in module.declared_functions.values() {
        let func_ident = Ident::new(func.name.as_str(), Span::call_site());
        let extern_ident = Ident::new(func.abi_name.as_str(), Span::call_site());
        new_contents.push(gen_custom_function(FuncGen {
            self_ident: None,
            func_ident,
            extern_ident,
            self_param: None,
            params: &func.params,
            return_type: func.output_type.as_ref(),
            lifetime_env: &func.lifetimes,
            attrs: &func.attrs,
        }))
    }

    ItemMod {
        attrs: input.attrs,
        vis: input.vis,
        mod_token: input.mod_token,
        ident: input.ident,
        content: Some((brace, new_contents)),
        semi: input.semi,
        unsafety: None,
    }
}

/// Mark a module to be exposed through Diplomat-generated FFI.
#[proc_macro_attribute]
pub fn bridge(
    _attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let expanded = gen_bridge(parse_macro_input!(input));
    proc_macro::TokenStream::from(expanded.to_token_stream())
}

// Config is done in [`diplomat_tool::gen`], so we just set things to be ignored here.
#[proc_macro_attribute]
pub fn config(
    _attr: proc_macro::TokenStream,
    _input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    "".parse().unwrap()
}

/// Generate From and Into implementations for a Diplomat enum
///
/// This is invoked as `#[diplomat::enum_convert(OtherEnumName)]`
/// on a Diplomat enum. It will assume the other enum has exactly the same variants
/// and generate From and Into implementations using those. In case that enum is `#[non_exhaustive]`,
/// you may use `#[diplomat::enum_convert(OtherEnumName, needs_wildcard)]` to generate a panicky wildcard
/// branch. It is up to the library author to ensure the enums are kept in sync. You may use the `#[non_exhaustive_omitted_patterns]`
/// lint to enforce this.
#[proc_macro_attribute]
pub fn enum_convert(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // proc macros handle compile errors by using special error tokens.
    // In case of an error, we don't want the original code to go away too
    // (otherwise that will cause more errors) so we hold on to it and we tack it in
    // with no modifications below
    let input_cached: proc_macro2::TokenStream = input.clone().into();
    let expanded =
        enum_convert::gen_enum_convert(parse_macro_input!(attr), parse_macro_input!(input));

    let full = quote! {
        #expanded
        #input_cached
    };
    proc_macro::TokenStream::from(full.to_token_stream())
}

/// Generate conversions from inner types for opaque Diplomat types with a single field
///
/// This is invoked as `#[diplomat::transparent_convert]`
/// on an opaque Diplomat type. It will add `#[repr(transparent)]` and implement `pub(crate) fn transparent_convert()`
/// which allows constructing an `&Self` from a reference to the inner field.
#[proc_macro_attribute]
pub fn transparent_convert(
    _attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // proc macros handle compile errors by using special error tokens.
    // In case of an error, we don't want the original code to go away too
    // (otherwise that will cause more errors) so we hold on to it and we tack it in
    // with no modifications below
    let input_cached: proc_macro2::TokenStream = input.clone().into();
    let expanded = transparent_convert::gen_transparent_convert(parse_macro_input!(input));

    let full = quote! {
        #expanded
        #[repr(transparent)]
        #input_cached
    };
    proc_macro::TokenStream::from(full.to_token_stream())
}

#[proc_macro_attribute]
pub fn macro_rules(
    _attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // proc macros handle compile errors by using special error tokens.
    // In case of an error, we don't want the original code to go away too
    // (otherwise that will cause more errors) so we hold on to it and we tack it in
    // with no modifications below
    let input_cached: proc_macro2::TokenStream = input.clone().into();
    let expanded = diplomat_core::ast::MacroDef::validate(parse_macro_input!(input));
    let full = quote! {
        #expanded
        #input_cached
    };
    proc_macro::TokenStream::from(full.to_token_stream())
}

// Expose attributes for macro_rules to parse:
macro_rules! expose_attrs {
    ($($attr_name:ident),+) => {
        $(
            #[proc_macro_attribute]
            pub fn $attr_name(
                _attr: proc_macro::TokenStream,
                input: proc_macro::TokenStream,
            ) -> proc_macro::TokenStream {
                input
            }
        )*
    }
}

expose_attrs! {opaque, attr, demo}

#[cfg(test)]
mod tests {

    use proc_macro2::TokenStream;
    use quote::ToTokens;
    use syn::parse_quote;

    use super::gen_bridge;

    fn pretty_print_code(tokens: TokenStream) -> String {
        let item = syn::parse2(tokens).unwrap();
        let file = syn::File {
            attrs: vec![],
            items: vec![item],
            shebang: None,
        };

        prettyplease::unparse(&file)
    }

    #[test]
    fn method_taking_str() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn from_str(s: &DiplomatStr) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn slices() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    use diplomat_runtime::{DiplomatStr, DiplomatStr16, DiplomatByte, DiplomatOwnedSlice,
                                           DiplomatOwnedStr16Slice, DiplomatOwnedStrSlice, DiplomatOwnedUTF8StrSlice,
                                           DiplomatSlice, DiplomatSliceMut, DiplomatStr16Slice, DiplomatStrSlice, DiplomatUtf8StrSlice};
                    struct Foo<'a> {
                        a: DiplomatSlice<'a, u8>,
                        b: DiplomatSlice<'a, u16>,
                        c: DiplomatUtf8StrSlice<'a>,
                        d: DiplomatStrSlice<'a>,
                        e: DiplomatStr16Slice<'a>,
                        f: DiplomatSlice<'a, DiplomatByte>,
                    }

                    impl Foo {
                        pub fn make(a: &'a [u8], b: &'a [u16], c: &'a str, d: &'a DiplomatStr, e: &'a DiplomatStr16, f: &'a [DiplomatByte]) -> Self {
                            Foo {
                                a, b, c, d, e, f,
                            }
                        }
                        pub fn make_runtime_types(a: DiplomatSlice<'a, u8>, b: DiplomatSlice<'a, u16>, c: DiplomatUtf8StrSlice<'a>, d: DiplomatStrSlice<'a>, e: DiplomatStr16Slice<'a>, f: DiplomatSlice<'a, DiplomatByte>) -> Self {
                            Foo {
                                a: a.into(),
                                b: b.into(),
                                c: c.into(),
                                d: d.into(),
                                e: e.into(),
                                f: f.into(),
                            }
                        }
                        pub fn boxes(a: Box<[u8]>, b: Box<[u16]>, c: Box<str>, d: Box<DiplomatStr>, e: Box<DiplomatStr16>, f: Box<[DiplomatByte]>) -> Self {
                            unimplemented!()
                        }
                        pub fn boxes_runtime_types(a: DiplomatOwnedSlice<u8>, b: DiplomatOwnedSlice<u16>, c: DiplomatOwnedUTF8StrSlice, d: DiplomatOwnedStrSlice, e: DiplomatOwnedStr16Slice, f: DiplomatOwnedSlice<DiplomatByte>) -> Self {
                            unimplemented!()
                        }
                        pub fn a(self) -> &[u8] {
                            self.a
                        }
                        pub fn b(self) -> &[u16] {
                            self.b
                        }
                        pub fn c(self) -> &str {
                            self.c
                        }
                        pub fn d(self) -> &DiplomatStr {
                            self.d
                        }
                        pub fn e(self) -> &DiplomatStr16 {
                            self.e
                        }
                        pub fn f(self) -> &[DiplomatByte] {
                            self.f
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn method_taking_slice() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn from_slice(s: &[f64]) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn method_taking_mutable_slice() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn fill_slice(s: &mut [f64]) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn method_taking_owned_slice() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn fill_slice(s: Box<[u16]>) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn method_taking_owned_str() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn something_with_str(s: Box<str>) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn mod_with_enum() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    enum Abc {
                        A,
                        B = 123,
                    }

                    impl Abc {
                        pub fn do_something(&self) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn mod_with_write_result() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn to_string(&self, to: &mut DiplomatWrite) -> Result<(), ()> {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn mod_with_rust_result() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        pub fn bar(&self) -> Result<(), ()> {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn multilevel_borrows() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    #[diplomat::opaque]
                    struct Foo<'a>(&'a str);

                    #[diplomat::opaque]
                    struct Bar<'b, 'a: 'b>(&'b Foo<'a>);

                    struct Baz<'x, 'y> {
                        foo: &'y Foo<'x>,
                    }

                    impl<'a> Foo<'a> {
                        pub fn new(x: &'a str) -> Box<Foo<'a>> {
                            unimplemented!()
                        }

                        pub fn get_bar<'b>(&'self) -> Box<Bar<'b, 'a>> {
                            unimplemented!()
                        }

                        pub fn get_baz<'b>(&'self) -> Baz<'b, 'a> {
                            Bax { foo: self }
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn self_params() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    #[diplomat::opaque]
                    struct RefList<'a> {
                        data: &'a i32,
                        next: Option<Box<Self>>,
                    }

                    impl<'b> RefList<'b> {
                        pub fn extend(&mut self, other: &Self) -> Self {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn cfged_method() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    impl Foo {
                        #[cfg(feature = "foo")]
                        pub fn bar(s: u8) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));

        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    struct Foo {}

                    #[cfg(feature = "bar")]
                    impl Foo {
                        #[cfg(feature = "foo")]
                        pub fn bar(s: u8) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn cfgd_struct() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    #[diplomat::opaque]
                    #[cfg(feature = "foo")]
                    struct Foo {}
                    #[cfg(feature = "foo")]
                    impl Foo {
                        pub fn bar(s: u8) {
                            unimplemented!()
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn callback_arguments() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    pub struct Wrapper {
                        cant_be_empty: bool,
                    }
                    pub struct TestingStruct {
                        x: i32,
                        y: i32,
                    }
                    impl Wrapper {
                        pub fn test_multi_arg_callback(f: impl Fn(i32) -> i32, x: i32) -> i32 {
                            f(10 + x)
                        }
                        pub fn test_multiarg_void_callback(f: impl Fn(i32, &str)) {
                            f(-10"hello it's a string\0");
                        }
                        pub fn test_mod_array(g: impl Fn(&[u8])) {
                            let bytes: Vec<u8> = vec![0x11, 0x22];
                            g(bytes.as_slice().into());
                        }
                        pub fn test_no_args(h: impl Fn()) -> i32 {
                            h();
                            -5
                        }
                        pub fn test_cb_with_struct(f: impl Fn(TestingStruct) -> i32) -> i32 {
                            let arg = TestingStruct {
                                x: 1,
                                y: 5,
                            };
                            f(arg)
                        }
                        pub fn test_multiple_cb_args(f: impl Fn() -> i32, g: impl Fn(i32) -> i32) -> i32 {
                            f() + g(5)
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn traits() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    pub struct TestingStruct {
                        x: i32,
                        y: i32,
                    }

                    pub trait TesterTrait: std::marker::Send {
                        fn test_trait_fn(&self, x: i32) -> i32;
                        fn test_void_trait_fn(&self);
                        fn test_struct_trait_fn(&self, s: TestingStruct) -> i32;
                        fn test_slice_trait_fn(&self, s: &[u8]) -> i32;
                    }

                    pub struct Wrapper {
                        cant_be_empty: bool,
                    }

                    impl Wrapper {
                        pub fn test_with_trait(t: impl TesterTrait, x: i32) -> i32 {
                            t.test_void_trait_fn();
                            t.test_trait_fn(x)
                        }

                        pub fn test_trait_with_struct(t: impl TesterTrait) -> i32 {
                            let arg = TestingStruct {
                                x: 1,
                                y: 5,
                            };
                            t.test_struct_trait_fn(arg)
                        }
                    }

                }
            })
            .to_token_stream()
        ));
    }

    #[test]
    fn both_kinds_of_option() {
        insta::assert_snapshot!(pretty_print_code(
            gen_bridge(parse_quote! {
                mod ffi {
                    use diplomat_runtime::DiplomatOption;
                    #[diplomat::opaque]
                    struct Foo {}
                    struct CustomStruct {
                        num: u8,
                        b: bool,
                        diplo_option: DiplomatOption<u8>,
                    }
                    impl Foo {
                        pub fn diplo_option_u8(x: DiplomatOption<u8>) -> DiplomatOption<u8> {
                            x
                        }
                        pub fn diplo_option_ref(x: DiplomatOption<&Foo>) -> DiplomatOption<&Foo> {
                            x
                        }
                        pub fn diplo_option_box() -> DiplomatOption<Box<Foo>> {
                            x
                        }
                        pub fn diplo_option_struct(x: DiplomatOption<CustomStruct>) -> DiplomatOption<CustomStruct> {
                            x
                        }
                        pub fn option_u8(x: Option<u8>) -> Option<u8> {
                            x
                        }
                        pub fn option_ref(x: Option<&Foo>) -> Option<&Foo> {
                            x
                        }
                        pub fn option_box() -> Option<Box<Foo>> {
                            x
                        }
                        pub fn option_struct(x: Option<CustomStruct>) -> Option<CustomStruct> {
                            x
                        }
                    }
                }
            })
            .to_token_stream()
        ));
    }
}

Messung V0.5 in Prozent
C=90 H=98 G=94

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.