use std::borrow::Cow;
use std::fmt;
use std::fmt::Write;
use crate::type_ref::{
Constness, ConstnessOverride, Dir, ExternDir, FishStyle, Kind, NameStyle, Signedness, StrEnc, StrType, TypeRef,
};
use crate::{IteratorExt, StringExt};
use super::element::RustElement;
use super::function::FunctionExt;
use super::renderer::RustRenderer;
use super::rust_disambiguate_names;
use super::smart_ptr::SmartPtrExt;
use super::tuple::TupleExt;
use super::vector::VectorExt;
pub trait TypeRefExt {
fn format_as_array(&self, elem_type: &str, size: Option<usize>) -> String;
fn rust_safe_id(&self, add_const: bool) -> Cow<str>;
fn rust_module(&self) -> Cow<str>;
fn rust_simple_name(&self) -> String;
fn rust_name(&self, name_style: NameStyle) -> Cow<str>;
fn rust_name_ext(&self, name_style: NameStyle, lifetime: Lifetime) -> Cow<str>;
fn rust_self_func_decl(&self, method_constness: Constness) -> String;
fn rust_arg_func_decl(&self, name: &str) -> String;
fn rust_extern_self_func_decl(&self, method_constness: Constness) -> String;
fn rust_extern_arg_func_decl(&self, name: &str, constness: ConstnessOverride) -> String;
fn rust_arg_pre_call(&self, name: &str, is_function_infallible: bool) -> String;
fn rust_userdata_pre_call(&self, name: &str, callback_name: &str) -> String;
fn rust_self_func_call(&self, method_constness: Constness) -> String;
fn rust_arg_func_call(&self, name: &str, constness: ConstnessOverride) -> String;
fn rust_arg_forward(&self, name: &str) -> String;
fn rust_arg_post_call(&self, name: &str, _is_function_infallible: bool) -> String;
fn rust_extern(&self, dir: ExternDir) -> Cow<str>;
fn rust_return(&self, turbo_fish_style: FishStyle, is_static_func: bool) -> Cow<str>;
fn rust_extern_return_fallible(&self) -> Cow<str>;
fn rust_lifetime_count(&self) -> usize;
fn cpp_arg_func_decl(&self, name: &str) -> String;
fn cpp_arg_pre_call(&self, name: &str) -> String;
fn cpp_arg_func_call<'a>(&self, name: impl Into<Cow<'a, str>>) -> Cow<'a, str>;
fn cpp_arg_post_call(&self, name: &str) -> String;
fn cpp_arg_cleanup(&self, name: &str) -> String;
}
impl<'tu, 'ge> TypeRefExt for TypeRef<'tu, 'ge> {
fn format_as_array(&self, elem_type: &str, size: Option<usize>) -> String {
format!(
"&{cnst}[{elem_type}{size}]",
cnst = self.constness().rust_qual(false),
size = size.map_or_else(|| "".to_string(), |s| format!("; {s}"))
)
}
fn rust_safe_id(&self, add_const: bool) -> Cow<str> {
let mut out = String::with_capacity(64);
let kind = self.kind();
if add_const && self.clang_constness().is_const() {
out.push_str("const_");
}
out.push_str(&match kind {
Kind::Array(inner, ..) => inner.rust_safe_id(add_const).into_owned() + "_X",
Kind::StdVector(vec) => vec.rust_localalias().into_owned(),
Kind::StdTuple(tuple) => tuple.rust_localalias().into_owned(),
Kind::Pointer(inner) => {
let mut inner_safe_id: String = inner.rust_safe_id(add_const).into_owned();
if !self.is_extern_by_ptr() {
inner_safe_id += "_X";
}
inner_safe_id
}
Kind::Reference(inner) | Kind::RValueReference(inner) => inner.rust_safe_id(add_const).into_owned(),
Kind::SmartPtr(ptr) => ptr.rust_localalias().into_owned(),
Kind::Class(cls) => cls.rust_name(NameStyle::decl()).into_owned(),
Kind::Primitive(..) | Kind::Enum(..) | Kind::Function(..) | Kind::Typedef(..) | Kind::Generic(..) | Kind::Ignored => {
self.rust_name(NameStyle::decl()).into_owned()
}
});
out.cleanup_name();
out.into()
}
fn rust_module(&self) -> Cow<str> {
match self.kind() {
Kind::Primitive(..) => "core".into(),
Kind::StdVector(vec) => vec.rust_element_module().into_owned().into(),
Kind::StdTuple(tuple) => tuple.rust_element_module().into_owned().into(),
Kind::Array(inner, ..) | Kind::Pointer(inner) | Kind::Reference(inner) | Kind::RValueReference(inner) => {
inner.rust_module().into_owned().into()
}
Kind::SmartPtr(ptr) => ptr.rust_module().into_owned().into(),
Kind::Class(cls) => cls.rust_module().into_owned().into(),
Kind::Enum(enm) => enm.rust_module().into_owned().into(),
Kind::Function(..) => {
"core".into() }
Kind::Typedef(tdef) => tdef.rust_module().into_owned().into(),
Kind::Generic(..) | Kind::Ignored => "core".into(),
}
}
fn rust_simple_name(&self) -> String {
let maybe_ptr = self.as_pointer().or_else(|| self.as_reference());
let type_ref = if let Some(inner) = maybe_ptr.as_ref() {
inner
} else {
self
};
type_ref.rust_name(NameStyle::Declaration).to_lowercase()
}
fn rust_name(&self, name_style: NameStyle) -> Cow<str> {
self.rust_name_ext(name_style, Lifetime::elided())
}
fn rust_name_ext(&self, name_style: NameStyle, lifetime: Lifetime) -> Cow<str> {
self.render(RustRenderer::new(name_style, lifetime, self.is_rust_by_ptr()))
}
fn rust_self_func_decl(&self, method_constness: Constness) -> String {
if self.is_extern_by_ptr() {
if method_constness.is_const() {
"&self".to_string()
} else {
"&mut self".to_string()
}
} else {
"self".to_string()
}
}
fn rust_arg_func_decl(&self, name: &str) -> String {
#[allow(clippy::never_loop)] let typ = 'decl_type: loop {
if let Some(dir) = self.as_string() {
break 'decl_type match dir {
Dir::In(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr) => "&str".into(),
Dir::In(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary)) => "&[u8]".into(),
Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr) => {
"&mut String".into()
}
Dir::Out(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary)) => "&mut Vec<u8>".into(),
};
} else if self.is_input_array() {
break 'decl_type "&dyn core::ToInputArray".into();
} else if self.is_output_array() {
break 'decl_type "&mut dyn core::ToOutputArray".into();
} else if self.is_input_output_array() {
break 'decl_type "&mut dyn core::ToInputOutputArray".into();
} else if let Some((_, size)) = self.as_string_array() {
break 'decl_type self.format_as_array("&str", size).into();
} else if self.as_char8().is_some() {
break 'decl_type "char".into();
}
break 'decl_type self.rust_name(NameStyle::ref_());
};
let cnst = Constness::from_is_mut(
self.is_by_move()
|| self.is_extern_by_ptr()
&& self.constness().is_mut()
&& !self.as_pointer().is_some()
&& !self.as_reference().is_some(),
);
format!("{cnst}{name}: {typ}", cnst = cnst.rust_qual(false))
}
fn rust_extern_self_func_decl(&self, method_constness: Constness) -> String {
self.rust_extern_arg_func_decl("instance", ConstnessOverride::force(method_constness))
}
fn rust_extern_arg_func_decl(&self, name: &str, constness: ConstnessOverride) -> String {
format!("{name}: {typ}", typ = self.rust_extern(ExternDir::ToCpp(constness)))
}
fn rust_arg_pre_call(&self, name: &str, is_function_infallible: bool) -> String {
if let Some(dir) = self.as_string() {
return match dir {
Dir::In(_) => {
let mut flags = vec![];
if is_function_infallible {
flags.push("nofail")
}
if self.constness().is_mut() {
flags.push("mut")
}
let mut flags = flags.join(" ");
if !flags.is_empty() {
flags.push(' ');
}
format!("extern_container_arg!({flags}{name})")
}
Dir::Out(_) => {
format!("string_arg_output_send!(via {name}_via)")
}
};
} else if self.is_input_array() || self.is_output_array() || self.is_input_output_array() {
return format!("extern_container_arg!({name})");
} else if self.as_string_array().is_some() {
return if self.constness().is_const() {
format!("string_array_arg!({name})")
} else {
format!("string_array_arg_mut!({name})")
};
} else if let Some(func) = self.as_function() {
let args = rust_disambiguate_names(func.arguments()).collect::<Vec<_>>();
if let Some((userdata_name, _)) = args.iter().find(|(_, f)| f.is_user_data()).cloned() {
let ret = func.return_type();
let tramp_args = args
.into_iter()
.map(|(name, a)| a.type_ref().rust_extern_arg_func_decl(&name, ConstnessOverride::No))
.join(", ");
let fw_args = rust_disambiguate_names(func.rust_arguments())
.map(|(name, a)| a.type_ref().rust_extern_arg_func_decl(&name, ConstnessOverride::No))
.join(", ");
return format!(
"callback_arg!({name}_trampoline({tramp_args}) -> {tramp_ret} => {userdata_name} in callbacks => {name}({fw_args}) -> {fw_ret})",
tramp_ret=ret.rust_extern(ExternDir::FromCpp),
fw_ret=ret.rust_extern(ExternDir::FromCpp),
);
}
}
"".to_string()
}
fn rust_userdata_pre_call(&self, name: &str, callback_name: &str) -> String {
format!("userdata_arg!({name} in callbacks => {callback_name})")
}
fn rust_self_func_call(&self, method_constness: Constness) -> String {
self.rust_arg_func_call(
"self",
if method_constness.is_const() {
ConstnessOverride::Const
} else {
ConstnessOverride::No
},
)
}
fn rust_arg_func_call(&self, name: &str, constness: ConstnessOverride) -> String {
if let Some(dir) = self.as_string() {
return match dir {
Dir::In(_) => {
if constness.with(self.constness()).is_const() {
format!("{name}.opencv_as_extern()")
} else {
format!("{name}.opencv_as_extern_mut()")
}
}
Dir::Out(_) => format!("&mut {name}_via"),
};
}
if self.as_reference().map_or(false, |inner| {
(inner.as_simple_class().is_some() || inner.is_enum())
&& (constness.with(inner.constness()).is_const() || self.is_by_move())
}) {
let cnst = constness.with(self.constness());
return format!("&{cnst}{name}", cnst = cnst.rust_qual(false));
}
if self.as_simple_class().is_some() {
return format!("{name}.opencv_as_extern()");
}
if self.is_extern_by_ptr() {
let typ = self.source();
let by_ptr = if constness.with(self.constness()).is_const() {
format!("{name}.as_raw_{rust_safe_id}()", rust_safe_id = typ.rust_safe_id(false))
} else {
format!("{name}.as_raw_mut_{rust_safe_id}()", rust_safe_id = typ.rust_safe_id(false))
};
return if self.is_nullable() {
format!(
"{name}.map_or({null_ptr}, |{name}| {by_ptr})",
null_ptr = constness.with(self.constness()).rust_null_ptr()
)
} else {
by_ptr
};
}
if self.as_variable_array().is_some() {
let arr = if constness.with(self.constness()).is_const() {
format!("{name}.as_ptr()")
} else {
format!("{name}.as_mut_ptr()")
};
return if self.is_nullable() {
format!(
"{name}.map_or({null_ptr}, |{name}| {arr})",
null_ptr = constness.with(self.constness()).rust_null_ptr()
)
} else {
arr
};
}
if let Some(func) = self.as_function() {
if func.arguments().into_iter().any(|a| a.is_user_data()) {
return format!("{name}_trampoline");
}
}
if let Some(inner) = self.as_pointer() {
if inner.as_pointer().is_some() {
return format!(
"{name} as *{cnst} _ as *{cnst} *{const_inner} _",
cnst = self.clang_constness().rust_qual(true),
const_inner = inner.clang_constness().rust_qual(true)
);
}
}
if self.is_nullable() && (self.as_reference().is_some() || self.as_pointer().is_some()) {
let arg = if constness.with(self.constness()).is_const() {
format!("{name} as *const _")
} else {
format!("{name} as *mut _")
};
return format!(
"{name}.map_or({null_ptr}, |{name}| {arg})",
null_ptr = constness.with(self.constness()).rust_null_ptr(),
);
}
match self.as_char8() {
Some(Signedness::Unsigned) => {
return format!("u8::try_from({name})?");
}
Some(Signedness::Signed) => {
return format!("u8::try_from({name})? as i8");
}
None => {}
}
name.to_string()
}
fn rust_arg_forward(&self, name: &str) -> String {
name.to_string()
}
fn rust_arg_post_call(&self, name: &str, _is_function_infallible: bool) -> String {
match self.as_string() {
Some(Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr)) => {
format!("string_arg_output_receive!({name}_via => {name})")
}
Some(Dir::Out(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary))) => {
format!("byte_string_arg_output_receive!({name}_via => {name})")
}
_ => "".to_string(),
}
}
fn rust_extern(&self, dir: ExternDir) -> Cow<str> {
let constness = match dir {
ExternDir::Pure => self.constness(),
ExternDir::ToCpp(constness) => constness.with(self.constness()),
ExternDir::FromCpp => Constness::Mut,
};
#[allow(clippy::never_loop)] 'typ: loop {
if let Some(arg_dir) = self.as_string() {
break 'typ match dir {
ExternDir::ToCpp(_) | ExternDir::Pure => match arg_dir {
Dir::In(_) => format!("*{}c_char", constness.rust_qual(true)).into(),
Dir::Out(_) => "*mut *mut c_void".into(),
},
ExternDir::FromCpp => "*mut c_void".into(),
};
}
if self.is_extern_by_ptr() {
break 'typ if constness.is_const() {
"*const c_void"
} else {
"*mut c_void"
}
.into();
}
if let Some(inner) = self.as_pointer().or_else(|| self.as_reference()) {
let mut out = String::with_capacity(64);
write!(out, "*{}", self.constness().rust_qual(true)).expect("Impossible");
if inner.is_void() {
out += "c_void";
} else if self.as_string().is_some() {
out += "c_char";
} else {
out += inner.rust_extern(ExternDir::Pure).as_ref()
}
break 'typ out.into();
}
if let Some((elem, len)) = self.as_fixed_array() {
break 'typ format!(
"*{cnst}[{typ}; {len}]",
cnst = self.constness().rust_qual(true),
typ = elem.rust_extern(ExternDir::Pure),
)
.into();
}
if let Some(elem) = self.as_variable_array() {
let typ = if matches!(elem.as_string(), Some(Dir::Out(StrType::CharPtr))) {
format!("*{cnst}c_char", cnst = elem.clang_constness().rust_qual(true)).into()
} else {
elem.rust_extern(ExternDir::Pure)
};
break 'typ format!("*{cnst}{typ}", cnst = self.constness().rust_qual(true)).into();
}
if let Some(func) = self.as_function() {
break 'typ func.rust_extern().into_owned().into();
}
if self.as_simple_class().is_some() && matches!(dir, ExternDir::ToCpp(_)) {
break 'typ format!("*const {}", self.rust_name(NameStyle::ref_())).into();
}
break 'typ self.rust_name(NameStyle::ref_());
}
}
fn rust_return(&self, turbo_fish_style: FishStyle, is_static_func: bool) -> Cow<str> {
if self.as_abstract_class_ptr().is_some() {
format!(
"types::AbstractRef{mut_suf}{fish}<{lt}{typ}>",
mut_suf = if self.constness().is_const() {
""
} else {
"Mut"
},
fish = turbo_fish_style.rust_qual(),
lt = if is_static_func {
"'static, "
} else {
""
},
typ = self.source().rust_name(NameStyle::Reference(turbo_fish_style)),
)
.into()
} else if self.is_extern_by_ptr() {
self
.source()
.rust_name(NameStyle::Reference(turbo_fish_style))
.into_owned()
.into()
} else {
self.rust_name(NameStyle::Reference(turbo_fish_style))
}
}
fn rust_extern_return_fallible(&self) -> Cow<str> {
if self.is_void() {
"Result_void".into()
} else {
format!("Result<{ext}>", ext = self.rust_extern(ExternDir::FromCpp)).into()
}
}
fn rust_lifetime_count(&self) -> usize {
if self.as_string().is_some() {
0
} else {
match self.kind() {
Kind::Pointer(inner) => {
if inner.is_void() {
0
} else {
1 + inner.rust_lifetime_count()
}
}
Kind::Reference(inner) => {
if !((inner.as_simple_class().is_some() || inner.is_enum()) && inner.clang_constness().is_const()) {
1 + inner.rust_lifetime_count()
} else {
0
}
}
Kind::Typedef(tdef) => tdef.underlying_type_ref().rust_lifetime_count(),
_ => 0,
}
}
}
fn cpp_arg_func_decl(&self, name: &str) -> String {
if matches!(self.as_string(), Some(Dir::Out(_))) || self.as_simple_class().is_some() {
return format!("{typ}* {name}", typ = self.cpp_extern());
}
self.cpp_extern_with_name(name).into_owned()
}
fn cpp_arg_pre_call(&self, name: &str) -> String {
match self.as_string() {
Some(Dir::Out(StrType::StdString(_))) => {
format!("std::string {name}_out")
}
Some(Dir::Out(StrType::CvString(_))) => {
format!("cv::String {name}_out")
}
Some(Dir::Out(StrType::CharPtr)) => {
format!("char* {name}_out = new char[1024]()")
}
Some(Dir::In(_)) | None => "".to_string(),
}
}
fn cpp_arg_func_call<'a>(&self, name: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
let name = name.into();
match self.as_string() {
Some(Dir::Out(str_type)) => {
let ptr = if str_type != StrType::CharPtr && self.as_pointer().is_some() {
"&"
} else {
""
};
return format!("{ptr}{name}_out").into();
}
Some(Dir::In(StrType::StdString(_))) => {
return format!("std::string({name})").into();
}
Some(Dir::In(StrType::CvString(_))) => {
return format!("cv::String({name})").into();
}
Some(Dir::In(StrType::CharPtr)) | None => {}
}
if self.is_by_move() {
return format!("std::move(*{name})").into();
}
if self.is_extern_by_ptr() {
return if self.as_pointer().is_some() {
name
} else {
format!("*{name}").into()
};
}
if self.as_reference().is_some() || self.as_fixed_array().is_some() || self.as_simple_class().is_some() {
return format!("*{name}").into();
}
name
}
fn cpp_arg_post_call(&self, name: &str) -> String {
match self.as_string() {
Some(Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text))) => {
format!("*{name} = ocvrs_create_string({name}_out.c_str())")
}
Some(Dir::Out(StrType::CharPtr)) => {
format!("*{name} = ocvrs_create_string({name}_out)")
}
Some(Dir::Out(StrType::StdString(StrEnc::Binary))) => {
format!("*{name} = ocvrs_create_byte_string({name}_out.data(), {name}_out.size())")
}
Some(Dir::Out(StrType::CvString(StrEnc::Binary))) => {
format!("*{name} = ocvrs_create_byte_string({name}_out.begin(), {name}_out.size())")
}
Some(Dir::In(_)) | None => "".to_string(),
}
}
fn cpp_arg_cleanup(&self, name: &str) -> String {
if let Some(Dir::Out(StrType::CharPtr)) = self.as_string() {
return format!("delete[] {name}_out");
}
"".to_string()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Lifetime {
Elided,
Static,
Explicit(u8),
}
impl Lifetime {
pub fn elided() -> Self {
Self::Elided
}
pub fn statik() -> Self {
Self::Static
}
pub fn explicit() -> Self {
Self::Explicit(0)
}
pub fn is_elided(&self) -> bool {
matches!(self, Self::Elided)
}
pub fn is_explicit(&self) -> bool {
matches!(self, Self::Explicit(_))
}
pub fn next(self) -> Option<Self> {
match self {
Self::Elided => Some(Self::Elided),
Self::Static => Some(Self::Static),
Self::Explicit(n) if n >= 25 => None,
Self::Explicit(n) => Some(Self::Explicit(n + 1)),
}
}
}
impl IntoIterator for Lifetime {
type Item = Lifetime;
type IntoIter = LifetimeIterator;
fn into_iter(self) -> LifetimeIterator {
LifetimeIterator {
cur_lifetime: Some(self),
}
}
}
impl fmt::Display for Lifetime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[inline]
fn write_align(f: &mut fmt::Formatter) -> fmt::Result {
if f.align().is_some() {
match f.fill() {
',' => f.write_str(", ")?,
' ' => f.write_char(' ')?,
_ => {}
}
}
Ok(())
}
match *self {
Self::Elided => Ok(()),
Self::Static => {
f.write_str("'static")?;
write_align(f)
}
Self::Explicit(n) if n >= 25 => {
panic!("Too many lifetimes")
}
Self::Explicit(n) => {
f.write_char('\'')?;
f.write_char(char::from(b'a' + n))?;
write_align(f)
}
}
}
}
pub struct LifetimeIterator {
cur_lifetime: Option<Lifetime>,
}
impl Iterator for LifetimeIterator {
type Item = Lifetime;
fn next(&mut self) -> Option<Self::Item> {
let out = self.cur_lifetime;
self.cur_lifetime = self.cur_lifetime.and_then(|l| l.next());
out
}
}