1use std::{collections::vec_deque::VecDeque, slice::Iter};
2
3use crate::{
4 analysis::{
5 function_parameters::CParameter,
6 functions::{find_function, find_index_to_ignore, finish_function_name},
7 imports::Imports,
8 out_parameters::use_function_return_for_result,
9 ref_mode::RefMode,
10 rust_type::RustType,
11 },
12 config,
13 consts::TYPE_PARAMETERS_START,
14 env::Env,
15 library::{Basic, Class, Concurrency, Function, ParameterDirection, Type, TypeId},
16 traits::IntoString,
17};
18
19#[derive(Clone, Eq, Debug, PartialEq)]
20pub enum BoundType {
21 NoWrapper,
22 IsA(Option<char>),
24 AsRef(Option<char>),
26}
27
28impl BoundType {
29 pub fn need_isa(&self) -> bool {
30 matches!(*self, Self::IsA(_))
31 }
32
33 pub fn has_alias(&self) -> bool {
38 matches!(*self, Self::NoWrapper)
39 }
40}
41
42#[derive(Clone, Eq, Debug, PartialEq)]
43pub struct Bound {
44 pub bound_type: BoundType,
45 pub parameter_name: String,
46 pub alias: Option<char>,
48 pub type_str: String,
49 pub callback_modified: bool,
50}
51
52#[derive(Clone, Debug)]
53pub struct Bounds {
54 unused: VecDeque<char>,
55 used: Vec<Bound>,
56 lifetimes: Vec<char>,
57}
58
59impl Default for Bounds {
60 fn default() -> Bounds {
61 Bounds {
62 unused: (TYPE_PARAMETERS_START..)
63 .take_while(|x| *x <= 'Z')
64 .collect(),
65 used: Vec::new(),
66 lifetimes: Vec::new(),
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
72pub struct CallbackInfo {
73 pub callback_type: String,
74 pub success_parameters: String,
75 pub error_parameters: Option<String>,
76 pub bound_name: char,
77}
78
79impl Bounds {
80 pub fn add_for_parameter(
81 &mut self,
82 env: &Env,
83 func: &Function,
84 par: &CParameter,
85 r#async: bool,
86 concurrency: Concurrency,
87 configured_functions: &[&config::functions::Function],
88 ) -> (Option<String>, Option<CallbackInfo>) {
89 let type_name = RustType::builder(env, par.typ)
90 .ref_mode(RefMode::ByRefFake)
91 .try_build();
92 if type_name.is_err() {
93 return (None, None);
94 }
95 let mut type_string = type_name.into_string();
96 let mut callback_info = None;
97 let mut ret = None;
98 let mut need_is_into_check = false;
99
100 if !par.instance_parameter && par.direction != ParameterDirection::Out {
101 if let Some(bound_type) = Bounds::type_for(env, par.typ) {
102 ret = Some(Bounds::get_to_glib_extra(
103 &bound_type,
104 *par.nullable,
105 par.instance_parameter,
106 par.move_,
107 ));
108 if r#async && (par.name == "callback" || par.name.ends_with("_callback")) {
109 let func_name = func.c_identifier.as_ref().unwrap();
110 let finish_func_name = if let Some(finish_func_name) = &func.finish_func {
111 finish_func_name.to_string()
112 } else {
113 finish_function_name(func_name)
114 };
115 if let Some(function) = find_function(env, &finish_func_name) {
116 let mut out_parameters =
120 find_out_parameters(env, function, configured_functions);
121 if use_function_return_for_result(
122 env,
123 function.ret.typ,
124 &func.name,
125 configured_functions,
126 ) {
127 let nullable = configured_functions
128 .iter()
129 .find_map(|f| f.ret.nullable)
130 .unwrap_or(function.ret.nullable);
131
132 out_parameters.insert(
133 0,
134 RustType::builder(env, function.ret.typ)
135 .direction(function.ret.direction)
136 .nullable(nullable)
137 .try_build()
138 .into_string(),
139 );
140 }
141 let parameters = format_out_parameters(&out_parameters);
142 let error_type = find_error_type(env, function);
143 if let Some(ref error) = error_type {
144 type_string =
145 format!("FnOnce(Result<{parameters}, {error}>) + 'static");
146 } else {
147 type_string = format!("FnOnce({parameters}) + 'static");
148 }
149 let bound_name = *self.unused.front().unwrap();
150 callback_info = Some(CallbackInfo {
151 callback_type: type_string.clone(),
152 success_parameters: parameters,
153 error_parameters: error_type,
154 bound_name,
155 });
156 }
157 } else if par.c_type == "GDestroyNotify" || env.library.type_(par.typ).is_function()
158 {
159 need_is_into_check = par.c_type != "GDestroyNotify";
160 if let Type::Function(_) = env.library.type_(par.typ) {
161 let callback_parameters_config =
162 configured_functions.iter().find_map(|f| {
163 f.parameters
164 .iter()
165 .find(|p| p.ident.is_match(&par.name))
166 .map(|p| &p.callback_parameters)
167 });
168
169 let mut rust_ty = RustType::builder(env, par.typ)
170 .direction(par.direction)
171 .scope(par.scope)
172 .concurrency(concurrency);
173 if let Some(callback_parameters_config) = callback_parameters_config {
174 rust_ty =
175 rust_ty.callback_parameters_config(callback_parameters_config);
176 }
177 type_string = rust_ty
178 .try_from_glib(&par.try_from_glib)
179 .try_build()
180 .into_string();
181 let bound_name = *self.unused.front().unwrap();
182 callback_info = Some(CallbackInfo {
183 callback_type: type_string.clone(),
184 success_parameters: String::new(),
185 error_parameters: None,
186 bound_name,
187 });
188 }
189 }
190 if (!need_is_into_check || !*par.nullable) && par.c_type != "GDestroyNotify" {
191 self.add_parameter(&par.name, &type_string, bound_type, r#async);
192 }
193 }
194 } else if par.instance_parameter {
195 if let Some(bound_type) = Bounds::type_for(env, par.typ) {
196 ret = Some(Bounds::get_to_glib_extra(
197 &bound_type,
198 *par.nullable,
199 true,
200 par.move_,
201 ));
202 }
203 }
204
205 (ret, callback_info)
206 }
207
208 pub fn type_for(env: &Env, type_id: TypeId) -> Option<BoundType> {
209 use self::BoundType::*;
210 match env.library.type_(type_id) {
211 Type::Basic(Basic::Filename | Basic::OsString) => Some(AsRef(None)),
212 Type::Class(Class {
213 is_fundamental: true,
214 ..
215 }) => Some(AsRef(None)),
216 Type::Class(Class {
217 final_type: true, ..
218 }) => None,
219 Type::Class(Class {
220 final_type: false, ..
221 }) => Some(IsA(None)),
222 Type::Interface(..) => Some(IsA(None)),
223 Type::List(_) | Type::SList(_) | Type::CArray(_) => None,
224 Type::Function(_) => Some(NoWrapper),
225 _ => None,
226 }
227 }
228
229 pub fn get_to_glib_extra(
230 bound_type: &BoundType,
231 nullable: bool,
232 instance_parameter: bool,
233 move_: bool,
234 ) -> String {
235 use self::BoundType::*;
236 match bound_type {
237 AsRef(_) if move_ && nullable => ".map(|p| p.as_ref().clone().upcast())".to_owned(),
238 AsRef(_) if nullable => ".as_ref().map(|p| p.as_ref())".to_owned(),
239 AsRef(_) if move_ => ".upcast()".to_owned(),
240 AsRef(_) => ".as_ref()".to_owned(),
241 IsA(_) if move_ && nullable => ".map(|p| p.upcast())".to_owned(),
242 IsA(_) if nullable && !instance_parameter => ".map(|p| p.as_ref())".to_owned(),
243 IsA(_) if move_ => ".upcast()".to_owned(),
244 IsA(_) => ".as_ref()".to_owned(),
245 _ => String::new(),
246 }
247 }
248
249 pub fn add_parameter(
250 &mut self,
251 name: &str,
252 type_str: &str,
253 mut bound_type: BoundType,
254 r#async: bool,
255 ) {
256 if r#async && name == "callback" {
257 bound_type = BoundType::NoWrapper;
258 }
259 if self.used.iter().any(|n| n.parameter_name == name) {
260 return;
261 }
262 let alias = bound_type
263 .has_alias()
264 .then(|| self.unused.pop_front().expect("No free type aliases!"));
265 self.used.push(Bound {
266 bound_type,
267 parameter_name: name.to_owned(),
268 alias,
269 type_str: type_str.to_owned(),
270 callback_modified: false,
271 });
272 }
273
274 pub fn get_parameter_bound(&self, name: &str) -> Option<&Bound> {
275 self.iter().find(move |n| n.parameter_name == name)
276 }
277
278 pub fn update_imports(&self, imports: &mut Imports) {
279 use self::BoundType::*;
281 for used in &self.used {
282 match used.bound_type {
283 NoWrapper => (),
284 IsA(_) => imports.add("glib::prelude::*"),
285 AsRef(_) => imports.add_used_type(&used.type_str),
286 }
287 }
288 }
289
290 pub fn is_empty(&self) -> bool {
291 self.used.is_empty()
292 }
293
294 pub fn iter(&self) -> Iter<'_, Bound> {
295 self.used.iter()
296 }
297
298 pub fn iter_lifetimes(&self) -> Iter<'_, char> {
299 self.lifetimes.iter()
300 }
301}
302
303#[derive(Clone, Debug)]
304pub struct PropertyBound {
305 pub alias: char,
306 pub type_str: String,
307}
308
309impl PropertyBound {
310 pub fn get(env: &Env, type_id: TypeId) -> Option<Self> {
311 let type_ = env.type_(type_id);
312 if type_.is_final_type() {
313 return None;
314 }
315 Some(Self {
316 alias: TYPE_PARAMETERS_START,
317 type_str: RustType::builder(env, type_id)
318 .ref_mode(RefMode::ByRefFake)
319 .try_build()
320 .into_string(),
321 })
322 }
323}
324
325fn find_out_parameters(
326 env: &Env,
327 function: &Function,
328 configured_functions: &[&config::functions::Function],
329) -> Vec<String> {
330 let index_to_ignore = find_index_to_ignore(&function.parameters, Some(&function.ret));
331 function
332 .parameters
333 .iter()
334 .enumerate()
335 .filter(|&(index, param)| {
336 Some(index) != index_to_ignore
337 && param.direction == ParameterDirection::Out
338 && param.name != "error"
339 })
340 .map(|(_, param)| {
341 let nullable = configured_functions
345 .iter()
346 .find_map(|f| {
347 f.parameters
348 .iter()
349 .filter(|p| p.ident.is_match(¶m.name))
350 .find_map(|p| p.nullable)
351 })
352 .unwrap_or(param.nullable);
353
354 RustType::builder(env, param.typ)
355 .direction(param.direction)
356 .nullable(nullable)
357 .try_build()
358 .into_string()
359 })
360 .collect()
361}
362
363fn format_out_parameters(parameters: &[String]) -> String {
364 if parameters.len() == 1 {
365 parameters[0].to_string()
366 } else {
367 format!("({})", parameters.join(", "))
368 }
369}
370
371fn find_error_type(env: &Env, function: &Function) -> Option<String> {
372 let error_param = function
373 .parameters
374 .iter()
375 .find(|param| param.direction.is_out() && param.is_error)?;
376 if let Type::Record(_) = env.type_(error_param.typ) {
377 Some(
378 RustType::builder(env, error_param.typ)
379 .direction(error_param.direction)
380 .try_build()
381 .into_string(),
382 )
383 } else {
384 None
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn get_new_all() {
394 let mut bounds: Bounds = Default::default();
395 let typ = BoundType::IsA(None);
396 bounds.add_parameter("a", "", typ.clone(), false);
397 assert_eq!(bounds.iter().len(), 1);
398 bounds.add_parameter("a", "", typ.clone(), false);
400 assert_eq!(bounds.iter().len(), 1);
401 bounds.add_parameter("b", "", typ.clone(), false);
402 bounds.add_parameter("c", "", typ.clone(), false);
403 bounds.add_parameter("d", "", typ.clone(), false);
404 bounds.add_parameter("e", "", typ.clone(), false);
405 bounds.add_parameter("f", "", typ.clone(), false);
406 bounds.add_parameter("g", "", typ.clone(), false);
407 bounds.add_parameter("h", "", typ.clone(), false);
408 assert_eq!(bounds.iter().len(), 8);
409 bounds.add_parameter("h", "", typ.clone(), false);
410 assert_eq!(bounds.iter().len(), 8);
411 bounds.add_parameter("i", "", typ.clone(), false);
412 bounds.add_parameter("j", "", typ.clone(), false);
413 bounds.add_parameter("k", "", typ, false);
414 }
415
416 #[test]
417 #[should_panic(expected = "No free type aliases!")]
418 fn exhaust_type_parameters() {
419 let mut bounds: Bounds = Default::default();
420 let typ = BoundType::NoWrapper;
421 for c in 'a'..='l' {
422 bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false);
424 }
425 }
426
427 #[test]
428 fn get_parameter_bound() {
429 let mut bounds: Bounds = Default::default();
430 let typ = BoundType::NoWrapper;
431 bounds.add_parameter("a", "", typ.clone(), false);
432 bounds.add_parameter("b", "", typ.clone(), false);
433 let bound = bounds.get_parameter_bound("a").unwrap();
434 assert_eq!(bound.alias, Some('P'));
436 assert_eq!(bound.bound_type, typ);
437 let bound = bounds.get_parameter_bound("b").unwrap();
438 assert_eq!(bound.alias, Some('Q'));
439 assert_eq!(bound.bound_type, typ);
440 assert_eq!(bounds.get_parameter_bound("c"), None);
441 }
442
443 #[test]
444 fn impl_bound() {
445 let mut bounds: Bounds = Default::default();
446 let typ = BoundType::IsA(None);
447 bounds.add_parameter("a", "", typ.clone(), false);
448 bounds.add_parameter("b", "", typ.clone(), false);
449 let bound = bounds.get_parameter_bound("a").unwrap();
450 assert_eq!(bound.alias, None);
453 assert_eq!(bound.bound_type, typ);
454
455 let typ = BoundType::AsRef(None);
456 bounds.add_parameter("c", "", typ.clone(), false);
457 let bound = bounds.get_parameter_bound("c").unwrap();
458 assert_eq!(bound.alias, None);
460 assert_eq!(bound.bound_type, typ);
461 }
462}