diff --git a/binding-generator/Cargo.toml b/binding-generator/Cargo.toml index fb44035e..e6d65fa5 100644 --- a/binding-generator/Cargo.toml +++ b/binding-generator/Cargo.toml @@ -20,5 +20,8 @@ percent-encoding = { version = "2", default-features = false } regex = "1" shlex = { version = "1.3", default-features = false } +[dev-dependencies] +tempfile = "3" + [features] clang-runtime = ["clang/runtime", "clang-sys/runtime"] diff --git a/binding-generator/src/generator.rs b/binding-generator/src/generator.rs index 98fee173..95dbecf5 100644 --- a/binding-generator/src/generator.rs +++ b/binding-generator/src/generator.rs @@ -77,7 +77,7 @@ pub trait GeneratorVisitor<'tu>: Sized { /// It takes [Entity]s supplied by the entity walker, extracts their export data (whether the entity should appear in bindings at /// all or is internal) and calls the corresponding method in [GeneratorVisitor] based on their type. This is the 2nd pass of the /// binding generation. -struct OpenCvWalker<'tu, 'r, V> { +pub struct OpenCvWalker<'tu, 'r, V> { module: &'r str, opencv_module_header_dir: &'r Path, visitor: V, diff --git a/binding-generator/src/lib.rs b/binding-generator/src/lib.rs index 1f6af650..64ff2d15 100644 --- a/binding-generator/src/lib.rs +++ b/binding-generator/src/lib.rs @@ -30,7 +30,7 @@ pub use entity::EntityExt; pub use enumeration::Enum; use field::Field; pub use func::{Func, FuncId, FuncTypeHint}; -pub use generator::{GeneratedType, Generator, GeneratorVisitor}; +pub use generator::{GeneratedType, Generator, GeneratorVisitor, OpenCvWalker}; pub use generator_env::{ClassKindOverride, ExportConfig, GeneratorEnv}; pub use iterator_ext::IteratorExt; use memoize::{MemoizeMap, MemoizeMapExt}; diff --git a/binding-generator/src/settings/argument_override.rs b/binding-generator/src/settings/argument_override.rs index c748127f..d0554461 100644 --- a/binding-generator/src/settings/argument_override.rs +++ b/binding-generator/src/settings/argument_override.rs @@ -149,6 +149,20 @@ pub static ARGUMENT_OVERRIDE: Lazy ClangTypeExt<'tu> for Type<'tu> { match kind { TypeKind::Pointer => { let pointee = self.get_pointee_type().expect("No pointee type for pointer"); - let pointee_typeref = TypeRef::new_ext(pointee, type_hint, parent_entity, gen_env); + let pointee_typeref = TypeRef::new_ext(pointee, type_hint.recurse(), parent_entity, gen_env); let pointee_kind = pointee_typeref.kind(); if pointee_kind.is_function() { pointee_kind.into_owned() @@ -392,7 +392,7 @@ impl<'tu> ClangTypeExt<'tu> for Type<'tu> { TypeKind::LValueReference => TypeRefKind::Reference(TypeRef::new_ext( self.get_pointee_type().expect("No pointee type for reference"), - type_hint, + type_hint.recurse(), parent_entity, gen_env, )), diff --git a/binding-generator/src/type_ref/kind.rs b/binding-generator/src/type_ref/kind.rs index 2ebf34ad..55fb7d6f 100644 --- a/binding-generator/src/type_ref/kind.rs +++ b/binding-generator/src/type_ref/kind.rs @@ -251,7 +251,7 @@ impl<'tu, 'ge> TypeRefKind<'tu, 'ge> { .map(|(_, str_type)| (Dir::from_out_dir(inner.inherent_constness().is_mut()), str_type)), TypeRefKind::Array(inner, ..) => { if inner.kind().is_char() { - Some((Dir::In, StrType::CharPtr(StrEnc::Text))) + Some((Dir::from_out_dir(inner.constness().is_mut()), StrType::CharPtr(StrEnc::Text))) } else { None } @@ -527,7 +527,7 @@ mod tests { { let char_array = TypeRef::new_array(TypeRefDesc::char(), None); - assert_eq!(Some((Dir::In, StrType::CharPtr(StrEnc::Text))), as_string(char_array)); + assert_eq!(Some((Dir::Out, StrType::CharPtr(StrEnc::Text))), as_string(char_array)); } } } diff --git a/binding-generator/src/type_ref/types.rs b/binding-generator/src/type_ref/types.rs index ce203013..a7efb368 100644 --- a/binding-generator/src/type_ref/types.rs +++ b/binding-generator/src/type_ref/types.rs @@ -44,6 +44,17 @@ impl TypeRefTypeHint { } } + /// Filters current TypeRef type hint to make it suitable for inner type e.g. for the pointee + /// + /// Useful for example to strip the nullability from the inner type of a pointer + pub fn recurse(self) -> Self { + match self { + Self::Nullable => Self::None, + Self::NullableSlice => Self::Slice, + recursable => recursable, + } + } + pub fn nullability(&self) -> Nullability { match self { TypeRefTypeHint::Nullable | TypeRefTypeHint::NullableSlice => Nullability::Nullable, diff --git a/binding-generator/src/writer/rust_native/type_ref.rs b/binding-generator/src/writer/rust_native/type_ref.rs index c4fd6172..59a284c9 100644 --- a/binding-generator/src/writer/rust_native/type_ref.rs +++ b/binding-generator/src/writer/rust_native/type_ref.rs @@ -93,8 +93,14 @@ impl TypeRefExt for TypeRef<'_, '_> { } kind => { let (indirection, tref_kind, tref) = match kind { - TypeRefKind::Pointer(pointee) => (Indirection::Pointer, pointee.kind().into_owned(), Owned(pointee)), - TypeRefKind::Reference(pointee) => (Indirection::Reference, pointee.kind().into_owned(), Owned(pointee)), + TypeRefKind::Pointer(pointee) => { + let pointee = pointee.with_type_hint(self.type_hint().clone()); + (Indirection::Pointer, pointee.kind().into_owned(), Owned(pointee)) + } + TypeRefKind::Reference(pointee) => { + let pointee = pointee.with_type_hint(self.type_hint().clone()); + (Indirection::Reference, pointee.kind().into_owned(), Owned(pointee)) + } kind => (Indirection::None, kind, Borrowed(self)), }; match tref_kind.canonical().into_owned() { diff --git a/binding-generator/tests/code_template.cpp b/binding-generator/tests/code_template.cpp new file mode 100644 index 00000000..2f1c3c2e --- /dev/null +++ b/binding-generator/tests/code_template.cpp @@ -0,0 +1,7 @@ +#define CV_EXPORTS + +namespace cv { + +{{code}} + +} diff --git a/binding-generator/tests/generation.rs b/binding-generator/tests/generation.rs new file mode 100644 index 00000000..19bdc32d --- /dev/null +++ b/binding-generator/tests/generation.rs @@ -0,0 +1,83 @@ +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use clang::diagnostic::Severity; +use clang::{Clang, Entity, Index}; +use opencv_binding_generator::writer::rust_native::element::RustNativeGeneratedElement; +use opencv_binding_generator::{EntityWalkerExt, Func, GeneratorEnv, GeneratorVisitor, OpenCvWalker}; +use tempfile::TempDir; + +fn clang_parse(code: &str, op: impl FnOnce(Entity)) { + const CODE_TPL: &str = include_str!("code_template.cpp"); + const CODE_PAT: &str = "{{code}}"; + + let temp_dir = TempDir::new().expect("Can't create temp dir"); + let temp_file_path = temp_dir.path().join("temp.cpp"); + if let Some(start) = CODE_TPL.find(CODE_PAT) { + let mut temp_cpp = File::create(&temp_file_path).expect("Can't create temp file"); + temp_cpp + .write_all(CODE_TPL[..start].as_bytes()) + .expect("Can't write to temp file"); + temp_cpp.write_all(code.as_bytes()).expect("Can't write to temp file"); + temp_cpp + .write_all(CODE_TPL[start + CODE_PAT.len()..].as_bytes()) + .expect("Can't write to temp file"); + } + let clang = Clang::new().expect("Can't init clang"); + let index = Index::new(&clang, false, false); + let root_tu = index + .parser(&temp_file_path) + .skip_function_bodies(true) + .detailed_preprocessing_record(true) + .parse() + .expect("Can't parse"); + let diags = root_tu.get_diagnostics(); + if !diags.is_empty() { + let mut has_error = false; + eprintln!("WARNING: {} diagnostic messages", diags.len()); + for diag in diags { + if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) { + has_error = true; + } + eprintln!(" {diag}"); + } + if has_error { + panic!("Errors during header parsing"); + } + } + op(root_tu.get_entity()); +} + +fn extract_functions(code: &str, cb: impl FnMut(Func)) { + struct FunctionExtractor { + cb: F, + } + + impl GeneratorVisitor<'_> for FunctionExtractor { + fn visit_func(&mut self, func: Func) { + (self.cb)(func); + } + } + + clang_parse(code, |root_tu| { + let gen_env = GeneratorEnv::empty(); + let visitor = FunctionExtractor { cb }; + let opencv_walker = OpenCvWalker::new("core", Path::new(""), visitor, gen_env); + + root_tu.walk_opencv_entities(opencv_walker); + }); +} + +#[test] +fn char_ptr_slice() { + extract_functions("CV_EXPORTS int startLoop(int argc, char* argv[]);", |f| { + dbg!(&f); + assert_eq!(f.gen_rust("0.0.0").trim(), "#[inline]\npub fn start_loop(argv: &mut [&str]) -> Result {\n\tstring_array_arg_mut!(argv);\n\treturn_send!(via ocvrs_return);\n\tunsafe { sys::cv_startLoop_int_charXX(argv.len().try_into()?, argv.as_mut_ptr(), ocvrs_return.as_mut_ptr()) };\n\treturn_receive!(unsafe ocvrs_return => ret);\n\tlet ret = ret.into_result()?;\n\tOk(ret)\n}"); + assert_eq!(f.gen_cpp().trim(), "void cv_startLoop_int_charXX(int argc, char** argv, Result* ocvrs_return) {\n\ttry {\n\t\tint ret = cv::startLoop(argc, argv);\n\t\tOk(ret, ocvrs_return);\n\t} OCVRS_CATCH(ocvrs_return);\n}"); + assert_eq!( + f.gen_rust_externs().trim(), + r#"pub fn cv_startLoop_int_charXX(argc: i32, argv: *mut *mut c_char, ocvrs_return: *mut Result);"#, + ); + }); +}