Skip to main content

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, 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// TODO: remove unused fields
50#[derive(Clone, Debug)]
51pub struct RustParameter {
52    pub ind_c: usize, // index in `Vec<CParameter>`
53    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    /// Index of the user data parameter associated with the callback.
70    pub user_data_index: Option<usize>,
71    /// Index of the destroy notification parameter associated with the
72    /// callback.
73    pub destroy_index: Option<usize>,
74
75    // analysis fields
76    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        // filled by functions
97        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,            // index in `Vec<CParameter>`
141    pub ind_rust: Option<usize>, // index in `Vec<RustParameter>`
142    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    // Map: length argument position => parameter
198    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            // For simple types no reason to have these flags
262            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                // FIXME: drop the condition here once we have figured out how to handle
270                // the Vec<T> use case, e.g with something like PtrSlice
271
272                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                // Remove the conversion of callback for async functions.
431                transformation_type = Some(TransformationType::ToSome(name.clone()));
432            }
433            TransformationType::ToGlibPointer { ref name, .. }
434                if async_func && name == data_param_name =>
435            {
436                // Do the conversion of user_data for async functions.
437                // In async functions, this argument is used to send the callback.
438                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}