opencv_binding_generator/writer/rust_native/
element.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3use std::ops::ControlFlow;
4
5use clang::{Entity, EntityKind};
6
7use super::comment::RenderComment;
8use crate::type_ref::FishStyle;
9use crate::{
10	opencv_module_from_path, reserved_rename, settings, CppNameStyle, Element, EntityExt, GeneratedType, IteratorExt, NameStyle,
11	StringExt, SupportedModule,
12};
13
14pub struct DefaultRustNativeElement;
15
16impl DefaultRustNativeElement {
17	pub fn rust_module(entity: Entity) -> SupportedModule {
18		entity
19			.get_location()
20			.expect("Can't get location")
21			.get_spelling_location()
22			.file
23			.and_then(|file| opencv_module_from_path(&file.get_path()))
24			.map_or(SupportedModule::Core, |m| rust_module_hack(entity, m))
25	}
26
27	pub fn rust_module_reference(this: &(impl RustElement + ?Sized)) -> Cow<'_, str> {
28		let module = this.rust_module();
29		let module_rust_safe_name = module.rust_safe_name();
30		if settings::STATIC_RUST_MODULES.contains(module_rust_safe_name) {
31			module_rust_safe_name.into()
32		} else {
33			format!("crate::{module_rust_safe_name}").into()
34		}
35	}
36
37	pub fn rust_leafname(this: &(impl Element + ?Sized)) -> Cow<'_, str> {
38		reserved_rename(this.cpp_name(CppNameStyle::Declaration))
39	}
40
41	pub fn rust_name(this: &(impl RustElement + ?Sized), entity: Entity, name_style: NameStyle) -> String {
42		let mut parts = Vec::with_capacity(4);
43		parts.push(this.rust_leafname(name_style.turbo_fish_style()));
44		let mut entity = entity;
45		let module = Self::rust_module(entity);
46		while let Some(parent) = entity.get_semantic_parent() {
47			match parent.get_kind() {
48				EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::ClassTemplate => {
49					let parent_name = parent.get_name().expect("Can't get parent name");
50					if parts.last().is_none_or(|last| last != &parent_name) {
51						parts.push(parent_name.into());
52					}
53				}
54				EntityKind::EnumDecl => {
55					if parent.is_scoped() {
56						parts.push(parent.get_name().expect("Can't get parent name").into());
57					}
58				}
59				EntityKind::TranslationUnit | EntityKind::UnexposedDecl | EntityKind::FunctionTemplate => break,
60				EntityKind::Namespace => {
61					let parent_namespace = parent.get_name().expect("Can't get parent name");
62					let no_skip_prefix = settings::NO_SKIP_NAMESPACE_IN_LOCALNAME
63						.get(&Some(module))
64						.and_then(|module_specific| module_specific.get(parent_namespace.as_str()))
65						.or_else(|| {
66							settings::NO_SKIP_NAMESPACE_IN_LOCALNAME
67								.get(&None)
68								.and_then(|generic| generic.get(parent_namespace.as_str()))
69						});
70					if let Some(&prefix) = no_skip_prefix {
71						parts.push(prefix.into());
72					}
73				}
74				EntityKind::Constructor | EntityKind::FunctionDecl | EntityKind::Method | EntityKind::NotImplemented => {}
75				_ => unreachable!("Can't get kind of parent: {parent:#?} for element: {entity:#?}"),
76			}
77			entity = parent;
78		}
79		let decl_name = parts.into_iter().rev().join("_");
80		match name_style {
81			NameStyle::Declaration => decl_name,
82			NameStyle::Reference(_) => {
83				let mut out = this.rust_module_reference().into_owned();
84				out.extend_sep("::", &decl_name);
85				out
86			}
87		}
88	}
89}
90
91pub trait RustNativeGeneratedElement {
92	/// Element order in the output file, lower means earlier
93	fn element_order(&self) -> u8 {
94		50
95	}
96
97	fn element_safe_id(&self) -> String;
98
99	fn gen_rust(&self, _opencv_version: &str) -> String {
100		"".to_string()
101	}
102
103	fn gen_rust_externs(&self) -> String {
104		"".to_string()
105	}
106
107	fn gen_cpp(&self) -> String {
108		"".to_string()
109	}
110}
111
112pub trait RustElement: Element {
113	fn rust_module(&self) -> SupportedModule;
114
115	fn rust_module_reference(&self) -> Cow<'_, str> {
116		DefaultRustNativeElement::rust_module_reference(self)
117	}
118
119	fn rust_name(&self, style: NameStyle) -> Cow<'_, str>;
120
121	/// The very last concrete part of the name in Rust
122	///
123	/// This might not match `rust_name(NameStyle::Declaration)` because some classes in Rust are prefixed with their namespace. E.g.
124	/// `Detail_Blender`, in this case the `rust_leafname()` == `Blender` and `rust_name(NameStyle::Declaration)` == `Detail_Blender`.
125	fn rust_leafname(&self, _fish_style: FishStyle) -> Cow<'_, str> {
126		DefaultRustNativeElement::rust_leafname(self)
127	}
128
129	fn rust_doc_comment(&self, comment_marker: &str, opencv_version: &str) -> String {
130		RenderComment::new(self.doc_comment().into_owned(), opencv_version)
131			.render_with_comment_marker(comment_marker)
132			.into_owned()
133	}
134}
135
136pub trait DebugRust {
137	type DebugType: Debug;
138
139	fn dbg_rust(self) -> Self::DebugType;
140}
141
142impl<'ne, 'tu: 'ne, 'ge: 'ne> AsRef<dyn RustNativeGeneratedElement + 'ne> for GeneratedType<'tu, 'ge> {
143	fn as_ref(&self) -> &(dyn RustNativeGeneratedElement + 'ne) {
144		match self {
145			GeneratedType::Vector(vec) => vec,
146			GeneratedType::SmartPtr(ptr) => ptr,
147			GeneratedType::Tuple(tuple) => tuple,
148			GeneratedType::AbstractRefWrapper(aref) => aref,
149		}
150	}
151}
152
153/// Adjusts the module for certain entities that are technically in one module but actually implemented in another.
154///
155/// One (and for now the only) example is that some class declarations are technically in `hpp` files in the `video` module, but
156/// their code is actually in the `tracking` module of `opencv_contrib`. This mismatch leads to linking errors if only the
157/// `opencv_video` module is linked.
158fn rust_module_hack(entity: Entity, module_from_path: SupportedModule) -> SupportedModule {
159	match module_from_path {
160		SupportedModule::Video => {
161			let break_res = entity.walk_parents(|parent| match parent.get_kind() {
162				EntityKind::Namespace if parent.is_inline_namespace() && parent.get_name().is_some_and(|n| n == "tracking") => {
163					ControlFlow::Break(SupportedModule::Tracking)
164				}
165				_ => ControlFlow::Continue(()),
166			});
167			// todo: MSRV 1.83 use `break_value()`
168			match break_res {
169				ControlFlow::Break(m) => Some(m),
170				ControlFlow::Continue(_) => None,
171			}
172			.unwrap_or(module_from_path)
173		}
174		_ => module_from_path,
175	}
176}