Source code
Revision control
Copy as Markdown
Other Tools
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 cb_ret_type = out_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_type, unsafe 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,)*)
};
};
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_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<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_{}_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::<Vec<_>>();
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<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: {}, 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())
}
// 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())
}
#[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>(&'b self) -> Box<Bar<'b, 'a>> {
unimplemented!()
}
pub fn get_baz<'b>(&'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()
));
}
}