use std::{
borrow::Cow,
collections::HashMap,
fmt,
};
use clang::{Availability, Entity, EntityKind};
use once_cell::sync::Lazy;
use regex::Regex;
use crate::{
Class,
comment,
DefaultElement,
DefinitionLocation,
DependentTypeMode,
DependentType,
Element,
EntityElement,
EntityExt,
Field,
FieldTypeHint,
GeneratorEnv,
reserved_rename,
settings::{self, SliceHint},
StrExt,
StringExt,
type_ref::{Dir, StrType},
TypeRef,
};
#[derive(Debug, PartialEq)]
pub enum OperatorKind {
Unsupported,
Index,
Add,
Sub,
Mul,
Div,
Deref,
}
impl OperatorKind {
pub fn new(token: &str, arg_count: usize) -> Self {
match token.trim() {
"[]" => {
OperatorKind::Index
}
"+" => {
OperatorKind::Add
}
"-" => {
OperatorKind::Sub
}
"*" => {
if arg_count == 0 {
OperatorKind::Deref
} else {
OperatorKind::Mul
}
}
"/" => {
OperatorKind::Div
}
_ => {
OperatorKind::Unsupported
},
}
}
}
#[derive(Debug)]
pub enum Kind<'tu, 'ge> {
Function,
FunctionOperator(OperatorKind),
Constructor(Class<'tu, 'ge>),
InstanceMethod(Class<'tu, 'ge>),
StaticMethod(Class<'tu, 'ge>),
FieldAccessor(Class<'tu, 'ge>),
ConversionMethod(Class<'tu, 'ge>),
InstanceOperator(Class<'tu, 'ge>, OperatorKind),
GenericFunction,
GenericInstanceMethod(Class<'tu, 'ge>),
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum FunctionTypeHint {
None,
FieldSetter,
Specialized(&'static HashMap<&'static str, &'static str>),
}
#[derive(Clone)]
pub struct Func<'tu, 'ge> {
entity: Entity<'tu>,
type_hint: FunctionTypeHint,
name_hint: Option<&'tu str>,
gen_env: &'ge GeneratorEnv<'tu>,
}
impl<'tu, 'ge> Func<'tu, 'ge> {
pub fn new(entity: Entity<'tu>, gen_env: &'ge GeneratorEnv<'tu>) -> Self {
Self { entity, type_hint: FunctionTypeHint::None, name_hint: None, gen_env }
}
pub fn new_ext(entity: Entity<'tu>, type_hint: FunctionTypeHint, name_hint: Option<&'tu str>, gen_env: &'ge GeneratorEnv<'tu>) -> Self {
Self { entity, type_hint, name_hint, gen_env }
}
pub fn set_name_hint(&mut self, name_hint: Option<&'tu str>) {
self.name_hint = name_hint;
}
pub fn name_hint(&self) -> Option<&'tu str> {
self.name_hint
}
pub fn type_hint(&self) -> FunctionTypeHint {
self.type_hint
}
pub fn kind(&self) -> Kind<'tu, 'ge> {
const OPERATOR: &str = "operator";
match self.entity.get_kind() {
EntityKind::FunctionDecl => {
if let Some(operator) = self.entity.cpp_localname().strip_prefix(OPERATOR) {
let arg_count = self.entity.get_arguments().map_or(0, |v| v.len());
Kind::FunctionOperator(OperatorKind::new(operator.trim(), arg_count))
} else {
Kind::Function
}
}
EntityKind::Constructor => {
Kind::Constructor(Class::new(
self.entity.get_semantic_parent().expect("Can't get parent of constructor"),
self.gen_env,
))
}
EntityKind::Method => {
let class = Class::new(
self.entity.get_semantic_parent().expect("Can't get parent of method"),
self.gen_env,
);
if self.entity.is_static_method() {
Kind::StaticMethod(class)
} else if let Some(operator) = self.entity.cpp_localname().strip_prefix(OPERATOR) {
let arg_count = self.entity.get_arguments().map_or(0, |v| v.len());
Kind::InstanceOperator(class, OperatorKind::new(operator.trim(), arg_count))
} else {
Kind::InstanceMethod(class)
}
}
EntityKind::FieldDecl | EntityKind::VarDecl => {
Kind::FieldAccessor(Field::new(self.entity, self.gen_env).parent())
}
EntityKind::ConversionFunction => {
Kind::ConversionMethod(Class::new(
self.entity.get_semantic_parent().expect("Can't get parent of method"),
self.gen_env,
))
}
EntityKind::FunctionTemplate => {
match self.entity.get_template_kind() {
Some(EntityKind::Method) => {
Kind::GenericInstanceMethod(Class::new(
self.entity.get_semantic_parent().expect("Can't get parent of generic method"),
self.gen_env,
))
}
_ => {
Kind::GenericFunction
}
}
}
_ => unreachable!("Unknown function entity: {:#?}", self.entity)
}
}
pub fn as_constructor(&self) -> Option<Class<'tu, 'ge>> {
if let Kind::Constructor(out) = self.kind() {
Some(out)
} else {
None
}
}
pub fn as_instance_method(&self) -> Option<Class<'tu, 'ge>> {
match self.kind() {
Kind::InstanceMethod(out) | Kind::FieldAccessor(out) | Kind::GenericInstanceMethod(out)
| Kind::ConversionMethod(out) | Kind::InstanceOperator(out, ..) => {
Some(out)
}
_ => {
None
}
}
}
pub fn as_static_method(&self) -> Option<Class<'tu, 'ge>> {
if let Kind::StaticMethod(out) = self.kind() {
Some(out)
} else {
None
}
}
pub fn as_field_accessor(&self) -> Option<Field<'tu, 'ge>> {
if let Kind::FieldAccessor(..) = self.kind() {
Some(Field::new(self.entity, self.gen_env))
} else {
None
}
}
pub fn as_field_setter(&self) -> Option<Field<'tu, 'ge>> {
if self.as_field_accessor().is_some() && self.type_hint == FunctionTypeHint::FieldSetter {
Some(Field::new_ext(self.entity, FieldTypeHint::FieldSetter, self.gen_env))
} else {
None
}
}
pub fn as_conversion_method(&self) -> Option<Class<'tu, 'ge>> {
if let Kind::ConversionMethod(out) = self.kind() {
Some(out)
} else {
None
}
}
pub fn as_operator(&self) -> Option<(Option<Class<'tu, 'ge>>, OperatorKind)> {
match self.kind() {
Kind::FunctionOperator(kind) => {
Some((None, kind))
}
Kind::InstanceOperator(cls, kind) => {
Some((Some(cls), kind))
}
_ => {
None
}
}
}
pub fn is_const(&self) -> bool {
if settings::FORCE_CONSTANT_METHOD.contains(self.cpp_fullname().as_ref()) {
true
}
else if let Some(fld) = self.as_field_accessor() {
self.type_hint != FunctionTypeHint::FieldSetter && {
let type_ref = fld.type_ref();
type_ref.constness().is_const() || type_ref.is_copy() || matches!(type_ref.as_string(), Some(Dir::In(StrType::CvString)) | Some(Dir::In(StrType::StdString)))
}
} else {
self.entity.is_const_method()
}
}
pub fn is_abstract(&self) -> bool {
self.entity.is_pure_virtual_method()
}
pub fn is_generic(&self) -> bool {
match self.kind() {
Kind::GenericFunction | Kind::GenericInstanceMethod(..) => {
!self.as_specialized().is_some()
}
Kind::Function | Kind::Constructor(..) | Kind::InstanceMethod(..)
| Kind::StaticMethod(..) | Kind::FieldAccessor(..) | Kind::ConversionMethod(..)
| Kind::FunctionOperator(..) | Kind::InstanceOperator(..) => {
false
}
}
}
pub fn is_infallible(&self) -> bool {
self.as_field_accessor().is_some()
|| self.gen_env.get_export_config(self.entity).map_or(false, |e| e.no_except)
}
pub fn is_clone(&self) -> bool {
if self.rust_leafname() == "clone" {
if let Some(c) = self.as_instance_method() {
!self.has_arguments() && self.return_type().as_class().map_or(false, |r| r == c)
} else {
false
}
} else {
false
}
}
pub fn as_specialized(&self) -> Option<&'static HashMap<&'static str, &'static str>> {
if let FunctionTypeHint::Specialized(spec) = self.type_hint {
Some(spec)
} else {
None
}
}
pub fn return_type(&self) -> TypeRef<'tu, 'ge> {
match self.kind() {
Kind::Constructor(cls) => {
cls.type_ref()
}
Kind::Function | Kind::InstanceMethod(..) | Kind::StaticMethod(..)
| Kind::ConversionMethod(..) | Kind::GenericInstanceMethod(..) | Kind::GenericFunction
| Kind::FunctionOperator(..) | Kind::InstanceOperator(..) => {
let mut out = TypeRef::new(
self.entity.get_result_type().expect("Can't get return type"),
self.gen_env,
);
if let Some(type_ref) = out.as_reference() {
out = type_ref
}
if let Some(spec) = self.as_specialized() {
if out.is_generic() {
let spec_type = spec.get(out.base().cpp_full().as_ref())
.and_then(|s| self.gen_env.resolve_type(s));
if let Some(spec_type) = spec_type {
out.specialize(spec_type);
}
}
}
out
}
Kind::FieldAccessor(..) => {
if self.type_hint == FunctionTypeHint::FieldSetter {
TypeRef::new(self.gen_env.resolve_type("void").expect("Can't resolve void type"), self.gen_env)
} else {
Field::new(self.entity, self.gen_env).type_ref()
}
}
}
}
pub fn has_arguments(&self) -> bool {
!self.clang_arguments().is_empty()
}
pub fn clang_arguments(&self) -> Vec<Entity<'tu>> {
match self.kind() {
Kind::GenericFunction | Kind::GenericInstanceMethod(..) => {
let mut out = vec![];
self.entity.walk_children_while(|child| {
if child.get_kind() == EntityKind::ParmDecl {
out.push(child);
}
true
});
out
}
Kind::FieldAccessor(..) => {
if self.type_hint == FunctionTypeHint::FieldSetter {
vec![self.entity]
} else {
vec![]
}
}
_ => {
self.entity.get_arguments().expect("Can't get arguments")
}
}
}
pub fn arguments(&self) -> Vec<Field<'tu, 'ge>> {
let args = self.clang_arguments();
let empty_hashmap = HashMap::new();
let spec = if let Some(spec) = self.as_specialized() {
spec
} else {
&empty_hashmap
};
let args_len = args.len();
let func_name = self.cpp_fullname();
let is_field_setter = self.as_field_setter().is_some();
let slice_args = settings::SLICE_ARGUMENT.get(&(func_name.as_ref(), args_len));
args.into_iter()
.map(|a| {
if is_field_setter {
return Field::new_ext(a, FieldTypeHint::FieldSetter, self.gen_env)
}
if let Some(slice_arg) = slice_args.and_then(|o| o.get(a.rust_leafname().as_ref())) {
return match *slice_arg {
SliceHint::Slice => {
Field::new_ext(a, FieldTypeHint::Slice, self.gen_env)
},
SliceHint::NullableSlice => {
Field::new_ext(a, FieldTypeHint::NullableSlice, self.gen_env)
},
SliceHint::LenForSlice(slice, len_div) => {
Field::new_ext(a, FieldTypeHint::LenForSlice(slice, len_div), self.gen_env)
}
}
}
let out = Field::new(a, self.gen_env);
let type_ref = out.type_ref();
if type_ref.is_generic() {
let spec_type = spec.get(type_ref.base().cpp_full().as_ref())
.and_then(|s| self.gen_env.resolve_type(s));
if let Some(spec_type) = spec_type {
return Field::new_ext(a, FieldTypeHint::Specialized(spec_type), self.gen_env);
}
}
out
})
.collect()
}
pub fn dependent_types(&self) -> Vec<DependentType<'tu, 'ge>> {
self.arguments().into_iter()
.map(|a| a.type_ref())
.filter(|t| !t.is_ignored())
.map(|t| t.dependent_types())
.flatten()
.chain(self.return_type().dependent_types_with_mode(DependentTypeMode::ForReturn(DefinitionLocation::Module)))
.collect()
}
pub fn identifier(&self) -> Cow<str> {
let mut out: String = if self.as_field_accessor().is_some() {
let mut out: String = self.cpp_namespace().into_owned();
if !out.is_empty() {
out += "::";
}
let local_name = DefaultElement::cpp_localname(self);
let (first_letter, rest) = local_name.split_at(1);
if self.as_field_setter().is_some() {
out += &format!("setProp{}{}", first_letter.to_uppercase(), rest);
} else {
out += &format!("getProp{}{}", first_letter.to_uppercase(), rest);
}
out
} else {
self.cpp_fullname().into_owned()
};
out.cleanup_name();
if let Some(spec) = self.as_specialized() {
for typ in spec.values() {
out.push('_');
let mut typ = typ.to_string();
typ.cleanup_name();
out.push_str(&typ);
}
}
if self.is_const() {
out += "_const";
}
for arg in self.arguments() {
out.push('_');
let type_ref = arg.type_ref();
out += &type_ref.cpp_safe_id();
}
out.into()
}
}
impl<'tu> EntityElement<'tu> for Func<'tu, '_> {
fn entity(&self) -> Entity<'tu> {
self.entity
}
}
impl Element for Func<'_, '_> {
fn is_excluded(&self) -> bool {
let identifier = self.identifier();
if settings::FUNC_MANUAL.contains_key(identifier.as_ref()) || settings::FUNC_SPECIALIZE.contains_key(identifier.as_ref()) {
return false;
}
DefaultElement::is_excluded(self)
|| self.is_generic()
|| if let Some(cls) = self.as_constructor() {
cls.is_abstract()
} else {
false
}
}
fn is_ignored(&self) -> bool {
let identifier = self.identifier();
if settings::FUNC_MANUAL.contains_key(identifier.as_ref()) || settings::FUNC_SPECIALIZE.contains_key(identifier.as_ref()) {
return false;
}
DefaultElement::is_ignored(self)
|| self.entity.get_availability() == Availability::Unavailable
|| self.as_operator().map_or(false, |(.., op)| op == OperatorKind::Unsupported)
|| self.arguments().into_iter().any(|a| a.type_ref().is_ignored())
|| {
let ret = self.return_type();
ret.is_ignored() || ret.as_class().map_or(false, |cls| cls.is_abstract())
}
|| settings::FUNC_RENAME.get(identifier.as_ref()).filter(|&&n| n == "-").is_some()
}
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 {
let mut comment = self.entity.get_comment().unwrap_or_default();
const OVERLOAD: &str = "@overload";
if let Some(idx) = comment.find(OVERLOAD) {
let rep = if let Some(copy) = self.gen_env.get_func_comment(self.entity.cpp_fullname().as_ref()) {
format!("{}\n\n## Overloaded parameters\n", copy)
} else {
"".to_string()
};
comment.replace_range(idx..idx + OVERLOAD.len(), &rep);
}
static COPY_BRIEF: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@copybrief\s+(\w+)"#).unwrap());
comment.replace_in_place_regex_cb(©_BRIEF, |comment, caps| {
let copy_name = caps.get(1).map(|(s, e)| &comment[s..e]).expect("Impossible");
let mut copy_full_name = self.cpp_namespace().into_owned();
copy_full_name += "::";
copy_full_name += copy_name;
if let Some(copy) = self.gen_env.get_func_comment(©_full_name) {
Some(copy.into())
} else {
Some("".into())
}
});
comment::render_doc_comment_with_processor(&comment, prefix, opencv_version, |out| {
let mut default_args_comment = String::with_capacity(1024);
for arg in self.arguments() {
if let Some(def_val) = arg.default_value() {
if default_args_comment.is_empty() {
default_args_comment += "## C++ default parameters";
}
default_args_comment += &format!("\n* {name}: {val}", name=arg.rust_leafname(), val=def_val);
}
}
if !default_args_comment.is_empty() {
if !out.is_empty() {
out.push_str("\n\n");
}
out.push_str(&default_args_comment);
}
})
}
fn cpp_namespace(&self) -> Cow<str> {
DefaultElement::cpp_namespace(self)
}
fn cpp_localname(&self) -> Cow<str> {
if self.as_conversion_method().is_some() {
format!("operator {}", self.return_type().cpp_full()).into()
} else if self.as_field_setter().is_some() {
let name = DefaultElement::cpp_localname(self);
let (first_letter, rest) = name.split_at(1);
format!("set{}{}", first_letter.to_uppercase(), rest).into()
} else {
DefaultElement::cpp_localname(self)
}
}
fn rust_module(&self) -> Cow<str> {
DefaultElement::rust_module(self)
}
fn rust_leafname(&self) -> Cow<str> {
let cpp_name = if let Some(name) = self.gen_env.get_export_config(self.entity).and_then(|c| c.rename.as_ref()) {
name.into()
} else {
self.cpp_localname()
};
let rust_name = if let Some(cls) = self.as_constructor() {
let args = self.arguments();
#[allow(clippy::never_loop)]
'ctor_name: loop {
if args.is_empty() {
break 'ctor_name "default";
} else if args.len() == 1 {
let arg_typeref = args[0].type_ref();
let class_arg = arg_typeref.as_reference().and_then(|typ| {
if let Some(ptr) = typ.as_smart_ptr() {
ptr.pointee()
} else {
typ
}.as_class()
});
if let Some(other) = class_arg {
if cls == other {
break 'ctor_name if arg_typeref.constness().is_const() {
"copy"
} else {
"copy_mut"
};
}
}
}
break 'ctor_name "new";
}.into()
} else if let Some(..) = self.as_conversion_method() {
let mut name: String = self.return_type().rust_local().into_owned();
name.cleanup_name();
format!("to_{}", name).into()
} else if let Some((.., kind)) = self.as_operator() {
if cpp_name.starts_with("operator") {
match kind {
OperatorKind::Unsupported => {
cpp_name
}
OperatorKind::Index => {
if self.is_const() {
"get".into()
} else {
"get_mut".into()
}
}
OperatorKind::Add => {
"add".into()
}
OperatorKind::Sub => {
"sub".into()
}
OperatorKind::Mul => {
"mul".into()
}
OperatorKind::Div => {
"div".into()
}
OperatorKind::Deref => {
if self.is_const() {
"try_deref".into()
} else {
"try_deref_mut".into()
}
}
}
} else {
cpp_name
}
} else {
cpp_name
};
if let Some(&name) = settings::FUNC_RENAME.get(self.identifier().as_ref()) {
if name.contains('+') {
reserved_rename(name.replace('+', rust_name.as_ref()).to_snake_case().into())
} else {
name.into()
}
} else {
reserved_rename(rust_name.to_snake_case().into())
}
}
fn rust_localname(&self) -> Cow<str> {
DefaultElement::rust_localname(self)
}
}
impl fmt::Display for Func<'_, '_> {
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 Func<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = f.debug_struct("Func");
self.update_debug_struct(&mut debug_struct)
.field("export_config", &self.gen_env.get_export_config(self.entity))
.field("is_const", &self.is_const())
.field("type_hint", &self.type_hint)
.field("kind", &self.kind())
.field("return_type", &self.return_type())
.field("arguments", &self.arguments())
.finish()
}
}