//! 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::*;
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(iflet 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) => { letmut 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; letmut cb_param_list = vec![]; letmut cb_params_and_types_list = vec![]; letmut cb_arg_type_list = vec![]; letmut 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); iflet 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::<unsafeextern"C"fn (*mut c_void, ...) -> #cb_ret_type, unsafeextern"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 { letmut method_sigs: Vec<proc_macro2::TokenStream> = vec![];
method_sigs.push(quote!( pub destructor: Option<unsafeextern"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 letmut 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: unsafeextern"C"fn (#(#param_types),*) #return_tokens,
impl AttributeInfo { fn extract(attrs: &mut Vec<Attribute>) -> Self { letmut repr = false; letmut opaque = false; letmut 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 returntrue;
} elseif ident == "diplomat" { if attr.path().segments.len() == 2 { let seg = &attr.path().segments.iter().nth(1).unwrap().ident; if seg == "opaque" {
opaque = true; returnfalse;
} elseif seg == "out" {
is_out = true; returnfalse;
} elseif 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 returnfalse;
} elseif seg == "enum_convert" || seg == "transparent_convert" { // diplomat::bridge doesn't read this, but it's handled separately // as an attribute returntrue;
} elseif 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 { iflet syn::ImplItem::Fn(refmut 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);
});
iflet ast::CustomType::Opaque(opaque) = custom_type { let destroy_ident = Ident::new(opaque.dtor_abi_name.as_str(), Span::call_site());
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());
/// Mark a module to be exposed through Diplomat-generated FFI. #[proc_macro_attribute] pubfn 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] pubfn 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] pubfn 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] pubfn 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] pubfn 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())
}
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.