use std::collections::{HashMap, HashSet};
pub(crate) use prebindgen_registry::types_util::{
is_result_type as is_result, path_tail_ident as type_path_tail, result_parts,
};
use prebindgen_registry::{
decl::{ConvertDecl, ConvertSpec},
flat::{extract_fn_trait_args, Field, Origin, ScalarKind, TypeKind, TypeRef},
Conversions, ConverterImpl, Direction, NicheSlot, Niches, Prebindgen, Registry, TypeKey,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
fn declared_origin(ty: syn::Type) -> Origin<syn::Type> {
Origin::new(ty, std::rc::Rc::new(prebindgen::SourceLocation::default()))
}
type CallbackKey = Vec<TypeKey>;
#[derive(Clone)]
struct TypeCfg {
rust_type: Origin<syn::Type>,
base: Option<String>,
}
impl TypeCfg {
fn new(rust_type: syn::Type) -> Self {
Self {
rust_type: declared_origin(rust_type),
base: None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum OpaqueKind {
Data,
Owned,
}
#[derive(Clone)]
struct ValueOpaqueCfg {
opaque: syn::Type,
kind: OpaqueKind,
generate_mirror: bool,
assume_c_field_validity: bool,
cfg: TypeCfg,
}
#[derive(Clone)]
struct CbCfg {
args: Vec<syn::Type>,
base: Option<String>,
takeable: std::collections::BTreeSet<usize>,
}
impl CbCfg {
fn new(args: Vec<syn::Type>) -> Self {
Self {
args,
base: None,
takeable: std::collections::BTreeSet::new(),
}
}
}
#[derive(Clone, Default)]
struct FnCfg {
base: Option<String>,
panic: bool,
}
#[derive(Clone)]
enum CurrentDecl {
Ptr(TypeKey),
Data(TypeKey),
ValueOpaque(TypeKey),
Enum(TypeKey),
TaggedUnion(TypeKey),
Callback(CallbackKey),
Function(syn::Ident),
Convert(TypeKey),
}
#[allow(clippy::large_enum_variant)]
enum ErrRoute<'a> {
Result {
e_conv: &'a syn::Ident,
e_ty_src: syn::Type,
fail_return: TokenStream,
},
Panic,
}
fn route_message(route: &ErrRoute<'_>) -> TokenStream {
match route {
ErrRoute::Result {
e_conv,
e_ty_src,
fail_return,
} => quote!(
if !e.is_null() {
*e = #e_conv(
<#e_ty_src as ::core::convert::From<::std::string::String>>::from(__msg),
);
}
return #fail_return;
),
ErrRoute::Panic => quote!(panic!("{}", __msg);),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum AliasAccess {
Consume,
Exclusive,
Shared,
}
impl AliasAccess {
fn describe(self) -> &'static str {
match self {
AliasAccess::Consume => "consumed",
AliasAccess::Exclusive => "exclusively borrowed",
AliasAccess::Shared => "borrowed",
}
}
}
pub struct Cbindgen {
pub(crate) gen: CbindgenBuilder,
pub(crate) registry: prebindgen_registry::Registry<()>,
}
impl std::fmt::Debug for Cbindgen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Cbindgen(..)")
}
}
impl Cbindgen {
pub fn builder() -> CbindgenBuilder {
CbindgenBuilder::new()
}
pub fn write_rust(
&self,
out_path: impl AsRef<std::path::Path>,
) -> Result<std::path::PathBuf, prebindgen_registry::WriteRustError> {
Ok(prebindgen_registry::write::write_rust(
&self.registry,
&self.gen,
out_path,
)?)
}
pub fn registry(&self) -> &prebindgen_registry::Registry<()> {
&self.registry
}
pub fn declarations(&self) -> &CbindgenBuilder {
&self.gen
}
}
#[derive(Default)]
pub struct CbindgenBuilder {
source_module: Option<syn::Path>,
functions: HashMap<syn::Ident, FnCfg>,
convert_decls: Vec<ConvertDecl>,
convert_bases: HashMap<TypeKey, String>,
ignored_functions: HashSet<syn::Ident>,
opaque: HashMap<TypeKey, TypeCfg>,
data: HashMap<TypeKey, TypeCfg>,
value_opaque: HashMap<TypeKey, ValueOpaqueCfg>,
enums: HashMap<TypeKey, TypeCfg>,
tagged_unions: HashMap<TypeKey, TypeCfg>,
callbacks: HashMap<CallbackKey, CbCfg>,
ignored_types: HashSet<TypeKey>,
error: HashSet<TypeKey>,
opaque_errors: HashMap<TypeKey, syn::Ident>,
free_fn: Option<String>,
current: Option<CurrentDecl>,
mangle_rust_type: Option<Mangle1>,
mangle_type_name: Option<Mangle1>,
mangle_destructor: Option<Mangle1>,
mangle_take: Option<Mangle1>,
mangle_callback: Option<MangleN>,
mangle_function: Option<Mangle1>,
pub(crate) sources: prebindgen_registry::flat::FlatBuilder,
}
type Mangle1 = Box<dyn Fn(&str) -> String>;
type MangleN = Box<dyn Fn(&[String]) -> String>;
mod builder;
mod convert;
mod emit;
mod selector;
#[cfg(test)]
mod test_util;
#[cfg(test)]
mod tests;
mod trait_impl;
fn sorted_by_key(map: &HashMap<TypeKey, TypeCfg>) -> Vec<(&TypeKey, &TypeCfg)> {
let mut entries: Vec<(&TypeKey, &TypeCfg)> = map.iter().collect();
entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
entries
}
fn sanitize(key: &TypeKey) -> String {
key.as_str()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect()
}
fn type_short(key: &TypeKey) -> String {
key.short_name().unwrap_or_else(|| sanitize(key))
}
fn payload_enum<'r>(
registry: &'r impl Conversions<()>,
key: &TypeKey,
) -> Option<&'r prebindgen_registry::flat::Variant> {
match registry.flat().declared_type(&key.ident()?)? {
prebindgen_registry::flat::Type::Variant(v) => Some(v),
prebindgen_registry::flat::Type::Enum(e) => panic!(
"Cbindgen: `{}` has no payload variants: declare it with `.enum_type()`, \
not `.tagged_union()` — a fieldless enum crosses as a plain C `enum`",
e.name
),
_ => None,
}
}
fn unit_enum<'r>(
registry: &'r impl Conversions<()>,
key: &TypeKey,
) -> Option<&'r prebindgen_registry::flat::Enum> {
match registry.flat().declared_type(&key.ident()?)? {
prebindgen_registry::flat::Type::Enum(e) => Some(e),
prebindgen_registry::flat::Type::Variant(v) => {
let offender = v
.alternatives
.iter()
.find(|a| !a.is_empty())
.map(|a| a.name.to_string())
.unwrap_or_default();
panic!(
"Cbindgen: `{}` is a data-carrying enum (variant `{offender}` has fields): \
declare it with `.tagged_union()`, not `.enum_type()` — a C `enum` is a bare \
discriminant and has no room for a payload",
v.name
)
}
_ => None,
}
}
pub fn snake_case(s: &str) -> String {
prebindgen_registry::types_util::pascal_to_snake(s)
}
fn spelled(t: &TypeRef, emit: &prebindgen_registry::Emit) -> syn::Type {
let toks = emit.spell(t);
syn::parse_quote!(#toks)
}
fn r_is_string(t: &TypeRef) -> bool {
matches!(t.kind(), TypeKind::String)
}
fn r_is_str(t: &TypeRef) -> bool {
matches!(t.kind(), TypeKind::Str)
}
fn r_is_bool(t: &TypeRef) -> bool {
matches!(t.kind(), TypeKind::Scalar(ScalarKind::Bool))
}
fn scalar_ty(t: &TypeRef) -> Option<syn::Type> {
let TypeKind::Scalar(k) = t.kind() else {
return None;
};
let id = syn::Ident::new(k.as_str(), proc_macro2::Span::call_site());
Some(syn::parse_quote!(#id))
}
fn r_is_scalar(t: &TypeRef) -> bool {
matches!(t.kind(), TypeKind::Scalar(_))
}
fn r_is_vec(t: &TypeRef) -> bool {
matches!(t.kind(), TypeKind::Vec(_))
}
fn r_boxed_inner(t: &TypeRef) -> Option<&TypeRef> {
let core = t.optional_inner().unwrap_or(t);
match core.kind() {
TypeKind::Boxed(inner) => Some(inner),
_ => None,
}
}
fn is_string(ty: &syn::Type) -> bool {
type_path_tail(ty).map(|i| i == "String").unwrap_or(false)
}
fn bool_wire() -> syn::Type {
syn::parse_quote!(::core::mem::MaybeUninit<bool>)
}
fn bool_in_expr(access: TokenStream) -> TokenStream {
quote!(::core::ptr::read(#access.as_ptr() as *const u8) != 0)
}
fn bool_out_expr(value: TokenStream) -> TokenStream {
quote!(::core::mem::MaybeUninit::new(#value))
}
fn is_scalar(ty: &syn::Type) -> bool {
type_path_tail(ty)
.map(|i| {
matches!(
i.to_string().as_str(),
"bool"
| "i8"
| "i16"
| "i32"
| "i64"
| "isize"
| "u8"
| "u16"
| "u32"
| "u64"
| "usize"
| "f32"
| "f64"
)
})
.unwrap_or(false)
}
fn r_shared_slice_elem(t: &TypeRef) -> Option<&TypeRef> {
let TypeKind::Ref {
mutable: false,
inner,
..
} = t.kind()
else {
return None;
};
match inner.kind() {
TypeKind::Slice(e) => Some(e),
_ => None,
}
}
fn r_cow_slice_elem(t: &TypeRef) -> Option<&TypeRef> {
let TypeKind::Cow { inner, .. } = t.kind() else {
return None;
};
match inner.kind() {
TypeKind::Slice(e) if r_is_scalar(e) => Some(e),
_ => None,
}
}
fn r_scalar_slice_elem(t: &TypeRef) -> Option<&TypeRef> {
r_shared_slice_elem(t).filter(|e| r_is_scalar(e))
}
fn scalar_slice_elem(ty: &syn::Type) -> Option<syn::Type> {
let syn::Type::Reference(r) = ty else {
return None;
};
if r.mutability.is_some() {
return None;
}
let syn::Type::Slice(s) = &*r.elem else {
return None;
};
let elem = (*s.elem).clone();
is_scalar(&elem).then_some(elem)
}
fn out_param_name(suffix: &str, prefixed: bool) -> syn::Ident {
if prefixed {
format_ident!("out{}", suffix)
} else {
format_ident!("{}", suffix.trim_start_matches('_'))
}
}
fn null_for(wire: &syn::Type) -> TokenStream {
match wire {
syn::Type::Ptr(p) if p.mutability.is_some() => quote!(::core::ptr::null_mut()),
_ => quote!(::core::ptr::null()),
}
}
struct WireField {
suffix: &'static str,
wire: syn::Type,
}
struct ValueShape {
fields: Vec<WireField>,
niches: Niches,
}
fn returns_result(output: &syn::ReturnType) -> bool {
match output {
syn::ReturnType::Type(_, ty) => is_result(ty),
syn::ReturnType::Default => false,
}
}
fn route_result(call: TokenStream, route: &ErrRoute<'_>) -> TokenStream {
match route {
ErrRoute::Result {
e_conv,
e_ty_src,
fail_return,
} => quote! {
match #call {
::core::result::Result::Ok(value) => value,
::core::result::Result::Err(message) => {
if !e.is_null() {
*e = #e_conv(
<#e_ty_src as ::core::convert::From<
::std::string::String
>>::from(message)
);
}
return #fail_return;
}
}
},
ErrRoute::Panic => quote! {
match #call {
::core::result::Result::Ok(value) => value,
::core::result::Result::Err(message) => panic!("{}", message),
}
},
}
}