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 { 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 and DiplomatOption // 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) => { 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 cb_ret_type = out_type.to_syn(); let tokens = quote! { let #cb_wrap_ident = move | #(#cb_params_and_types_list,)* | unsafe { #(#all_params_conversion)* std::mem::transmute:: #cb_ret_type, unsafe extern "C" fn (*const c_void, #(#cb_arg_type_list,)*) -> #cb_ret_type> (#cb_wrap_ident.run_callback)(#cb_wrap_ident.data, #(#cb_param_list,)*) }; }; Some(parse2(tokens).unwrap()) } _ => None, } } fn gen_custom_vtable(custom_trait: &ast::Trait, custom_trait_vtable_type: &Ident) -> Item { let mut method_sigs: Vec = vec![]; method_sigs.push(quote!( pub destructor: Option, 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 = 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_name: unsafe extern "C" fn (#(#param_types),*) #return_tokens, )); } syn::parse_quote!( #[repr(C)] 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 = vec![]; for m in &custom_trait.methods { let param_names: Vec = 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 = 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_{}_callback", method_name), 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()); let mut all_params = vec![]; let mut all_params_conversion = vec![]; let mut all_params_names = vec![]; m.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) = &m.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 = &m.lifetime_env; if lifetime_env.is_empty() { quote! {} } else { quote! { <#lifetime_env> } } }; let method_invocation = if m.self_param.is_some() { quote! { #this_ident.#method_ident } } else { quote! { #self_ident::#method_ident } }; let (return_tokens, maybe_into) = if let Some(return_type) = &m.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 = m .params .iter() .filter(|p| p.is_write()) .map(|p| { let p = &p.name; quote! { #p.flush(); } }) .collect::>(); let cfg = cfgs_to_stream(&m.attrs.cfg); if write_flushes.is_empty() { Item::Fn(syn::parse_quote! { #[no_mangle] #cfg 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 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) -> 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 { 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: {}, try {}", ty, 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 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); } } } } }) } 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()) } /// 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()) } #[cfg(test)] mod tests { use std::fs::File; use std::io::{Read, Write}; use std::process::Command; use quote::ToTokens; use syn::parse_quote; use tempfile::tempdir; use super::gen_bridge; fn rustfmt_code(code: &str) -> String { let dir = tempdir().unwrap(); let file_path = dir.path().join("temp.rs"); let mut file = File::create(file_path.clone()).unwrap(); writeln!(file, "{code}").unwrap(); drop(file); Command::new("rustfmt") .arg(file_path.to_str().unwrap()) .spawn() .unwrap() .wait() .unwrap(); let mut file = File::open(file_path).unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); drop(file); dir.close().unwrap(); data } #[test] fn method_taking_str() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn from_str(s: &DiplomatStr) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn slices() { insta::assert_snapshot!(rustfmt_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, d: Box, e: Box, f: Box<[DiplomatByte]>) -> Self { unimplemented!() } pub fn boxes_runtime_types(a: DiplomatOwnedSlice, b: DiplomatOwnedSlice, c: DiplomatOwnedUTF8StrSlice, d: DiplomatOwnedStrSlice, e: DiplomatOwnedStr16Slice, f: DiplomatOwnedSlice) -> 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() .to_string() )); } #[test] fn method_taking_slice() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn from_slice(s: &[f64]) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn method_taking_mutable_slice() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn fill_slice(s: &mut [f64]) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn method_taking_owned_slice() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn fill_slice(s: Box<[u16]>) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn method_taking_owned_str() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn something_with_str(s: Box) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn mod_with_enum() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { enum Abc { A, B = 123, } impl Abc { pub fn do_something(&self) { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn mod_with_write_result() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn to_string(&self, to: &mut DiplomatWrite) -> Result<(), ()> { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn mod_with_rust_result() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { pub fn bar(&self) -> Result<(), ()> { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn multilevel_borrows() { insta::assert_snapshot!(rustfmt_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> { unimplemented!() } pub fn get_bar<'b>(&'b self) -> Box> { unimplemented!() } pub fn get_baz<'b>(&'b self) -> Baz<'b, 'a> { Bax { foo: self } } } } }) .to_token_stream() .to_string() )); } #[test] fn self_params() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { #[diplomat::opaque] struct RefList<'a> { data: &'a i32, next: Option>, } impl<'b> RefList<'b> { pub fn extend(&mut self, other: &Self) -> Self { unimplemented!() } } } }) .to_token_stream() .to_string() )); } #[test] fn cfged_method() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { struct Foo {} impl Foo { #[cfg(feature = "foo")] pub fn bar(s: u8) { unimplemented!() } } } }) .to_token_stream() .to_string() )); insta::assert_snapshot!(rustfmt_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() .to_string() )); } #[test] fn cfgd_struct() { insta::assert_snapshot!(rustfmt_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() .to_string() )); } #[test] fn callback_arguments() { insta::assert_snapshot!(rustfmt_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 = 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() .to_string() )); } #[test] fn traits() { insta::assert_snapshot!(rustfmt_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() .to_string() )); } #[test] fn both_kinds_of_option() { insta::assert_snapshot!(rustfmt_code( &gen_bridge(parse_quote! { mod ffi { use diplomat_runtime::DiplomatOption; #[diplomat::opaque] struct Foo {} struct CustomStruct { num: u8, b: bool, diplo_option: DiplomatOption, } impl Foo { pub fn diplo_option_u8(x: DiplomatOption) -> DiplomatOption { x } pub fn diplo_option_ref(x: DiplomatOption<&Foo>) -> DiplomatOption<&Foo> { x } pub fn diplo_option_box() -> DiplomatOption> { x } pub fn diplo_option_struct(x: DiplomatOption) -> DiplomatOption { x } pub fn option_u8(x: Option) -> Option { x } pub fn option_ref(x: Option<&Foo>) -> Option<&Foo> { x } pub fn option_box() -> Option> { x } pub fn option_struct(x: Option) -> Option { x } } } }) .to_token_stream() .to_string() )); } }