opencv_binding_generator/writer/rust_native/
renderer.rs

1use std::borrow::Cow;
2use std::fmt::Write;
3
4use crate::renderer::TypeRefRenderer;
5use crate::type_ref::{
6	Constness, CppNameStyle, Dir, ExternDir, FishStyle, NameStyle, StrType, TemplateArg, TypeRef, TypeRefDesc, TypeRefKind,
7};
8use crate::writer::rust_native::class::ClassExt;
9use crate::writer::rust_native::element::RustElement;
10use crate::writer::rust_native::function::FunctionExt;
11use crate::writer::rust_native::type_ref::{Lifetime, NullabilityExt, TypeRefExt};
12use crate::{settings, CowMapBorrowedExt, Element, IteratorExt};
13
14fn render_rust_tpl<'a>(
15	renderer: impl TypeRefRenderer<'a>,
16	type_ref: &TypeRef,
17	lifetime: Option<Lifetime>,
18	fish_style: FishStyle,
19) -> String {
20	let generic_types = type_ref.template_specialization_args();
21	if !generic_types.is_empty() {
22		let const_generics_implemented = type_ref
23			.kind()
24			.as_class()
25			.is_some_and(|cls| settings::IMPLEMENTED_CONST_GENERICS.contains(cls.cpp_name(CppNameStyle::Reference).as_ref()));
26		let mut constant_suffix = String::new();
27		let generic_types = generic_types.iter().filter_map(|t| match t {
28			TemplateArg::Typename(type_ref) => Some(renderer.recurse().render(type_ref)),
29			TemplateArg::Constant(literal) => {
30				if const_generics_implemented {
31					Some(literal.into())
32				} else {
33					constant_suffix += literal;
34					None
35				}
36			}
37			TemplateArg::Unknown => None,
38		});
39		let mut generics = generic_types.join(", ");
40		if let Some(lifetime) = lifetime {
41			generics.insert_str(0, &format!("{lifetime:,<#}"));
42		}
43		format!("{constant_suffix}{fish}<{generics}>", fish = fish_style.rust_qual())
44	} else if let Some(lifetime) = lifetime {
45		format!("{fish}<{lifetime:#}>", fish = fish_style.rust_qual())
46	} else {
47		"".to_string()
48	}
49}
50
51pub struct RustRenderer {
52	pub name_style: NameStyle,
53	pub lifetime: Lifetime,
54}
55
56impl RustRenderer {
57	pub fn new(name_style: NameStyle, lifetime: Lifetime) -> Self {
58		Self { name_style, lifetime }
59	}
60
61	pub fn format_as_array(constness: Constness, elem_type: &str, size: Option<usize>) -> String {
62		format!(
63			"&{cnst}[{elem_type}{size}]",
64			cnst = constness.rust_qual(),
65			size = size.map_or_else(|| "".to_string(), |s| format!("; {s}"))
66		)
67	}
68}
69
70impl TypeRefRenderer<'_> for RustRenderer {
71	type Recursed = Self;
72
73	fn render<'t>(self, type_ref: &'t TypeRef) -> Cow<'t, str> {
74		let kind = type_ref.kind();
75		if let Some((_, str_type)) = kind.as_string(type_ref.type_hint()) {
76			if str_type.is_binary() {
77				format!("Vec{fish}<u8>", fish = self.name_style.turbo_fish_style().rust_qual()).into()
78			} else {
79				"String".into()
80			}
81		} else {
82			kind.map_borrowed(|kind| match kind {
83				TypeRefKind::Primitive(rust, _) => (*rust).into(),
84				TypeRefKind::Array(elem, size) => {
85					let typ = RustRenderer::format_as_array(type_ref.constness(), &self.recurse().render(elem), *size);
86					type_ref
87						.type_hint()
88						.nullability()
89						.rust_wrap_nullable_decl(typ.into(), self.name_style)
90				}
91				TypeRefKind::StdVector(vec) => vec.rust_name(self.name_style),
92				TypeRefKind::StdTuple(tuple) => tuple.rust_name(self.name_style),
93				TypeRefKind::RValueReference(inner) => self.recurse().render(inner),
94				kind @ TypeRefKind::Pointer(inner) if kind.is_rust_by_ptr(type_ref.type_hint()) => {
95					let typ = if inner.kind().is_void() {
96						"c_void".into()
97					} else {
98						self.recurse().render(inner)
99					};
100					format!("*{cnst}{typ}", cnst = type_ref.constness().rust_qual_ptr()).into()
101				}
102				TypeRefKind::Pointer(inner) | TypeRefKind::Reference(inner) => {
103					let typ = format!(
104						"&{lt: <}{cnst}{typ}",
105						cnst = type_ref.constness().rust_qual(),
106						lt = self.lifetime,
107						typ = self.recurse().render(inner)
108					);
109					type_ref
110						.type_hint()
111						.nullability()
112						.rust_wrap_nullable_decl(typ.into(), self.name_style)
113				}
114				TypeRefKind::SmartPtr(ptr) => {
115					let typ = ptr.rust_name(self.name_style);
116					type_ref
117						.type_hint()
118						.nullability()
119						.rust_wrap_nullable_decl(typ, self.name_style)
120				}
121				TypeRefKind::Class(cls) => {
122					let fish_style = self.name_style.turbo_fish_style();
123					let lifetime = cls.rust_lifetime().map(|_| self.lifetime);
124					format!(
125						"{name}{generic}",
126						name = cls.rust_name(self.name_style),
127						generic = render_rust_tpl(self, type_ref, lifetime, fish_style),
128					)
129					.into()
130				}
131				TypeRefKind::Enum(enm) => enm.rust_name(self.name_style),
132				TypeRefKind::Typedef(decl) => {
133					let mut out = decl.rust_name(self.name_style);
134					let lifetime_count = decl.underlying_type_ref().rust_lifetime_count();
135					if lifetime_count >= 1 && self.lifetime.is_explicit() {
136						write!(out.to_mut(), "<{}>", self.lifetime).expect("Impossible");
137					}
138					out
139				}
140				TypeRefKind::Generic(name) => name.into(),
141				TypeRefKind::Function(func) => func.rust_name(self.name_style),
142				TypeRefKind::Ignored => "<ignored>".into(),
143			})
144		}
145	}
146
147	fn recurse(&self) -> Self::Recursed {
148		Self {
149			lifetime: self.lifetime.next().unwrap_or(Lifetime::Elided),
150			..*self
151		}
152	}
153}
154
155pub struct RustExternRenderer {
156	direction: ExternDir,
157}
158
159impl RustExternRenderer {
160	pub fn new(direction: ExternDir) -> Self {
161		Self { direction }
162	}
163}
164
165impl TypeRefRenderer<'_> for RustExternRenderer {
166	type Recursed = RustRenderer;
167
168	fn render<'t>(self, type_ref: &'t TypeRef) -> Cow<'t, str> {
169		let kind = type_ref.kind();
170		if let Some((arg_dir, _)) = kind.as_string(type_ref.type_hint()) {
171			match self.direction {
172				ExternDir::ToCpp | ExternDir::Contained => match arg_dir {
173					Dir::In => format!("*{cnst}c_char", cnst = Constness::Const.rust_qual_ptr()).into(),
174					Dir::Out => "*mut *mut c_void".into(),
175				},
176				ExternDir::FromCpp => "*mut c_void".into(),
177			}
178		} else if kind.extern_pass_kind().is_by_void_ptr() || kind.is_void_slice(type_ref.type_hint()) {
179			let void_ptr_constness = match self.direction {
180				ExternDir::Contained | ExternDir::ToCpp => type_ref.constness(),
181				ExternDir::FromCpp => Constness::Mut,
182			};
183			TypeRef::new_pointer(TypeRefDesc::void().with_inherent_constness(void_ptr_constness))
184				.rust_extern(self.direction)
185				.into_owned()
186				.into()
187		} else {
188			kind.map_borrowed(|kind| {
189				match kind {
190					TypeRefKind::Pointer(inner) | TypeRefKind::Reference(inner) | TypeRefKind::RValueReference(inner) => {
191						let typ = if inner.kind().is_void() {
192							"c_void".into()
193						} else {
194							inner.rust_extern(ExternDir::Contained)
195						};
196						format!("*{cnst}{typ}", cnst = type_ref.constness().rust_qual_ptr()).into()
197					}
198					TypeRefKind::Array(elem, None) => {
199						let typ = if matches!(elem.kind().as_string(elem.type_hint()), Some((Dir::Out, StrType::CharPtr(_)))) {
200							// kind of special casing for cv_startLoop_int__X__int__charXX__int_charXX, without that
201							// argv is treated as array of output arguments, and it doesn't seem to be meant this way
202							format!("*{cnst}c_char", cnst = elem.inherent_constness().rust_qual_ptr()).into()
203						} else {
204							elem.rust_extern(ExternDir::Contained)
205						};
206						format!("*{cnst}{typ}", cnst = type_ref.constness().rust_qual_ptr()).into()
207					}
208					TypeRefKind::Array(elem, Some(len)) => format!(
209						"*{cnst}[{typ}; {len}]",
210						cnst = type_ref.constness().rust_qual_ptr(),
211						typ = elem.rust_extern(ExternDir::Contained),
212					)
213					.into(),
214					TypeRefKind::Function(func) => func.rust_extern(),
215					TypeRefKind::Primitive(_, _)
216					| TypeRefKind::StdVector(_)
217					| TypeRefKind::StdTuple(_)
218					| TypeRefKind::SmartPtr(_)
219					| TypeRefKind::Enum(_)
220					| TypeRefKind::Typedef(_)
221					| TypeRefKind::Generic(_)
222					| TypeRefKind::Class(_)
223					| TypeRefKind::Ignored => type_ref.rust_name(NameStyle::ref_()),
224				}
225			})
226		}
227	}
228
229	fn recurse(&self) -> Self::Recursed {
230		RustRenderer::new(NameStyle::Reference(FishStyle::No), Lifetime::Elided)
231	}
232}
233
234pub struct RustReturnRenderer {
235	turbo_fish_style: FishStyle,
236	lifetime: Lifetime,
237}
238
239impl RustReturnRenderer {
240	pub fn new(turbo_fish_style: FishStyle, lifetime: Lifetime) -> Self {
241		Self {
242			turbo_fish_style,
243			lifetime,
244		}
245	}
246
247	fn next_lifetime(&mut self) -> Lifetime {
248		let out = self.lifetime;
249		self.lifetime = self.lifetime.next().unwrap_or(Lifetime::Elided);
250		out
251	}
252}
253
254impl TypeRefRenderer<'_> for RustReturnRenderer {
255	type Recursed = RustRenderer;
256
257	fn render<'t>(mut self, type_ref: &'t TypeRef) -> Cow<'t, str> {
258		let kind = type_ref.kind();
259		if kind.as_abstract_class_ptr().is_some() {
260			let lt = self.next_lifetime();
261			format!(
262				"types::AbstractRef{mut_suf}{fish}<{lt:,<#}{typ}>",
263				mut_suf = type_ref.constness().rust_name_qual(),
264				fish = self.turbo_fish_style.rust_qual(),
265				typ = self.recurse().render(&type_ref.source()),
266			)
267			.into()
268		} else if type_ref.type_hint().as_boxed_as_ref().is_some() {
269			let lt = self.next_lifetime();
270			format!(
271				"BoxedRef{mut_suf}{fish}<{lt:,<#}{typ}>",
272				mut_suf = type_ref.constness().rust_name_qual(),
273				fish = self.turbo_fish_style.rust_qual(),
274				typ = self.recurse().render(type_ref)
275			)
276			.into()
277		} else if kind.extern_pass_kind().is_by_void_ptr() {
278			self.recurse().render(&type_ref.source()).into_owned().into()
279		} else {
280			self.recurse().render(type_ref).into_owned().into()
281		}
282	}
283
284	fn recurse(&self) -> Self::Recursed {
285		RustRenderer::new(NameStyle::Reference(self.turbo_fish_style), self.lifetime)
286	}
287}