use std::{
borrow::Cow,
cmp,
collections::HashSet,
fmt,
hash,
iter,
};
use clang::Entity;
use maplit::hashmap;
use once_cell::sync::Lazy;
use crate::{
CompiledInterpolation,
DefaultElement,
DefinitionLocation,
DependentTypeMode,
Element,
EntityElement,
EntityExt,
Field,
Func,
FunctionTypeHint,
GeneratedElement,
GeneratorEnv,
get_debug,
IteratorExt,
settings,
StrExt,
TypeRef,
};
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
pub enum Kind {
Simple,
Boxed,
System,
Excluded,
}
#[derive(Clone)]
pub struct Class<'tu, 'g> {
entity: Entity<'tu>,
elaborate_name: Option<String>,
gen_env: & 'g GeneratorEnv < 'tu >,
}
impl<'tu, 'g> Class<'tu, 'g> {
pub fn new(entity: Entity<'tu>, gen_env: &'g GeneratorEnv<'tu>) -> Self {
Self { entity, elaborate_name: None, gen_env }
}
pub fn new_ext(entity: Entity<'tu>, elaborate_name: Option<String>, gen_env: &'g GeneratorEnv<'tu>) -> Self {
Self { entity, elaborate_name, gen_env }
}
fn kind(&self) -> Kind {
if settings::ELEMENT_EXCLUDE.is_match(self.cpp_fullname().as_ref()) {
return Kind::Excluded
}
match self.gen_env.get_export_config(self.entity).map(|c| c.simple) {
Some(true) => Kind::Simple,
Some(false) => Kind::Boxed,
None => {
if self.is_system() {
Kind::System
} else if let Some(kind) = self.gen_env.get_class_kind(self.entity) {
kind
} else {
Kind::Excluded
}
}
}
}
pub fn type_ref(&self) -> TypeRef<'tu, 'g> {
TypeRef::new(self.entity.get_type().expect("Can't get class type"), self.gen_env)
}
pub fn detect_class_simplicity(&self) -> bool {
!self.has_bases()
&& !self.has_descendants()
&& self.fields().into_iter()
.map(|f| f.type_ref())
.all(|t| t.is_copy())
}
pub fn as_template(&self) -> Option<Class<'tu, 'g>> {
self.entity.get_template()
.map(|t| Class::new(t, self.gen_env))
}
pub fn is_simple(&self) -> bool {
self.kind() == Kind::Simple
}
pub fn is_boxed(&self) -> bool {
self.kind() == Kind::Boxed
}
pub fn is_template(&self) -> bool {
self.entity.get_template_kind().is_some()
}
pub fn is_template_specialization(&self) -> bool {
self.entity.get_template().is_some()
}
pub fn is_abstract(&self) -> bool {
self.entity.is_abstract_record()
}
pub fn is_trait(&self) -> bool {
self.is_boxed()
}
pub fn is_by_ptr(&self) -> bool {
match self.kind() {
Kind::Boxed => true,
Kind::Simple | Kind::System | Kind::Excluded => false,
}
}
pub fn rust_trait_name(&self, full: bool) -> Cow<str> {
let mut out = self.rust_name(full);
if self.is_trait() && !self.is_abstract() {
out.to_mut().push_str("Trait");
}
out
}
pub fn rust_trait_localname(&self) -> Cow<str> {
self.rust_trait_name(false)
}
pub fn rust_trait_fullname(&self) -> Cow<str> {
self.rust_trait_name(true)
}
pub fn has_bases(&self) -> bool {
self.entity.walk_bases_while(|_| false)
}
pub fn bases(&self) -> Vec<Class<'tu, 'g>> {
let mut out = vec![];
let entity = if let Some(entity) = self.entity.get_template() {
entity
} else {
self.entity
};
entity.walk_bases_while(|child| {
out.push(Class::new(child.get_definition().expect("Can't get base class definition"), self.gen_env));
true
});
out
}
pub fn all_bases(&self) -> HashSet<Class<'tu, 'g>> {
self.bases().into_iter()
.map(|b| {
let mut out = b.all_bases();
out.insert(b);
out
})
.flatten()
.collect()
}
pub fn has_descendants(&self) -> bool {
self.gen_env.has_descendants(self.entity)
}
pub fn has_methods(&self) -> bool {
self.entity.walk_methods_while(|_| false)
}
pub fn methods(&self) -> Vec<Func<'tu, 'g>> {
let mut out = Vec::with_capacity(64);
self.entity.walk_methods_while(|child| {
let func = Func::new(child, self.gen_env);
if func.is_generic() {
if let Some(specs) = settings::FUNC_SPECIALIZE.get(func.identifier().as_ref()) {
for spec in specs {
out.push(Func::new_ext(child, FunctionTypeHint::Specialized(spec), None, self.gen_env));
}
return true;
}
}
out.push(func);
true
});
out
}
pub fn has_fields(&self) -> bool {
self.entity.walk_fields_while(|_| false)
}
pub fn fields(&self) -> Vec<Field<'tu, 'g>> {
let mut out = Vec::with_capacity(32);
self.entity.walk_fields_while(|child| {
out.push(Field::new(child, self.gen_env));
true
});
out
}
pub fn is_definition(&self) -> bool {
let class_loc = self.entity.get_location();
let def_loc = self.entity.get_definition().and_then(|d| d.get_location());
match (class_loc, def_loc) {
(Some(class_loc), Some(def_loc)) => {
class_loc == def_loc
}
(_, None) => {
false
}
_ => {
true
}
}
}
pub fn dependent_types(&self) -> Vec<Box<dyn GeneratedElement + 'g>> {
self.fields().into_iter()
.filter(|f| !f.is_excluded())
.map(|f| f.type_ref().dependent_types_with_mode(DependentTypeMode::ForReturn(DefinitionLocation::Module)))
.flatten()
.chain(self.methods().into_iter()
.filter(|m| !m.is_excluded())
.map(|m| m.dependent_types())
.flatten()
)
.collect()
}
pub fn cpp_method_call_name(&self, method_name: &str) -> String {
if self.is_by_ptr() {
format!("reinterpret_cast<{typ}*>(instance)->{name}", typ=self.cpp_fullname(), name=method_name)
} else {
format!("reinterpret_cast<{typ}*>(&instance)->{name}", typ=self.cpp_fullname(), name=method_name)
}
}
pub fn field_methods(&self, fields: impl Iterator<Item=&'g Field<'tu, 'g>>) -> Vec<Func<'tu, 'g>> {
let mut out = Vec::with_capacity(fields.size_hint().1.map_or(8, |x| x * 2));
out.extend(fields
.map(|fld| {
iter::from_fn({
let fld_type_ref = fld.type_ref();
let mut need_to_yield_read = true;
let mut need_to_yield_write = !fld_type_ref.is_const() && !fld_type_ref.as_fixed_array().is_some();
move || {
if need_to_yield_read {
need_to_yield_read = false;
Some(Func::new(fld.entity(), self.gen_env))
} else if need_to_yield_write {
need_to_yield_write = false;
Some(Func::new_ext(fld.entity(), FunctionTypeHint::FieldSetter, None, self.gen_env))
} else {
None
}
}
})
})
.flatten()
);
out
}
fn gen_rust_class(&self, opencv_version: &str) -> String {
static BOXED_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/boxed.tpl.rs").compile_interpolation()
);
static IMPL_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/impl.tpl.rs").compile_interpolation()
);
static SIMPLE_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/simple.tpl.rs").compile_interpolation()
);
static SIMPLE_FIELD_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/simple_field.tpl.rs").compile_interpolation()
);
static BASE_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/base.tpl.rs").compile_interpolation()
);
static SIMPLE_BASE_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/simple_base.tpl.rs").compile_interpolation()
);
static TRAIT_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/trait.tpl.rs").compile_interpolation()
);
static TRAIT_DYN_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/trait_dyn.tpl.rs").compile_interpolation()
);
let type_ref = self.type_ref();
let is_trait = self.is_trait();
let is_abstract = self.is_abstract();
let is_simple = self.is_simple();
let mut out = String::new();
let mut bases = self.all_bases().into_iter()
.filter(|b| !b.is_excluded() && !b.is_simple())
.collect::<Vec<_>>();
bases.sort_unstable_by(|a, b| a.cpp_fullname().cmp(&b.cpp_fullname()));
let fields = self.fields();
let mut methods = if is_simple {
vec![]
} else {
self.field_methods(fields.iter())
};
methods.extend_from_slice(self.methods().as_slice());
if is_trait {
let mut trait_bases = bases.iter()
.map(|x| x.rust_trait_fullname().into_owned())
.join(" + ");
if !trait_bases.is_empty() {
trait_bases = format!(": {}", trait_bases);
};
let trait_methods = Func::rust_generate_funcs(
methods.iter().filter(|m| m.as_instance_method().is_some()),
opencv_version
);
let dyn_impl = if is_abstract {
let methods = Func::rust_generate_funcs(
methods.iter().filter(|m| m.as_static_method().is_some()),
opencv_version
);
if !methods.is_empty() {
TRAIT_DYN_TPL.interpolate(&hashmap! {
"rust_local" => self.rust_trait_localname(),
"methods" => methods.into(),
})
} else {
String::new()
}
} else {
String::new()
};
out = TRAIT_TPL.interpolate(&hashmap! {
"doc_comment" => Cow::Owned(self.rendered_doc_comment(opencv_version)),
"debug" => get_debug(self).into(),
"rust_trait_local" => self.rust_trait_localname(),
"rust_local" => type_ref.rust_local(),
"rust_extern" => type_ref.rust_extern(),
"bases" => trait_bases.into(),
"trait_methods" => trait_methods.into(),
"dyn_impl" => dyn_impl.into(),
});
}
if !is_abstract {
if is_trait {
bases.push(self.clone());
bases.sort_unstable_by(|a, b| a.cpp_fullname().cmp(&b.cpp_fullname()));
}
let bases = bases.into_iter()
.map(|base| {
let base_type_ref = base.type_ref();
let tpl = if is_simple {
&SIMPLE_BASE_TPL
} else {
&BASE_TPL
};
tpl.interpolate(&hashmap! {
"base_rust_full" => base.rust_trait_fullname(),
"rust_local" => type_ref.rust_local(),
"base_rust_local" => base_type_ref.rust_local(),
"base_rust_extern" => base_type_ref.rust_extern(),
})
})
.collect::<Vec<_>>();
let fields = if is_simple {
let mut out: Vec<_> = self.fields().into_iter()
.map(|f| {
let type_ref = f.type_ref();
let mut typ = type_ref.rust_full();
if type_ref.as_fixed_array().is_some() {
if let Some(new_typ) = typ.strip_str_prefix("&mut ") {
typ = new_typ.to_string().into()
}
}
SIMPLE_FIELD_TPL.interpolate(&hashmap! {
"doc_comment" => Cow::Owned(f.rendered_doc_comment(opencv_version)),
"visibility" => "pub ".into(),
"name" => f.rust_leafname(),
"type" => typ,
})
})
.collect();
if out.is_empty() {
out.push(
SIMPLE_FIELD_TPL.interpolate(&hashmap! {
"doc_comment" => "",
"visibility" => "",
"name" => "__rust_private",
"type" => "[u8; 0]",
})
)
}
out
} else {
vec![]
};
let mut inherent_methods = String::with_capacity(512 * methods.len());
let is_trait = self.is_trait();
inherent_methods.push_str(&if is_trait {
Func::rust_generate_funcs(
methods.iter()
.filter(|m| m.as_static_method().is_some() || m.as_constructor().is_some()),
opencv_version
)
} else {
Func::rust_generate_funcs(&methods, opencv_version)
});
let tpl = if is_simple {
&SIMPLE_TPL
} else {
&BOXED_TPL
};
out += &tpl.interpolate(&hashmap! {
"doc_comment" => Cow::Owned(self.rendered_doc_comment(opencv_version)),
"debug" => get_debug(self).into(),
"rust_local" => type_ref.rust_local(),
"rust_extern" => type_ref.rust_extern(),
"fields" => fields.join("").into(),
"bases" => bases.join("").into(),
"impl" => IMPL_TPL.interpolate(&hashmap! {
"rust_local" => self.rust_localname(),
"methods" => inherent_methods.into(),
}).into()
});
}
out
}
fn gen_rust_exports_boxed(&self) -> String {
let fields = self.fields();
let mut out = String::with_capacity((fields.len() + 1) * 128);
for func in self.field_methods(fields.iter().filter(|f| !f.is_excluded())) {
if !func.is_excluded() {
out += &func.gen_rust_exports();
}
}
out
}
fn gen_cpp_boxed(&self) -> String {
static BOXED_CPP_TPL: Lazy<CompiledInterpolation> = Lazy::new(
|| include_str!("../tpl/class/boxed_dtor.tpl.cpp").compile_interpolation()
);
let fields = self.fields();
let mut out = String::with_capacity(fields.len() * 512);
for func in self.field_methods(fields.iter().filter(|f| !f.is_excluded())) {
if !func.is_excluded() {
out += &func.gen_cpp();
}
}
if !self.is_abstract() {
let type_ref = self.type_ref();
out += &BOXED_CPP_TPL.interpolate(&hashmap! {
"rust_local" => type_ref.rust_local(),
"cpp_full" => type_ref.cpp_full(),
"cpp_extern" => type_ref.cpp_extern(),
})
}
out
}
}
impl<'tu> EntityElement<'tu> for Class<'tu, '_> {
fn entity(&self) -> Entity<'tu> {
self.entity
}
}
impl Element for Class<'_, '_> {
fn is_excluded(&self) -> bool {
DefaultElement::is_excluded(self)
|| match self.kind() { Kind::Excluded => true, _ => false }
}
fn is_ignored(&self) -> bool {
DefaultElement::is_ignored(self)
|| self.is_template()
|| self.is_template_specialization() && !settings::IMPLEMENTED_GENERICS.contains(self.cpp_fullname().as_ref())
|| !self.is_template_specialization() && !self.is_definition()
}
fn is_system(&self) -> bool {
DefaultElement::is_system(self)
}
fn is_public(&self) -> bool {
DefaultElement::is_public(self)
}
fn usr(&self) -> Cow<str> {
DefaultElement::usr(self)
}
fn rendered_doc_comment_with_prefix(&self, prefix: &str, opencv_version: &str) -> String {
DefaultElement::rendered_doc_comment_with_prefix(self, prefix, opencv_version)
}
fn cpp_namespace(&self) -> Cow<str> {
if self.elaborate_name.is_some() {
let name = self.cpp_fullname();
if let Some(idx) = name.rfind("::") {
name[..idx].to_string().into()
} else {
name
}
} else {
DefaultElement::cpp_namespace(self)
}
}
fn cpp_localname(&self) -> Cow<str> {
if self.elaborate_name.is_some() {
let name = self.cpp_fullname();
const SEP: &str = "::";
if let Some(idx) = name.rfind(SEP) {
name[idx + SEP.len()..].to_string().into()
} else {
name
}
} else {
DefaultElement::cpp_localname(self)
}
}
fn cpp_fullname(&self) -> Cow<str> {
if let Some(elaborate_name) = &self.elaborate_name {
elaborate_name.into()
} else {
DefaultElement::cpp_fullname(self)
}
}
fn rust_module(&self) -> Cow<str> {
DefaultElement::rust_module(self)
}
fn rust_leafname(&self) -> Cow<str> {
self.cpp_localname()
}
fn rust_localname(&self) -> Cow<str> {
DefaultElement::rust_localname(self)
}
}
impl GeneratedElement for Class<'_, '_> {
fn element_safe_id(&self) -> String {
format!("{}-{}", self.rust_module(), self.rust_localname())
}
fn gen_rust(&self, opencv_version: &str) -> String {
match self.kind() {
Kind::Simple => {
self.gen_rust_class(opencv_version)
}
Kind::Boxed => {
self.gen_rust_class(opencv_version)
},
Kind::System | Kind::Excluded => {
"".to_string()
},
}
}
fn gen_rust_exports(&self) -> String {
let out = match self.kind() {
Kind::Simple => {
"".to_string()
}
Kind::Boxed => {
self.gen_rust_exports_boxed()
},
Kind::System | Kind::Excluded => {
"".to_string()
},
};
let mut methods = self.methods().into_iter()
.filter(|m| !m.is_excluded())
.map(|m| m.gen_rust_exports());
out + &methods.join("")
}
fn gen_cpp(&self) -> String {
let out = match self.kind() {
Kind::Simple => {
"".to_string()
}
Kind::Boxed => {
self.gen_cpp_boxed()
},
Kind::System | Kind::Excluded => {
"".to_string()
},
};
let methods: Vec<_> = self.methods().into_iter()
.filter(|m| !m.is_excluded())
.map(|m| m.gen_cpp())
.collect();
out + &methods.join("")
}
}
impl hash::Hash for Class<'_, '_> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.entity.hash(state)
}
}
impl cmp::PartialEq for Class<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.entity.eq(&other.entity)
}
}
impl cmp::Eq for Class<'_, '_> {}
impl fmt::Display for Class<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.entity.get_display_name().expect("Can't get display name"))
}
}
impl fmt::Debug for Class<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut props = vec![];
if self.is_template() {
props.push("template");
}
if self.is_template_specialization() {
props.push("template_specialization");
}
if self.is_abstract() {
props.push("abstract");
}
if self.is_trait() {
props.push("trait");
}
if self.is_by_ptr() {
props.push("by_ptr");
}
if self.is_simple() {
props.push("simple");
}
if self.has_descendants() {
props.push("has_descendants");
}
if self.has_methods() {
props.push("has_methods");
}
if self.has_fields() {
props.push("has_fields");
}
if self.has_bases() {
props.push("has_bases");
}
let mut debug_struct = f.debug_struct("Class");
self.update_debug_struct(&mut debug_struct)
.field("export_config", &self.gen_env.get_export_config(self.entity))
.field("kind", &self.kind())
.field("props", &props.join(", "))
.field("as_template", &self.as_template())
.finish()
}
}