libgir/analysis/
function_parameters.rs

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// TODO: remove unused fields
48#[derive(Clone, Debug)]
49pub struct RustParameter {
50    pub ind_c: usize, // index in `Vec<CParameter>`
51    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    /// Index of the user data parameter associated with the callback.
68    pub user_data_index: Option<usize>,
69    /// Index of the destroy notification parameter associated with the
70    /// callback.
71    pub destroy_index: Option<usize>,
72
73    // analysis fields
74    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        // filled by functions
95        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,            // index in `Vec<CParameter>`
139    pub ind_rust: Option<usize>, // index in `Vec<RustParameter>`
140    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    // Map: length argument position => parameter
196    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            // For simple types no reason to have these flags
259            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                // FIXME: drop the condition here once we have figured out how to handle
267                // the Vec<T> use case, e.g with something like PtrSlice
268
269                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                    // Remove the conversion of callback for async functions.
425                    transformation_type = Some(TransformationType::ToSome(name.clone()));
426                }
427            }
428            TransformationType::ToGlibPointer { ref name, .. } => {
429                if async_func && name == data_param_name {
430                    // Do the conversion of user_data for async functions.
431                    // In async functions, this argument is used to send the callback.
432                    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}