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