use crate::{
conversion::ConvertError,
types::{make_ident, QualifiedName},
};
use indoc::indoc;
use once_cell::sync::OnceCell;
use std::collections::{HashMap, HashSet};
use syn::{parse_quote, GenericArgument, PathArguments, Type, TypePath, TypePtr};
#[derive(Debug)]
enum Behavior {
CxxContainerByValueSafe,
CxxContainerNotByValueSafe,
CxxString,
RustStr,
RustString,
RustByValue,
CByValue,
CVariableLengthByValue,
CVoid,
}
#[derive(Debug)]
struct TypeDetails {
rs_name: String,
cpp_name: String,
behavior: Behavior,
extra_non_canonical_name: Option<String>,
}
impl TypeDetails {
fn new(
rs_name: impl Into<String>,
cpp_name: impl Into<String>,
behavior: Behavior,
extra_non_canonical_name: Option<String>,
) -> Self {
TypeDetails {
rs_name: rs_name.into(),
cpp_name: cpp_name.into(),
behavior,
extra_non_canonical_name,
}
}
fn get_prelude_entry(&self) -> Option<String> {
match self.behavior {
Behavior::RustString
| Behavior::RustStr
| Behavior::CxxString
| Behavior::CxxContainerByValueSafe
| Behavior::CxxContainerNotByValueSafe => {
let tn = QualifiedName::new_from_user_input(&self.rs_name);
let cxx_name = tn.get_final_item();
let (templating, payload) = match self.behavior {
Behavior::CxxContainerByValueSafe | Behavior::CxxContainerNotByValueSafe => {
("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 segs = self.rs_name.split("::").map(make_ident);
parse_quote! {
#(#segs)::*
}
}
fn to_typename(&self) -> QualifiedName {
QualifiedName::new_from_user_input(&self.rs_name)
}
}
#[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)
}
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()),
"\n",
)
}
pub(crate) fn get_pod_safe_types(&self) -> impl Iterator<Item = (&QualifiedName, bool)> {
self.by_rs_name.iter().map(|(tn, td)| {
(
tn,
match td.behavior {
Behavior::CxxContainerByValueSafe
| Behavior::RustStr
| Behavior::RustString
| Behavior::RustByValue
| Behavior::CByValue
| Behavior::CVariableLengthByValue => true,
Behavior::CxxString
| Behavior::CxxContainerNotByValueSafe
| Behavior::CVoid => false,
},
)
})
}
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 consider_substitution(
&self,
tn: &QualifiedName,
) -> Result<Option<TypePath>, ConvertError> {
match self.get(&tn) {
None => {
if tn
.ns_segment_iter()
.next()
.filter(|seg| BINDGEN_BLOCKLIST_NAMESPACES.iter().any(|ns| ns == seg))
.is_some()
{
return Err(ConvertError::UnacceptableSpecialNamespaceType(tn.clone()));
}
Ok(None)
}
Some(td) => Ok(Some(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 is_ctype(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|td| {
matches!(
td.behavior,
Behavior::CVariableLengthByValue | Behavior::CVoid
)
})
.unwrap_or(false)
}
pub(crate) fn is_cxx_acceptable_generic(&self, ty: &QualifiedName) -> bool {
self.get(ty)
.map(|x| {
matches!(
x.behavior,
Behavior::CxxContainerByValueSafe | Behavior::CxxContainerNotByValueSafe
)
})
.unwrap_or(false)
}
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_user_input(extra_non_canonical_name),
rs_name.clone(),
);
}
self.canonical_names.insert(
QualifiedName::new_from_user_input(&td.cpp_name),
rs_name.clone(),
);
self.by_rs_name.insert(rs_name, td);
}
}
fn create_type_database() -> TypeDatabase {
let mut db = TypeDatabase::default();
db.insert(TypeDetails::new(
"cxx::UniquePtr",
"std::unique_ptr",
Behavior::CxxContainerByValueSafe,
None,
));
db.insert(TypeDetails::new(
"cxx::CxxVector",
"std::vector",
Behavior::CxxContainerNotByValueSafe,
None,
));
db.insert(TypeDetails::new(
"cxx::SharedPtr",
"std::shared_ptr",
Behavior::CxxContainerByValueSafe,
None,
));
db.insert(TypeDetails::new(
"cxx::CxxString",
"std::string",
Behavior::CxxString,
None,
));
db.insert(TypeDetails::new(
"str",
"rust::Str",
Behavior::RustStr,
None,
));
db.insert(TypeDetails::new(
"String",
"rust::String",
Behavior::RustString,
None,
));
db.insert(TypeDetails::new(
"i8",
"int8_t",
Behavior::CByValue,
Some("std::os::raw::c_schar".into()),
));
db.insert(TypeDetails::new(
"u8",
"uint8_t",
Behavior::CByValue,
Some("std::os::raw::c_uchar".into()),
));
for (cpp_type, rust_type) in (4..7)
.map(|x| 2i32.pow(x))
.map(|x| {
vec![
(format!("uint{}_t", x), format!("u{}", x)),
(format!("int{}_t", x), format!("i{}", x)),
]
})
.flatten()
{
db.insert(TypeDetails::new(
rust_type,
cpp_type,
Behavior::CByValue,
None,
));
}
db.insert(TypeDetails::new("bool", "bool", Behavior::CByValue, None));
db.insert(TypeDetails::new(
"std::pin::Pin",
"Pin",
Behavior::RustByValue, None,
));
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)),
));
db.insert(TypeDetails::new(
format!("autocxx::c_u{}", concatenated_name),
format!("unsigned {}", cname),
Behavior::CVariableLengthByValue,
Some(format!("std::os::raw::c_u{}", concatenated_name)),
));
};
insert_ctype("long");
insert_ctype("int");
insert_ctype("short");
insert_ctype("long long");
db.insert(TypeDetails::new("f32", "float", Behavior::CByValue, None));
db.insert(TypeDetails::new("f64", "double", Behavior::CByValue, None));
db.insert(TypeDetails::new(
"std::os::raw::c_char",
"char",
Behavior::CByValue,
None,
));
db.insert(TypeDetails::new(
"autocxx::c_void",
"void",
Behavior::CVoid,
Some("std::os::raw::c_void".into()),
));
db
}
const BINDGEN_BLOCKLIST_NAMESPACES: &[&str] = &["std", "rust"];
const BINDGEN_BLOCKLIST: &[&str] = &["__gnu.*", ".*mbstate_t.*"];
pub(crate) fn get_initial_blocklist() -> impl Iterator<Item = String> {
BINDGEN_BLOCKLIST_NAMESPACES
.iter()
.map(|ns| format!("{}::.*", ns))
.chain(BINDGEN_BLOCKLIST.iter().map(|s| s.to_string()))
}
pub(crate) fn type_lacks_copy_constructor(ty: &Type) -> bool {
match ty {
Type::Path(typ) => {
let tn = QualifiedName::from_type_path(typ);
tn.to_cpp_name().starts_with("std::unique_ptr")
}
_ => false,
}
}
pub(crate) fn confirm_inner_type_is_acceptable_generic_payload(
path_args: &PathArguments,
desc: &QualifiedName,
unacceptable_types: &HashSet<QualifiedName>,
) -> Result<(), ConvertError> {
match path_args {
PathArguments::None => Ok(()),
PathArguments::Parenthesized(_) => Err(ConvertError::TemplatedTypeContainingNonPathArg(
desc.clone(),
)),
PathArguments::AngleBracketed(ab) => {
for inner in &ab.args {
match inner {
GenericArgument::Type(Type::Path(typ)) => {
let inner_qn = QualifiedName::from_type_path(&typ);
if unacceptable_types.contains(&inner_qn) {
return Err(ConvertError::TypeContainingForwardDeclaration(inner_qn));
}
if let Some(more_generics) = typ.path.segments.last() {
confirm_inner_type_is_acceptable_generic_payload(
&more_generics.arguments,
desc,
&HashSet::new(),
)?;
}
}
_ => {
return Err(ConvertError::TemplatedTypeContainingNonPathArg(
desc.clone(),
))
}
}
}
Ok(())
}
}
}
pub(crate) fn ensure_pointee_is_valid(ptr: &TypePtr) -> Result<(), ConvertError> {
match *ptr.elem {
Type::Path(..) => Ok(()),
_ => Err(ConvertError::InvalidPointee),
}
}