1use std::collections::HashMap;
2
3use super::{
4 conversion_type::ConversionType, out_parameters::can_as_return,
5 override_string_type::override_string_type_parameter, ref_mode::RefMode, rust_type::RustType,
6 try_from_glib::TryFromGlib,
7};
8use crate::{
9 analysis::{self, bounds::Bounds},
10 config::{self, parameter_matchable::ParameterMatchable},
11 env::Env,
12 library::{self, TypeId},
13 nameutil,
14 traits::IntoString,
15};
16
17#[derive(Clone, Debug)]
18pub struct Parameter {
19 pub lib_par: library::Parameter,
20 pub try_from_glib: TryFromGlib,
21}
22
23impl Parameter {
24 pub fn from_parameter(
25 env: &Env,
26 lib_par: library::Parameter,
27 configured_parameters: &[&config::functions::Parameter],
28 ) -> Self {
29 let ty = lib_par.typ();
30 Parameter {
31 lib_par,
32 try_from_glib: TryFromGlib::from_parameter(env, ty, configured_parameters),
33 }
34 }
35
36 pub fn from_return_value(
37 env: &Env,
38 lib_par: library::Parameter,
39 configured_functions: &[&config::functions::Function],
40 ) -> Self {
41 let ty = lib_par.typ();
42 Parameter {
43 lib_par,
44 try_from_glib: TryFromGlib::from_return_value(env, ty, configured_functions),
45 }
46 }
47}
48
49#[derive(Clone, Debug)]
51pub struct RustParameter {
52 pub ind_c: usize, pub name: String,
54 pub typ: TypeId,
55}
56
57#[derive(Clone, Debug)]
58pub struct CParameter {
59 pub name: String,
60 pub typ: TypeId,
61 pub c_type: String,
62 pub is_instance_parameter: bool,
63 pub direction: library::ParameterDirection,
64 pub nullable: bool,
65 pub transfer: gir_parser::TransferOwnership,
66 pub caller_allocates: bool,
67 pub is_error: bool,
68 pub scope: Option<gir_parser::FunctionScope>,
69 pub user_data_index: Option<usize>,
71 pub destroy_index: Option<usize>,
74
75 pub ref_mode: RefMode,
77 pub try_from_glib: TryFromGlib,
78 pub move_: bool,
79}
80
81#[derive(Clone, Debug)]
82pub enum TransformationType {
83 ToGlibDirect {
84 name: String,
85 },
86 ToGlibScalar {
87 name: String,
88 nullable: bool,
89 needs_into: bool,
90 },
91 ToGlibPointer {
92 name: String,
93 is_instance_parameter: bool,
94 transfer: gir_parser::TransferOwnership,
95 ref_mode: RefMode,
96 to_glib_extra: String,
98 explicit_target_type: String,
99 pointer_cast: String,
100 in_trait: bool,
101 nullable: bool,
102 move_: bool,
103 },
104 ToGlibBorrow,
105 ToGlibUnknown {
106 name: String,
107 },
108 Length {
109 array_name: String,
110 array_length_name: String,
111 array_length_type: String,
112 },
113 IntoRaw(String),
114 ToSome(String),
115}
116
117impl TransformationType {
118 pub fn is_to_glib(&self) -> bool {
119 matches!(
120 *self,
121 Self::ToGlibDirect { .. }
122 | Self::ToGlibScalar { .. }
123 | Self::ToGlibPointer { .. }
124 | Self::ToGlibBorrow
125 | Self::ToGlibUnknown { .. }
126 | Self::ToSome(_)
127 | Self::IntoRaw(_)
128 )
129 }
130
131 pub fn set_to_glib_extra(&mut self, to_glib_extra_: &str) {
132 if let Self::ToGlibPointer { to_glib_extra, .. } = self {
133 *to_glib_extra = to_glib_extra_.to_owned();
134 }
135 }
136}
137
138#[derive(Clone, Debug)]
139pub struct Transformation {
140 pub ind_c: usize, pub ind_rust: Option<usize>, pub transformation_type: TransformationType,
143}
144
145#[derive(Clone, Default, Debug)]
146pub struct Parameters {
147 pub rust_parameters: Vec<RustParameter>,
148 pub c_parameters: Vec<CParameter>,
149 pub transformations: Vec<Transformation>,
150}
151
152impl Parameters {
153 fn new(capacity: usize) -> Self {
154 Self {
155 rust_parameters: Vec::with_capacity(capacity),
156 c_parameters: Vec::with_capacity(capacity),
157 transformations: Vec::with_capacity(capacity),
158 }
159 }
160
161 pub fn analyze_return(&mut self, env: &Env, ret: &Option<analysis::Parameter>) {
162 let ret_data = ret
163 .as_ref()
164 .map(|r| (r.lib_par.array_length(), &r.try_from_glib));
165
166 let (ind_c, try_from_glib) = match ret_data {
167 Some((Some(array_length), try_from_glib)) => (array_length as usize, try_from_glib),
168 _ => return,
169 };
170
171 let c_par = if let Some(c_par) = self.c_parameters.get_mut(ind_c) {
172 c_par.try_from_glib = try_from_glib.clone();
173 c_par
174 } else {
175 return;
176 };
177
178 let transformation = Transformation {
179 ind_c,
180 ind_rust: None,
181 transformation_type: get_length_type(env, "", &c_par.name, c_par.typ),
182 };
183 self.transformations.push(transformation);
184 }
185}
186
187pub fn analyze(
188 env: &Env,
189 function_parameters: &[library::Parameter],
190 configured_functions: &[&config::functions::Function],
191 disable_length_detect: bool,
192 async_func: bool,
193 in_trait: bool,
194) -> Parameters {
195 let mut parameters = Parameters::new(function_parameters.len());
196
197 let array_lengths: HashMap<u32, &library::Parameter> = function_parameters
199 .iter()
200 .filter_map(|p| p.array_length().map(|pos| (pos, p)))
201 .collect();
202
203 let mut to_remove = Vec::new();
204 let mut correction_instance = 0;
205
206 for par in function_parameters.iter() {
207 if par.scope().is_none() {
208 continue;
209 }
210 if let Some(index) = par.closure() {
211 to_remove.push(index);
212 }
213 if let Some(index) = par.destroy() {
214 to_remove.push(index);
215 }
216 }
217
218 for (pos, par) in function_parameters.iter().enumerate() {
219 let is_instance_parameter = par.is_instance();
220 let name = if is_instance_parameter {
221 par.name().to_owned()
222 } else {
223 nameutil::mangle_keywords(par.name()).into_owned()
224 };
225 if is_instance_parameter {
226 correction_instance = 1;
227 }
228
229 let configured_parameters = configured_functions.matched_parameters(&name);
230
231 let c_type = par.c_type();
232 let typ = override_string_type_parameter(env, par.typ(), &configured_parameters);
233
234 let ind_c = parameters.c_parameters.len();
235 let mut ind_rust = Some(parameters.rust_parameters.len());
236
237 let mut add_rust_parameter = match par.direction() {
238 library::ParameterDirection::In | library::ParameterDirection::InOut => true,
239 library::ParameterDirection::Return => false,
240 library::ParameterDirection::Out => !can_as_return(env, par) && !async_func,
241 library::ParameterDirection::None => {
242 panic!("undefined direction for parameter {par:?}")
243 }
244 };
245
246 if async_func
247 && pos >= correction_instance
248 && to_remove.contains(&(pos - correction_instance))
249 {
250 add_rust_parameter = false;
251 }
252 let mut transfer = par.transfer_ownership();
253
254 let mut caller_allocates = par.is_caller_allocates();
255 let conversion = ConversionType::of(env, typ);
256 if let ConversionType::Direct
257 | ConversionType::Scalar
258 | ConversionType::Option
259 | ConversionType::Result { .. } = conversion
260 {
261 caller_allocates = false;
263 transfer = gir_parser::TransferOwnership::None;
264 }
265 let move_ = configured_parameters
266 .iter()
267 .find_map(|p| p.move_)
268 .unwrap_or_else(|| {
269 if matches!(env.library.type_(typ), library::Type::CArray(_)) {
273 false
274 } else {
275 transfer == gir_parser::TransferOwnership::Full && par.direction().is_in()
276 }
277 });
278 let mut array_par = configured_parameters.iter().find_map(|cp| {
279 cp.length_of
280 .as_ref()
281 .and_then(|n| function_parameters.iter().find(|fp| fp.name() == *n))
282 });
283 if array_par.is_none() {
284 array_par = array_lengths.get(&(pos as u32)).copied();
285 }
286 if array_par.is_none() && !disable_length_detect {
287 array_par = detect_length(env, pos, par, function_parameters);
288 }
289 if let Some(array_par) = array_par {
290 let mut array_name = nameutil::mangle_keywords(array_par.name());
291 if let Some(bound_type) = Bounds::type_for(env, array_par.typ()) {
292 array_name = (array_name.into_owned()
293 + &Bounds::get_to_glib_extra(
294 &bound_type,
295 array_par.is_nullable(),
296 array_par.is_instance(),
297 move_,
298 ))
299 .into();
300 }
301
302 add_rust_parameter = false;
303
304 let transformation = Transformation {
305 ind_c,
306 ind_rust: None,
307 transformation_type: get_length_type(env, &array_name, par.name(), typ),
308 };
309 parameters.transformations.push(transformation);
310 }
311
312 let immutable = configured_parameters.iter().any(|p| p.constant);
313 let ref_mode =
314 RefMode::without_unneeded_mut(env, par, immutable, in_trait && is_instance_parameter);
315
316 let nullable_override = configured_parameters.iter().find_map(|p| p.nullable);
317 let nullable = nullable_override.unwrap_or(par.is_nullable());
318
319 let try_from_glib = TryFromGlib::from_parameter(env, typ, &configured_parameters);
320
321 let c_par = CParameter {
322 name: name.clone(),
323 typ,
324 c_type: c_type.to_owned(),
325 is_instance_parameter,
326 direction: par.direction(),
327 transfer,
328 caller_allocates,
329 nullable,
330 ref_mode,
331 is_error: matches!(par, library::Parameter::Error(_)),
332 scope: par.scope(),
333 user_data_index: par.closure(),
334 destroy_index: par.destroy(),
335 try_from_glib: try_from_glib.clone(),
336 move_,
337 };
338 parameters.c_parameters.push(c_par);
339
340 let data_param_name = "user_data";
341 let callback_param_name = "callback";
342
343 if add_rust_parameter {
344 let rust_par = RustParameter {
345 name: name.clone(),
346 typ,
347 ind_c,
348 };
349 parameters.rust_parameters.push(rust_par);
350 } else {
351 ind_rust = None;
352 }
353
354 let transformation_type = match conversion {
355 ConversionType::Direct => {
356 if par.c_type() != "GLib.Pid" {
357 TransformationType::ToGlibDirect { name }
358 } else {
359 TransformationType::ToGlibScalar {
360 name,
361 nullable,
362 needs_into: false,
363 }
364 }
365 }
366 ConversionType::Scalar => TransformationType::ToGlibScalar {
367 name,
368 nullable,
369 needs_into: false,
370 },
371 ConversionType::Option => {
372 let needs_into = match try_from_glib {
373 TryFromGlib::Option => par.direction().is_in(),
374 TryFromGlib::OptionMandatory => false,
375 other => unreachable!("{:?} inconsistent / conversion type", other),
376 };
377 TransformationType::ToGlibScalar {
378 name,
379 nullable: false,
380 needs_into,
381 }
382 }
383 ConversionType::Result { .. } => {
384 let needs_into = match try_from_glib {
385 TryFromGlib::Result { .. } => par.direction().is_in(),
386 TryFromGlib::ResultInfallible { .. } => false,
387 other => unreachable!("{:?} inconsistent / conversion type", other),
388 };
389 TransformationType::ToGlibScalar {
390 name,
391 nullable: false,
392 needs_into,
393 }
394 }
395 ConversionType::Pointer => TransformationType::ToGlibPointer {
396 name,
397 is_instance_parameter,
398 transfer,
399 ref_mode,
400 to_glib_extra: Default::default(),
401 explicit_target_type: Default::default(),
402 pointer_cast: if matches!(env.library.type_(typ), library::Type::CArray(_))
403 && par.c_type() == "gpointer"
404 {
405 format!(" as {}", nameutil::use_glib_if_needed(env, "ffi::gpointer"))
406 } else {
407 Default::default()
408 },
409 in_trait,
410 nullable,
411 move_,
412 },
413 ConversionType::Borrow => TransformationType::ToGlibBorrow,
414 ConversionType::Unknown => TransformationType::ToGlibUnknown {
415 name: name.to_owned(),
416 },
417 };
418
419 let mut transformation = Transformation {
420 ind_c,
421 ind_rust,
422 transformation_type,
423 };
424 let mut transformation_type = None;
425 match transformation.transformation_type {
426 TransformationType::ToGlibDirect { ref name, .. }
427 | TransformationType::ToGlibUnknown { ref name, .. }
428 if async_func && name == callback_param_name =>
429 {
430 transformation_type = Some(TransformationType::ToSome(name.clone()));
432 }
433 TransformationType::ToGlibPointer { ref name, .. }
434 if async_func && name == data_param_name =>
435 {
436 transformation_type = Some(TransformationType::IntoRaw(name.clone()));
439 }
440 _ => (),
441 }
442 if let Some(transformation_type) = transformation_type {
443 transformation.transformation_type = transformation_type;
444 }
445 parameters.transformations.push(transformation);
446 }
447
448 parameters
449}
450
451fn get_length_type(
452 env: &Env,
453 array_name: &str,
454 length_name: &str,
455 length_typ: TypeId,
456) -> TransformationType {
457 let array_length_type = RustType::try_new(env, length_typ).into_string();
458 TransformationType::Length {
459 array_name: array_name.to_string(),
460 array_length_name: length_name.to_string(),
461 array_length_type,
462 }
463}
464
465fn detect_length<'a>(
466 env: &Env,
467 pos: usize,
468 par: &library::Parameter,
469 parameters: &'a [library::Parameter],
470) -> Option<&'a library::Parameter> {
471 if !is_length(par) || pos == 0 {
472 return None;
473 }
474
475 parameters.get(pos - 1).and_then(|p| {
476 if has_length(env, p.typ()) {
477 Some(p)
478 } else {
479 None
480 }
481 })
482}
483
484fn is_length(par: &library::Parameter) -> bool {
485 if !par.direction().is_in() {
486 return false;
487 }
488
489 let len = par.name().len();
490 if len >= 3 && &par.name()[len - 3..len] == "len" {
491 return true;
492 }
493
494 par.name().contains("length")
495}
496
497fn has_length(env: &Env, typ: TypeId) -> bool {
498 use crate::library::{Basic::*, Type};
499 let typ = env.library.type_(typ);
500 match typ {
501 Type::Basic(Utf8 | Filename | OsString) => true,
502 Type::CArray(..)
503 | Type::FixedArray(..)
504 | Type::Array(..)
505 | Type::PtrArray(..)
506 | Type::List(..)
507 | Type::SList(..)
508 | Type::HashTable(..) => true,
509 Type::Alias(alias) => has_length(env, alias.typ),
510 _ => false,
511 }
512}