use crate::types::{make_ident, QualifiedName};
use indexmap::map::IndexMap as HashMap;
use indoc::indoc;
use once_cell::sync::OnceCell;
use syn::{parse_quote, TypePath};
#[derive(Debug)]
enum Behavior {
CxxContainerPtr,
CxxContainerVector,
CxxString,
RustStr,
RustString,
RustByValue,
CByValue,
CByValueVecSafe,
CVariableLengthByValue,
CVoid,
CChar16,
RustContainerByValueSafe,
}
#[derive(Debug)]
struct TypeDetails {
rs_name: String,
cpp_name: String,
behavior: Behavior,
extra_non_canonical_name: Option<String>,
has_const_copy_constructor: bool,
has_move_constructor: bool,
}
impl TypeDetails {
fn new(
rs_name: impl Into<String>,
cpp_name: impl Into<String>,
behavior: Behavior,
extra_non_canonical_name: Option<String>,
has_const_copy_constructor: bool,
has_move_constructor: bool,
) -> Self {
TypeDetails {
rs_name: rs_name.into(),
cpp_name: cpp_name.into(),
behavior,
extra_non_canonical_name,
has_const_copy_constructor,
has_move_constructor,
}
}
fn get_prelude_entry(&self) -> Option<String> {
match self.behavior {
Behavior::RustString
| Behavior::RustStr
| Behavior::CxxString
| Behavior::CxxContainerPtr
| Behavior::CxxContainerVector
| Behavior::RustContainerByValueSafe => {
let tn = QualifiedName::new_from_cpp_name(&self.rs_name);
let cxx_name = tn.get_final_item();
let (templating, payload) = match self.behavior {
Behavior::CxxContainerPtr
| Behavior::CxxContainerVector
| Behavior::RustContainerByValueSafe => ("template<typename T> ", "T* ptr"),
_ => ("", "char* ptr"),
};
Some(format!(
indoc! {"
/**
* <div rustbindgen=\"true\" replaces=\"{}\">
*/
{}class {} {{
{};
}};
"},
self.cpp_name, templating, cxx_name, payload
))
}
_ => None,
}
}
fn to_type_path(&self) -> TypePath {
let mut segs = self.rs_name.split("::").peekable();
if segs.peek().map(|seg| seg.is_empty()).unwrap_or_default() {
segs.next();
let segs = segs.map(make_ident);
parse_quote! {
::#(#segs)::*
}
} else {
let segs = segs.map(make_ident);
parse_quote! {
#(#segs)::*
}
}
}
fn to_typename(&self) -> QualifiedName {
QualifiedName::new_from_cpp_name(&self.rs_name)
}
fn get_generic_behavior(&self) -> CxxGenericType {
match self.behavior {
Behavior::CxxContainerPtr => CxxGenericType::CppPtr,
Behavior::CxxContainerVector => CxxGenericType::CppVector,
Behavior::RustContainerByValueSafe => CxxGenericType::Rust,
_ => CxxGenericType::Not,
}
}
}
#[derive(Default)]
pub(crate) struct TypeDatabase {
by_rs_name: HashMap<QualifiedName, TypeDetails>,
canonical_names: HashMap<QualifiedName, QualifiedName>,
}
pub(crate) fn known_types() -> &'static TypeDatabase {
static KNOWN_TYPES: OnceCell<TypeDatabase> = OnceCell::new();
KNOWN_TYPES.get_or_init(create_type_database)
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum CxxGenericType {
Not,
CppPtr,
CppVector,
Rust,
}
pub struct KnownTypeConstructorDetails {
pub has_move_constructor: bool,
pub has_const_copy_constructor: bool,
}
impl TypeDatabase {
fn get(&self, ty: &QualifiedName) -> Option<&TypeDetails> {
let canonical_name = self.canonical_names.get(ty).unwrap_or(ty);
self.by_rs_name.get(canonical_name)
}
pub(crate) fn get_prelude(&self) -> String {
itertools::join(
self.by_rs_name
.values()
.filter_map(|t| t.get_prelude_entry()),
"",
)
}
pub(crate) fn all_names(&self) -> impl Iterator<Item = &QualifiedName> {
self.canonical_names.keys().chain(self.by_rs_name.keys())
}
pub(crate) fn get_pod_safe_types(&self) -> impl Iterator<Item = (QualifiedName, bool)> {
let pod_safety = self
.all_names()
.map(|tn| {
(
tn.clone(),
match self.get(tn).unwrap().behavior {
Behavior::CxxContainerPtr
| Behavior::RustStr
| Behavior::RustString
| Behavior::RustByValue
| Behavior::CByValueVecSafe
| Behavior::CByValue
| Behavior::CVariableLengthByValue
| Behavior::CChar16
| Behavior::RustContainerByValueSafe => true,
Behavior::CxxString | Behavior::CxxContainerVector | Behavior::CVoid => {
false
}
},
)
})
.collect::<HashMap<_, _>>();
pod_safety.into_iter()
}
pub(crate) fn get_constructor_details(
&self,
qn: &QualifiedName,
) -> Option<KnownTypeConstructorDetails> {
self.get(qn).map(|x| KnownTypeConstructorDetails {
has_move_constructor: x.has_move_constructor,
has_const_copy_constructor: x.has_const_copy_constructor,
})
}
pub(crate) fn should_dereference_in_cpp(&self, tn: &QualifiedName) -> bool {
self.get(tn)
.map(|td| matches!(td.behavior, Behavior::RustStr))
.unwrap_or(false)
}
pub(crate) fn lacks_copy_constructor(&self, tn: &QualifiedName) -> bool {
self.get(tn)
.map(|td| {
matches!(
td.behavior,
Behavior::CxxContainerPtr | Behavior::CxxContainerVector
)
})
.unwrap_or(false)
}
pub(crate) fn consider_substitution(&self, tn: &QualifiedName) -> Option<TypePath> {
self.get(tn).map(|td| td.to_type_path())
}
pub(crate) fn special_cpp_name(&self, rs: &QualifiedName) -> Option<String> {
self.get(rs).map(|x| x.cpp_name.to_string())
}
pub(crate) fn is_known_type(&self, ty: &QualifiedName) -> bool {
self.get(ty).is_some()
}
pub(crate) fn known_type_type_path(&self, ty: &QualifiedName) -> Option<TypePath> {
self.get(ty).map(|td| td.to_type_path())
}
pub(crate) fn get_initial_blocklist(&self) -> impl Iterator<Item = &str> + '_ {
self.by_rs_name
.iter()
.filter_map(|(_, td)| td.get_prelude_entry().map(|_| td.cpp_name.as_str()))
}
pub(crate) fn is_ctype(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|td| {
matches!(
td.behavior,
Behavior::CVariableLengthByValue | Behavior::CVoid | Behavior::CChar16
)
})
.unwrap_or(false)
}
pub(crate) fn cxx_generic_behavior(&self, ty: &QualifiedName) -> CxxGenericType {
self.get(ty)
.map(|x| x.get_generic_behavior())
.unwrap_or(CxxGenericType::Not)
}
pub(crate) fn is_cxx_acceptable_receiver(&self, ty: &QualifiedName) -> bool {
self.get(ty).is_none() }
pub(crate) fn permissible_within_vector(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|x| matches!(x.behavior, Behavior::CxxString | Behavior::CByValueVecSafe))
.unwrap_or(true)
}
pub(crate) fn permissible_within_unique_ptr(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|x| {
matches!(
x.behavior,
Behavior::CxxString | Behavior::CxxContainerVector
)
})
.unwrap_or(true)
}
pub(crate) fn conflicts_with_built_in_type(&self, ty: &QualifiedName) -> bool {
self.get(ty).is_some()
}
pub(crate) fn convertible_from_strs(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|x| matches!(x.behavior, Behavior::CxxString))
.unwrap_or(false)
}
fn insert(&mut self, td: TypeDetails) {
let rs_name = td.to_typename();
if let Some(extra_non_canonical_name) = &td.extra_non_canonical_name {
self.canonical_names.insert(
QualifiedName::new_from_cpp_name(extra_non_canonical_name),
rs_name.clone(),
);
}
self.canonical_names.insert(
QualifiedName::new_from_cpp_name(&td.cpp_name),
rs_name.clone(),
);
self.by_rs_name.insert(rs_name, td);
}
pub(crate) fn get_moveit_safe_types(&self) -> impl Iterator<Item = QualifiedName> + '_ {
self.all_names()
.filter(|tn| {
!matches!(
self.get(tn).unwrap().behavior,
Behavior::CxxString | Behavior::CxxContainerVector
)
})
.cloned()
}
}
fn create_type_database() -> TypeDatabase {
let mut db = TypeDatabase::default();
db.insert(TypeDetails::new(
"cxx::UniquePtr",
"std::unique_ptr",
Behavior::CxxContainerPtr,
None,
false,
true,
));
db.insert(TypeDetails::new(
"cxx::CxxVector",
"std::vector",
Behavior::CxxContainerVector,
None,
false,
true,
));
db.insert(TypeDetails::new(
"cxx::SharedPtr",
"std::shared_ptr",
Behavior::CxxContainerPtr,
None,
true,
true,
));
db.insert(TypeDetails::new(
"cxx::WeakPtr",
"std::weak_ptr",
Behavior::CxxContainerPtr,
None,
true,
true,
));
db.insert(TypeDetails::new(
"cxx::CxxString",
"std::string",
Behavior::CxxString,
None,
true,
true,
));
db.insert(TypeDetails::new(
"str",
"rust::Str",
Behavior::RustStr,
None,
true,
false,
));
db.insert(TypeDetails::new(
"String",
"rust::String",
Behavior::RustString,
None,
true,
true,
));
db.insert(TypeDetails::new(
"std::boxed::Box",
"rust::Box",
Behavior::RustContainerByValueSafe,
None,
false,
true,
));
db.insert(TypeDetails::new(
"i8",
"int8_t",
Behavior::CByValueVecSafe,
Some("std::os::raw::c_schar".into()),
true,
true,
));
db.insert(TypeDetails::new(
"u8",
"uint8_t",
Behavior::CByValueVecSafe,
Some("std::os::raw::c_uchar".into()),
true,
true,
));
for (cpp_type, rust_type) in (4..7).map(|x| 2i32.pow(x)).flat_map(|x| {
vec![
(format!("uint{x}_t"), format!("u{x}")),
(format!("int{x}_t"), format!("i{x}")),
]
}) {
db.insert(TypeDetails::new(
rust_type,
cpp_type,
Behavior::CByValueVecSafe,
None,
true,
true,
));
}
db.insert(TypeDetails::new(
"bool",
"bool",
Behavior::CByValue,
None,
true,
true,
));
db.insert(TypeDetails::new(
"std::pin::Pin",
"Pin",
Behavior::RustByValue, None,
true,
false,
));
let mut insert_ctype = |cname: &str| {
let concatenated_name = cname.replace(' ', "");
db.insert(TypeDetails::new(
format!("autocxx::c_{concatenated_name}"),
cname,
Behavior::CVariableLengthByValue,
Some(format!("std::os::raw::c_{concatenated_name}")),
true,
true,
));
db.insert(TypeDetails::new(
format!("autocxx::c_u{concatenated_name}"),
format!("unsigned {cname}"),
Behavior::CVariableLengthByValue,
Some(format!("std::os::raw::c_u{concatenated_name}")),
true,
true,
));
};
insert_ctype("long");
insert_ctype("int");
insert_ctype("short");
insert_ctype("long long");
db.insert(TypeDetails::new(
"f32",
"float",
Behavior::CByValueVecSafe,
None,
true,
true,
));
db.insert(TypeDetails::new(
"f64",
"double",
Behavior::CByValueVecSafe,
None,
true,
true,
));
db.insert(TypeDetails::new(
"::std::os::raw::c_char",
"char",
Behavior::CByValue,
None,
true,
true,
));
db.insert(TypeDetails::new(
"usize",
"size_t",
Behavior::CByValueVecSafe,
None,
true,
true,
));
db.insert(TypeDetails::new(
"autocxx::c_void",
"void",
Behavior::CVoid,
Some("std::os::raw::c_void".into()),
false,
false,
));
db.insert(TypeDetails::new(
"autocxx::c_char16_t",
"char16_t",
Behavior::CChar16,
Some("c_char16_t".into()),
false,
false,
));
db
}