libgir/analysis/
functions.rs

1// TODO: better heuristic (https://bugzilla.gnome.org/show_bug.cgi?id=623635#c5)
2// TODO: ProgressCallback types (not specific to async).
3// TODO: add annotation for methods like g_file_replace_contents_bytes_async
4// where the finish method has a different prefix.
5
6use std::{
7    borrow::Borrow,
8    collections::{HashMap, HashSet},
9};
10
11use log::warn;
12
13use super::{namespaces::NsId, special_functions};
14use crate::{
15    analysis::{
16        self,
17        bounds::{Bounds, CallbackInfo},
18        function_parameters::{self, CParameter, Parameters, Transformation, TransformationType},
19        imports::Imports,
20        is_gpointer,
21        out_parameters::{self, use_function_return_for_result},
22        ref_mode::RefMode,
23        return_value,
24        rust_type::*,
25        safety_assertion_mode::SafetyAssertionMode,
26        signatures::{Signature, Signatures},
27        trampolines::Trampoline,
28    },
29    codegen::Visibility,
30    config::{self, gobjects::GStatus},
31    env::Env,
32    library::{
33        self, Function, FunctionKind, MAIN_NAMESPACE, ParameterDirection, ParameterScope, Transfer,
34        Type,
35    },
36    nameutil,
37    traits::*,
38    version::Version,
39};
40
41#[derive(Clone, Debug)]
42pub struct AsyncTrampoline {
43    pub is_method: bool,
44    pub has_error_parameter: bool,
45    pub name: String,
46    pub finish_func_name: String,
47    pub callback_type: String,
48    pub bound_name: char,
49    pub output_params: Vec<analysis::Parameter>,
50    pub ffi_ret: Option<analysis::Parameter>,
51}
52
53#[derive(Clone, Debug)]
54pub struct AsyncFuture {
55    pub is_method: bool,
56    pub name: String,
57    pub success_parameters: String,
58    pub error_parameters: Option<String>,
59    pub assertion: SafetyAssertionMode,
60}
61
62#[derive(Debug)]
63pub struct Info {
64    pub name: String,
65    pub func_name: String,
66    pub new_name: Option<String>,
67    pub glib_name: String,
68    pub status: GStatus,
69    pub kind: library::FunctionKind,
70    pub visibility: Visibility,
71    pub type_name: Result,
72    pub parameters: Parameters,
73    pub ret: return_value::Info,
74    pub bounds: Bounds,
75    pub outs: out_parameters::Info,
76    pub version: Option<Version>,
77    pub deprecated_version: Option<Version>,
78    pub not_version: Option<Version>,
79    pub cfg_condition: Option<String>,
80    pub assertion: SafetyAssertionMode,
81    pub doc_hidden: bool,
82    pub doc_trait_name: Option<String>,
83    pub doc_struct_name: Option<String>,
84    pub doc_ignore_parameters: HashSet<String>,
85    pub r#async: bool,
86    pub unsafe_: bool,
87    pub trampoline: Option<AsyncTrampoline>,
88    pub callbacks: Vec<Trampoline>,
89    pub destroys: Vec<Trampoline>,
90    pub remove_params: Vec<usize>,
91    pub async_future: Option<AsyncFuture>,
92    /// Whether the function is hidden (an implementation detail)
93    /// Like the ref/unref/copy/free functions
94    pub hidden: bool,
95    /// Whether the function can't be generated
96    pub commented: bool,
97    /// In order to generate docs links we need to know in which namespace
98    /// this potential global function is defined
99    pub ns_id: NsId,
100    pub generate_doc: bool,
101    pub get_property: Option<String>,
102    pub set_property: Option<String>,
103}
104
105impl Info {
106    pub fn codegen_name(&self) -> &str {
107        self.new_name.as_ref().unwrap_or(&self.name)
108    }
109
110    pub fn is_special(&self) -> bool {
111        self.codegen_name()
112            .trim_end_matches('_')
113            .rsplit('_')
114            .next()
115            .is_some_and(|i| i.parse::<special_functions::Type>().is_ok())
116    }
117
118    // returns whether the method can be linked in the docs
119    pub fn should_be_doc_linked(&self, env: &Env) -> bool {
120        self.should_docs_be_generated(env)
121            && (self.status.manual() || (!self.commented && !self.hidden))
122    }
123
124    pub fn should_docs_be_generated(&self, env: &Env) -> bool {
125        !self.status.ignored() && !self.is_special() && !self.is_async_finish(env)
126    }
127
128    pub fn doc_link(
129        &self,
130        parent: Option<&str>,
131        visible_parent: Option<&str>,
132        is_self: bool,
133    ) -> String {
134        if let Some(p) = parent {
135            if is_self {
136                format!("[`{f}()`][Self::{f}()]", f = self.codegen_name())
137            } else {
138                format!(
139                    "[`{visible_parent}::{f}()`][crate::{p}::{f}()]",
140                    visible_parent = visible_parent.unwrap_or(p),
141                    p = p,
142                    f = self.codegen_name()
143                )
144            }
145        } else {
146            format!(
147                "[`{fn_name}()`][crate::{fn_name}()]",
148                fn_name = self.codegen_name()
149            )
150        }
151    }
152
153    pub fn is_async_finish(&self, env: &Env) -> bool {
154        let has_async_result = self
155            .parameters
156            .rust_parameters
157            .iter()
158            .any(|param| param.typ.full_name(&env.library) == "Gio.AsyncResult");
159        self.name.ends_with("_finish") && has_async_result
160    }
161}
162
163pub fn analyze<F: Borrow<library::Function>>(
164    env: &Env,
165    functions: &[F],
166    type_tid: Option<library::TypeId>,
167    in_trait: bool,
168    is_boxed: bool,
169    obj: &config::gobjects::GObject,
170    imports: &mut Imports,
171    mut signatures: Option<&mut Signatures>,
172    deps: Option<&[library::TypeId]>,
173) -> Vec<Info> {
174    let mut funcs = Vec::new();
175
176    'func: for func in functions {
177        let func = func.borrow();
178        let configured_functions = obj.functions.matched(&func.name);
179        let mut status = obj.status;
180        for f in &configured_functions {
181            match f.status {
182                GStatus::Ignore => continue 'func,
183                GStatus::Manual => {
184                    status = GStatus::Manual;
185                    break;
186                }
187                GStatus::Generate => (),
188            }
189        }
190
191        if env.is_totally_deprecated(
192            Some(type_tid.unwrap_or_default().ns_id),
193            func.deprecated_version,
194        ) {
195            continue;
196        }
197        let name = nameutil::mangle_keywords(&*func.name).into_owned();
198        let signature_params = Signature::new(func);
199        let mut not_version = None;
200        if func.kind == library::FunctionKind::Method
201            && let Some(deps) = deps
202        {
203            let (has, version) = signature_params.has_in_deps(env, &name, deps);
204            if has
205                && let Some(v) = version
206                && v > env.config.min_cfg_version
207            {
208                not_version = version;
209            }
210        }
211        if let Some(signatures) = signatures.as_mut() {
212            signatures.insert(name.clone(), signature_params);
213        }
214
215        let mut info = analyze_function(
216            env,
217            obj,
218            &func.name,
219            name,
220            status,
221            func,
222            type_tid,
223            in_trait,
224            is_boxed,
225            &configured_functions,
226            imports,
227        );
228        info.not_version = not_version;
229        funcs.push(info);
230    }
231
232    funcs
233}
234
235fn fixup_gpointer_parameter(
236    env: &Env,
237    type_tid: library::TypeId,
238    is_boxed: bool,
239    in_trait: bool,
240    parameters: &mut Parameters,
241    idx: usize,
242) {
243    use crate::analysis::ffi_type;
244
245    let instance_parameter = idx == 0;
246
247    let glib_name = env.library.type_(type_tid).get_glib_name().unwrap();
248    let ffi_name = ffi_type::ffi_type(env, type_tid, glib_name).unwrap();
249    let pointer_type = if is_boxed { "*const" } else { "*mut" };
250    parameters.rust_parameters[idx].typ = type_tid;
251    parameters.c_parameters[idx].typ = type_tid;
252    parameters.c_parameters[idx].instance_parameter = instance_parameter;
253    parameters.c_parameters[idx].ref_mode = RefMode::ByRef;
254    parameters.c_parameters[idx].transfer = Transfer::None;
255    parameters.transformations[idx] = Transformation {
256        ind_c: idx,
257        ind_rust: Some(idx),
258        transformation_type: TransformationType::ToGlibPointer {
259            name: parameters.rust_parameters[idx].name.clone(),
260            instance_parameter,
261            transfer: Transfer::None,
262            ref_mode: RefMode::ByRef,
263            to_glib_extra: Default::default(),
264            explicit_target_type: format!("{} {}", pointer_type, ffi_name.as_str()),
265            pointer_cast: format!(
266                " as {}",
267                nameutil::use_glib_if_needed(env, "ffi::gconstpointer")
268            ),
269            in_trait,
270            nullable: false,
271            move_: false,
272        },
273    };
274}
275
276fn fixup_special_functions(
277    env: &Env,
278    name: &str,
279    type_tid: library::TypeId,
280    is_boxed: bool,
281    in_trait: bool,
282    parameters: &mut Parameters,
283) {
284    // Workaround for some _hash() / _compare() / _equal() functions taking
285    // "gconstpointer" as arguments instead of the actual type
286    if name == "hash"
287        && parameters.c_parameters.len() == 1
288        && parameters.c_parameters[0].c_type == "gconstpointer"
289    {
290        fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 0);
291    }
292
293    if (name == "compare" || name == "equal" || name == "is_equal")
294        && parameters.c_parameters.len() == 2
295        && parameters.c_parameters[0].c_type == "gconstpointer"
296        && parameters.c_parameters[1].c_type == "gconstpointer"
297    {
298        fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 0);
299        fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 1);
300    }
301}
302
303fn find_callback_bound_to_destructor(
304    callbacks: &[Trampoline],
305    destroy: &mut Trampoline,
306    destroy_index: usize,
307) -> bool {
308    for call in callbacks {
309        if call.destroy_index == destroy_index {
310            destroy.nullable = call.nullable;
311            destroy.bound_name = call.bound_name.clone();
312            return true;
313        }
314    }
315    false
316}
317
318fn analyze_callbacks(
319    env: &Env,
320    func: &library::Function,
321    cross_user_data_check: &mut HashMap<usize, usize>,
322    user_data_indexes: &mut HashSet<usize>,
323    parameters: &mut Parameters,
324    used_types: &mut Vec<String>,
325    bounds: &mut Bounds,
326    to_glib_extras: &mut HashMap<usize, String>,
327    imports: &mut Imports,
328    destroys: &mut Vec<Trampoline>,
329    callbacks: &mut Vec<Trampoline>,
330    params: &mut Vec<library::Parameter>,
331    configured_functions: &[&config::functions::Function],
332    disable_length_detect: bool,
333    in_trait: bool,
334    commented: &mut bool,
335    concurrency: library::Concurrency,
336    type_tid: library::TypeId,
337) {
338    let mut to_replace = Vec::new();
339    let mut to_remove = Vec::new();
340
341    {
342        // When closure data and destroy are specified in gir, they don't take into
343        // account the actual closure parameter.
344        let mut c_parameters = Vec::new();
345        for (pos, par) in parameters.c_parameters.iter().enumerate() {
346            if par.instance_parameter {
347                continue;
348            }
349            c_parameters.push((par, pos));
350        }
351
352        let func_name = match &func.c_identifier {
353            Some(n) => n,
354            None => &func.name,
355        };
356        let mut destructors_to_update = Vec::new();
357        for pos in 0..parameters.c_parameters.len() {
358            // If it is a user data parameter, we ignore it.
359            if cross_user_data_check.values().any(|p| *p == pos) || user_data_indexes.contains(&pos)
360            {
361                continue;
362            }
363            let par = &parameters.c_parameters[pos];
364            assert!(
365                !par.instance_parameter || pos == 0,
366                "Wrong instance parameter in {}",
367                func.c_identifier.as_ref().unwrap()
368            );
369            if let Ok(rust_type) = RustType::builder(env, par.typ)
370                .direction(par.direction)
371                .try_from_glib(&par.try_from_glib)
372                .try_build()
373            {
374                used_types.extend(rust_type.into_used_types());
375            }
376            let rust_type = env.library.type_(par.typ);
377            let callback_info = if !*par.nullable || !rust_type.is_function() {
378                let (to_glib_extra, callback_info) = bounds.add_for_parameter(
379                    env,
380                    func,
381                    par,
382                    false,
383                    concurrency,
384                    configured_functions,
385                );
386                if let Some(to_glib_extra) = to_glib_extra {
387                    let pos_adjusted_for_removed_params =
388                        pos - to_remove.len() - cross_user_data_check.len();
389                    if par.c_type != "GDestroyNotify" {
390                        to_glib_extras.insert(pos_adjusted_for_removed_params, to_glib_extra);
391                    }
392                }
393                callback_info
394            } else {
395                None
396            };
397
398            if rust_type.is_function() {
399                if par.c_type != "GDestroyNotify" {
400                    let callback_parameters_config = configured_functions.iter().find_map(|f| {
401                        f.parameters
402                            .iter()
403                            .find(|p| p.ident.is_match(&par.name))
404                            .map(|p| &p.callback_parameters)
405                    });
406                    if let Some((mut callback, destroy_index)) = analyze_callback(
407                        func_name,
408                        type_tid,
409                        env,
410                        par,
411                        &callback_info,
412                        commented,
413                        imports,
414                        &c_parameters,
415                        rust_type,
416                        callback_parameters_config,
417                    ) {
418                        if let Some(destroy_index) = destroy_index {
419                            let user_data = cross_user_data_check
420                                .entry(destroy_index)
421                                .or_insert_with(|| callback.user_data_index);
422                            if *user_data != callback.user_data_index {
423                                warn_main!(
424                                    type_tid,
425                                    "`{}`: Different destructors cannot share the same user data",
426                                    func_name
427                                );
428                                *commented = true;
429                            }
430                            callback.destroy_index = destroy_index;
431                        } else {
432                            user_data_indexes.insert(callback.user_data_index);
433                            to_remove.push(callback.user_data_index);
434                        }
435                        callbacks.push(callback);
436                        to_replace.push((pos, par.typ));
437                        continue;
438                    }
439                } else if let Some((mut callback, _)) = analyze_callback(
440                    func_name,
441                    type_tid,
442                    env,
443                    par,
444                    &callback_info,
445                    commented,
446                    imports,
447                    &c_parameters,
448                    rust_type,
449                    None,
450                ) {
451                    // We just assume that for API "cleanness", the destroy callback will always
452                    // be |-> *after* <-| the initial callback.
453                    if let Some(user_data_index) = cross_user_data_check.get(&pos) {
454                        callback.user_data_index = *user_data_index;
455                        callback.destroy_index = pos;
456                    } else {
457                        warn_main!(
458                            type_tid,
459                            "`{}`: no user data point to the destroy callback",
460                            func_name,
461                        );
462                        *commented = true;
463                    }
464                    // We check if the user trampoline is there. If so, we change the destroy
465                    // nullable value if needed.
466                    if !find_callback_bound_to_destructor(callbacks, &mut callback, pos) {
467                        // Maybe the linked callback is after so we store it just in case...
468                        destructors_to_update.push((pos, destroys.len()));
469                    }
470                    destroys.push(callback);
471                    to_remove.push(pos);
472                    continue;
473                }
474            }
475            if !*commented {
476                *commented |= RustType::builder(env, par.typ)
477                    .direction(par.direction)
478                    .scope(par.scope)
479                    .try_from_glib(&par.try_from_glib)
480                    .try_build_param()
481                    .is_err();
482            }
483        }
484        for (destroy_index, pos_in_destroys) in destructors_to_update {
485            if !find_callback_bound_to_destructor(
486                callbacks,
487                &mut destroys[pos_in_destroys],
488                destroy_index,
489            ) {
490                warn_main!(
491                    type_tid,
492                    "`{}`: destructor without linked callback",
493                    func_name
494                );
495            }
496        }
497    }
498
499    // Check for cross "user data".
500    if cross_user_data_check
501        .values()
502        .collect::<Vec<_>>()
503        .windows(2)
504        .any(|a| a[0] == a[1])
505    {
506        *commented = true;
507        warn_main!(
508            type_tid,
509            "`{}`: Different user data share the same destructors",
510            func.name
511        );
512    }
513
514    if !destroys.is_empty() || !callbacks.is_empty() {
515        for (pos, typ) in to_replace {
516            let ty = env.library.type_(typ);
517            params[pos].typ = typ;
518            params[pos].c_type = ty.get_glib_name().unwrap().to_owned();
519        }
520        let mut s = to_remove
521            .iter()
522            .chain(cross_user_data_check.values())
523            .collect::<HashSet<_>>() // To prevent duplicates.
524            .into_iter()
525            .collect::<Vec<_>>();
526        s.sort(); // We need to sort the array, otherwise the indexes won't be working
527        // anymore.
528        for pos in s.iter().rev() {
529            params.remove(**pos);
530        }
531        *parameters = function_parameters::analyze(
532            env,
533            params,
534            configured_functions,
535            disable_length_detect,
536            false,
537            in_trait,
538        );
539    } else {
540        warn_main!(
541            type_tid,
542            "`{}`: this is supposed to be a callback function but no callback was found...",
543            func.name
544        );
545        *commented = true;
546    }
547}
548
549fn analyze_function(
550    env: &Env,
551    obj: &config::gobjects::GObject,
552    func_name: &str,
553    name: String,
554    status: GStatus,
555    func: &library::Function,
556    type_tid: Option<library::TypeId>,
557    in_trait: bool,
558    is_boxed: bool,
559    configured_functions: &[&config::functions::Function],
560    imports: &mut Imports,
561) -> Info {
562    let ns_id = type_tid.map_or(MAIN_NAMESPACE, |t| t.ns_id);
563    let type_tid = type_tid.unwrap_or_default();
564    let r#async = func.finish_func.is_some()
565        || func.parameters.iter().any(|parameter| {
566            parameter.scope == ParameterScope::Async && parameter.c_type == "GAsyncReadyCallback"
567        });
568    let has_callback_parameter = !r#async
569        && func
570            .parameters
571            .iter()
572            .any(|par| env.library.type_(par.typ).is_function());
573    let concurrency = match env.library.type_(type_tid) {
574        library::Type::Class(_) | library::Type::Interface(_) | library::Type::Record(_) => {
575            obj.concurrency
576        }
577        _ => library::Concurrency::SendSync,
578    };
579
580    let mut commented = false;
581    let mut bounds: Bounds = Default::default();
582    let mut to_glib_extras = HashMap::<usize, String>::new();
583    let mut used_types: Vec<String> = Vec::with_capacity(4);
584    let mut trampoline = None;
585    let mut callbacks = Vec::new();
586    let mut destroys = Vec::new();
587    let mut async_future = None;
588
589    if !r#async
590        && !has_callback_parameter
591        && func
592            .parameters
593            .iter()
594            .any(|par| par.c_type == "GDestroyNotify")
595    {
596        // In here, We have a DestroyNotify callback but no other callback is provided.
597        // A good example of this situation is this function:
598        // https://developer.gnome.org/gio/stable/GTlsPassword.html#g-tls-password-set-value-full
599        warn_main!(
600            type_tid,
601            "Function \"{}\" with destroy callback without callbacks",
602            func.name
603        );
604        commented = true;
605    }
606
607    let mut new_name = configured_functions.iter().find_map(|f| f.rename.clone());
608    let is_constructor = configured_functions.iter().find_map(|f| f.is_constructor);
609
610    let bypass_auto_rename = configured_functions.iter().any(|f| f.bypass_auto_rename);
611    let is_constructor = is_constructor.unwrap_or(false);
612    if !bypass_auto_rename && new_name.is_none() {
613        if func.kind == library::FunctionKind::Constructor || is_constructor {
614            if func.kind == library::FunctionKind::Constructor && is_constructor {
615                warn_main!(
616                    type_tid,
617                    "`{}`: config forces 'constructor' on an already gir-annotated 'constructor'",
618                    func_name
619                );
620            }
621
622            if name.starts_with("new_from")
623                || name.starts_with("new_with")
624                || name.starts_with("new_for")
625            {
626                new_name = Some(name[4..].to_string());
627            }
628        } else {
629            let nb_in_params = func
630                .parameters
631                .iter()
632                .filter(|param| library::ParameterDirection::In == param.direction)
633                .fold(0, |acc, _| acc + 1);
634            let is_bool_getter = (func.parameters.len() == nb_in_params)
635                && (func.ret.typ == library::TypeId::tid_bool()
636                    || func.ret.typ == library::TypeId::tid_c_bool());
637            new_name = getter_rules::try_rename_would_be_getter(&name, is_bool_getter)
638                .ok()
639                .map(getter_rules::NewName::unwrap);
640        }
641    }
642
643    let version = configured_functions
644        .iter()
645        .filter_map(|f| f.version)
646        .min()
647        .or(func.version);
648
649    let version = env.config.filter_version(version);
650    let deprecated_version = func.deprecated_version;
651    let visibility = configured_functions
652        .iter()
653        .find_map(|f| f.visibility)
654        .unwrap_or_default();
655    let cfg_condition = configured_functions
656        .iter()
657        .find_map(|f| f.cfg_condition.clone());
658    let doc_hidden = configured_functions.iter().any(|f| f.doc_hidden);
659    let doc_trait_name = configured_functions
660        .iter()
661        .find_map(|f| f.doc_trait_name.clone());
662    let doc_struct_name = configured_functions
663        .iter()
664        .find_map(|f| f.doc_struct_name.clone());
665    let doc_ignore_parameters = configured_functions
666        .iter()
667        .find(|f| !f.doc_ignore_parameters.is_empty())
668        .map(|f| f.doc_ignore_parameters.clone())
669        .unwrap_or_default();
670    let disable_length_detect = configured_functions.iter().any(|f| f.disable_length_detect);
671    let no_future = configured_functions.iter().any(|f| f.no_future);
672    let unsafe_ = configured_functions.iter().any(|f| f.unsafe_);
673    let assertion = configured_functions.iter().find_map(|f| f.assertion);
674
675    let imports = &mut imports.with_defaults(version, &cfg_condition);
676
677    let ret = return_value::analyze(
678        env,
679        obj,
680        func,
681        type_tid,
682        configured_functions,
683        &mut used_types,
684        imports,
685    );
686    commented |= ret.commented;
687
688    let mut params = func.parameters.clone();
689    let mut parameters = function_parameters::analyze(
690        env,
691        &params,
692        configured_functions,
693        disable_length_detect,
694        r#async,
695        in_trait,
696    );
697    parameters.analyze_return(env, &ret.parameter);
698
699    if let Some(ref f) = ret.parameter
700        && let Type::Function(_) = env.library.type_(f.lib_par.typ)
701        && env.config.work_mode.is_normal()
702    {
703        warn!("Function \"{}\" returns callback", func.name);
704        commented = true;
705    }
706
707    fixup_special_functions(
708        env,
709        name.as_str(),
710        type_tid,
711        is_boxed,
712        in_trait,
713        &mut parameters,
714    );
715
716    // Key: destroy callback index
717    // Value: associated user data index
718    let mut cross_user_data_check: HashMap<usize, usize> = HashMap::new();
719    let mut user_data_indexes: HashSet<usize> = HashSet::new();
720
721    if status.need_generate() {
722        if !has_callback_parameter {
723            let mut to_remove = Vec::new();
724            let mut correction_instance = 0;
725            for par in parameters.c_parameters.iter() {
726                if par.scope.is_none() {
727                    continue;
728                }
729                if let Some(index) = par.user_data_index {
730                    to_remove.push(index);
731                }
732                if let Some(index) = par.destroy_index {
733                    to_remove.push(index);
734                }
735            }
736            for (pos, par) in parameters.c_parameters.iter().enumerate() {
737                if par.instance_parameter {
738                    correction_instance = 1;
739                }
740
741                if r#async
742                    && pos >= correction_instance
743                    && to_remove.contains(&(pos - correction_instance))
744                {
745                    continue;
746                }
747                assert!(
748                    !par.instance_parameter || pos == 0,
749                    "Wrong instance parameter in {}",
750                    func.c_identifier.as_ref().unwrap()
751                );
752                if let Ok(rust_type) = RustType::builder(env, par.typ)
753                    .direction(par.direction)
754                    .try_from_glib(&par.try_from_glib)
755                    .try_build()
756                    && (!rust_type.as_str().ends_with("GString") || par.c_type == "gchar***")
757                {
758                    used_types.extend(rust_type.into_used_types());
759                }
760                let (to_glib_extra, callback_info) = bounds.add_for_parameter(
761                    env,
762                    func,
763                    par,
764                    r#async,
765                    library::Concurrency::None,
766                    configured_functions,
767                );
768                if let Some(to_glib_extra) = to_glib_extra {
769                    to_glib_extras.insert(pos, to_glib_extra);
770                }
771
772                analyze_async(
773                    env,
774                    func,
775                    type_tid,
776                    new_name.as_ref().unwrap_or(&name),
777                    callback_info,
778                    &mut commented,
779                    &mut trampoline,
780                    no_future,
781                    &mut async_future,
782                    configured_functions,
783                    &parameters,
784                );
785                let type_error = !(r#async
786                    && *env.library.type_(par.typ) == Type::Basic(library::Basic::Pointer))
787                    && RustType::builder(env, par.typ)
788                        .direction(par.direction)
789                        .scope(par.scope)
790                        .try_from_glib(&par.try_from_glib)
791                        .try_build_param()
792                        .is_err();
793                if type_error {
794                    commented = true;
795                }
796            }
797            if r#async && trampoline.is_none() {
798                commented = true;
799            }
800        } else {
801            analyze_callbacks(
802                env,
803                func,
804                &mut cross_user_data_check,
805                &mut user_data_indexes,
806                &mut parameters,
807                &mut used_types,
808                &mut bounds,
809                &mut to_glib_extras,
810                imports,
811                &mut destroys,
812                &mut callbacks,
813                &mut params,
814                configured_functions,
815                disable_length_detect,
816                in_trait,
817                &mut commented,
818                concurrency,
819                type_tid,
820            );
821        }
822    }
823
824    for par in &parameters.rust_parameters {
825        // Disallow basic arrays without length
826        let is_len_for_par = |t: &Transformation| {
827            if let TransformationType::Length { ref array_name, .. } = t.transformation_type {
828                array_name == &par.name
829            } else {
830                false
831            }
832        };
833        if is_carray_with_direct_elements(env, par.typ)
834            && !parameters.transformations.iter().any(is_len_for_par)
835        {
836            commented = true;
837        }
838    }
839
840    let (outs, unsupported_outs) = out_parameters::analyze(
841        env,
842        func,
843        &parameters.c_parameters,
844        &ret,
845        configured_functions,
846    );
847    if unsupported_outs {
848        warn_main!(
849            type_tid,
850            "Function {} has unsupported outs",
851            func.c_identifier.as_ref().unwrap_or(&func.name)
852        );
853        commented = true;
854    }
855
856    if r#async && status.need_generate() && !commented {
857        imports.add("std::boxed::Box as Box_");
858        imports.add("std::pin::Pin");
859
860        if let Some(ref trampoline) = trampoline {
861            for out in &trampoline.output_params {
862                if let Ok(rust_type) = RustType::builder(env, out.lib_par.typ)
863                    .direction(ParameterDirection::Out)
864                    .try_build()
865                {
866                    used_types.extend(rust_type.into_used_types());
867                }
868            }
869            if let Some(ref out) = trampoline.ffi_ret
870                && let Ok(rust_type) = RustType::builder(env, out.lib_par.typ)
871                    .direction(ParameterDirection::Return)
872                    .try_build()
873            {
874                used_types.extend(rust_type.into_used_types());
875            }
876        }
877    }
878
879    if status.need_generate() && !commented {
880        if (!destroys.is_empty() || !callbacks.is_empty())
881            && callbacks.iter().any(|c| !c.scope.is_call())
882        {
883            imports.add("std::boxed::Box as Box_");
884        }
885
886        for transformation in &mut parameters.transformations {
887            if let Some(to_glib_extra) = to_glib_extras.get(&transformation.ind_c) {
888                transformation
889                    .transformation_type
890                    .set_to_glib_extra(to_glib_extra);
891            }
892        }
893
894        imports.add("crate::ffi");
895
896        imports.add_used_types(&used_types);
897        if ret.base_tid.is_some() {
898            imports.add("glib::prelude::*");
899        }
900
901        if func.name.parse::<special_functions::Type>().is_err()
902            || parameters.c_parameters.iter().any(|p| p.move_)
903        {
904            imports.add("glib::translate::*");
905        }
906        bounds.update_imports(imports);
907    }
908
909    let is_method = func.kind == library::FunctionKind::Method;
910    let assertion =
911        assertion.unwrap_or_else(|| SafetyAssertionMode::of(env, is_method, &parameters));
912
913    let generate_doc = configured_functions.iter().all(|f| f.generate_doc);
914
915    Info {
916        name,
917        func_name: func_name.to_string(),
918        new_name,
919        glib_name: func.c_identifier.as_ref().unwrap().clone(),
920        status,
921        kind: func.kind,
922        visibility,
923        type_name: RustType::try_new(env, type_tid),
924        parameters,
925        ret,
926        bounds,
927        outs,
928        version,
929        deprecated_version,
930        not_version: None,
931        cfg_condition,
932        assertion,
933        doc_hidden,
934        doc_trait_name,
935        doc_struct_name,
936        doc_ignore_parameters,
937        r#async,
938        unsafe_,
939        trampoline,
940        async_future,
941        callbacks,
942        destroys,
943        remove_params: cross_user_data_check.values().copied().collect::<Vec<_>>(),
944        commented,
945        hidden: false,
946        ns_id,
947        generate_doc,
948        get_property: func.get_property.clone(),
949        set_property: func.set_property.clone(),
950    }
951}
952
953pub fn is_carray_with_direct_elements(env: &Env, typ: library::TypeId) -> bool {
954    match *env.library.type_(typ) {
955        Type::CArray(inner_tid) => {
956            use super::conversion_type::ConversionType;
957            matches!(env.library.type_(inner_tid), Type::Basic(..) if ConversionType::of(env, inner_tid) == ConversionType::Direct)
958        }
959        _ => false,
960    }
961}
962
963fn analyze_async(
964    env: &Env,
965    func: &library::Function,
966    type_tid: library::TypeId,
967    codegen_name: &str,
968    callback_info: Option<CallbackInfo>,
969    commented: &mut bool,
970    trampoline: &mut Option<AsyncTrampoline>,
971    no_future: bool,
972    async_future: &mut Option<AsyncFuture>,
973    configured_functions: &[&config::functions::Function],
974    parameters: &function_parameters::Parameters,
975) -> bool {
976    if let Some(CallbackInfo {
977        callback_type,
978        success_parameters,
979        error_parameters,
980        bound_name,
981    }) = callback_info
982    {
983        // Checks for /*Ignored*/ or other error comments
984        *commented |= callback_type.contains("/*");
985        let func_name = func.c_identifier.as_ref().unwrap();
986        let finish_func_name = if let Some(finish_func_name) = &func.finish_func {
987            finish_func_name.to_string()
988        } else {
989            finish_function_name(func_name)
990        };
991        let mut output_params = vec![];
992        let mut ffi_ret = None;
993        if let Some(function) = find_function(env, &finish_func_name) {
994            if use_function_return_for_result(
995                env,
996                function.ret.typ,
997                &func.name,
998                configured_functions,
999            ) {
1000                ffi_ret = Some(analysis::Parameter::from_return_value(
1001                    env,
1002                    &function.ret,
1003                    configured_functions,
1004                ));
1005            }
1006
1007            for param in &function.parameters {
1008                let mut lib_par = param.clone();
1009                if nameutil::needs_mangling(&param.name) {
1010                    lib_par.name = nameutil::mangle_keywords(&*param.name).into_owned();
1011                }
1012                let configured_parameters = configured_functions.matched_parameters(&lib_par.name);
1013                output_params.push(analysis::Parameter::from_parameter(
1014                    env,
1015                    &lib_par,
1016                    &configured_parameters,
1017                ));
1018            }
1019        }
1020        if trampoline.is_some() || async_future.is_some() {
1021            warn_main!(
1022                type_tid,
1023                "{}: Cannot handle callbacks and async parameters at the same time for the \
1024                 moment",
1025                func.name
1026            );
1027            *commented = true;
1028            return false;
1029        }
1030        if !*commented && success_parameters.is_empty() {
1031            if success_parameters.is_empty() {
1032                warn_main!(
1033                    type_tid,
1034                    "{}: missing success parameters for async future",
1035                    func.name
1036                );
1037            }
1038            *commented = true;
1039            return false;
1040        }
1041        let is_method = func.kind == FunctionKind::Method;
1042
1043        *trampoline = Some(AsyncTrampoline {
1044            is_method,
1045            has_error_parameter: error_parameters.is_some(),
1046            name: format!("{codegen_name}_trampoline"),
1047            finish_func_name: format!("{}::{}", env.main_sys_crate_name(), finish_func_name),
1048            callback_type,
1049            bound_name,
1050            output_params,
1051            ffi_ret,
1052        });
1053
1054        if !no_future {
1055            *async_future = Some(AsyncFuture {
1056                is_method,
1057                name: format!("{}_future", codegen_name.trim_end_matches("_async")),
1058                success_parameters,
1059                error_parameters,
1060                assertion: match SafetyAssertionMode::of(env, is_method, parameters) {
1061                    SafetyAssertionMode::None => SafetyAssertionMode::None,
1062                    // "_future" functions calls the "async" one which has the init check, so no
1063                    // need to do it twice.
1064                    _ => SafetyAssertionMode::Skip,
1065                },
1066            });
1067        }
1068        true
1069    } else {
1070        false
1071    }
1072}
1073
1074fn analyze_callback(
1075    func_name: &str,
1076    type_tid: library::TypeId,
1077    env: &Env,
1078    par: &CParameter,
1079    callback_info: &Option<CallbackInfo>,
1080    commented: &mut bool,
1081    imports: &mut Imports,
1082    c_parameters: &[(&CParameter, usize)],
1083    rust_type: &Type,
1084    callback_parameters_config: Option<&config::functions::CallbackParameters>,
1085) -> Option<(Trampoline, Option<usize>)> {
1086    let mut imports_to_add = Vec::new();
1087
1088    if let Type::Function(func) = rust_type {
1089        if par.c_type != "GDestroyNotify" {
1090            if let Some(user_data) = par.user_data_index {
1091                if user_data >= c_parameters.len() {
1092                    warn_main!(
1093                        type_tid,
1094                        "function `{}` has an invalid user data index of {} when there are {} parameters",
1095                        func_name,
1096                        user_data,
1097                        c_parameters.len()
1098                    );
1099                    return None;
1100                } else if !is_gpointer(&c_parameters[user_data].0.c_type) {
1101                    *commented = true;
1102                    warn_main!(
1103                        type_tid,
1104                        "function `{}`'s callback `{}` has invalid user data",
1105                        func_name,
1106                        par.name
1107                    );
1108                    return None;
1109                }
1110            } else {
1111                *commented = true;
1112                warn_main!(
1113                    type_tid,
1114                    "function `{}`'s callback `{}` without associated user data",
1115                    func_name,
1116                    par.name
1117                );
1118                return None;
1119            }
1120            if let Some(destroy_index) = par.destroy_index {
1121                if destroy_index >= c_parameters.len() {
1122                    warn_main!(
1123                        type_tid,
1124                        "function `{}` has an invalid destroy index of {} when there are {} \
1125                         parameters",
1126                        func_name,
1127                        destroy_index,
1128                        c_parameters.len()
1129                    );
1130                    return None;
1131                }
1132                if c_parameters[destroy_index].0.c_type != "GDestroyNotify" {
1133                    *commented = true;
1134                    warn_main!(
1135                        type_tid,
1136                        "function `{}`'s callback `{}` has invalid destroy callback",
1137                        func_name,
1138                        par.name
1139                    );
1140                    return None;
1141                }
1142            }
1143        }
1144
1145        // If we don't have a "user data" parameter, we can't get the closure so there's
1146        // nothing we can do...
1147        if par.c_type != "GDestroyNotify"
1148            && (func.parameters.is_empty() || !func.parameters.iter().any(|c| c.closure.is_some()))
1149        {
1150            *commented = true;
1151            warn_main!(
1152                type_tid,
1153                "Closure type `{}` doesn't provide user data for function {}",
1154                par.c_type,
1155                func_name,
1156            );
1157            return None;
1158        }
1159
1160        let parameters = crate::analysis::trampoline_parameters::analyze(
1161            env,
1162            &func.parameters,
1163            par.typ,
1164            &[],
1165            callback_parameters_config,
1166        );
1167        if par.c_type != "GDestroyNotify" && !*commented {
1168            *commented |= func.parameters.iter().any(|p| {
1169                if p.closure.is_none() {
1170                    crate::analysis::trampolines::type_error(env, p).is_some()
1171                } else {
1172                    false
1173                }
1174            });
1175        }
1176        for p in &parameters.rust_parameters {
1177            if let Ok(rust_type) = RustType::builder(env, p.typ)
1178                .direction(p.direction)
1179                .nullable(p.nullable)
1180                .try_from_glib(&p.try_from_glib)
1181                .try_build()
1182            {
1183                imports_to_add.extend(rust_type.into_used_types());
1184            }
1185        }
1186        if let Ok(rust_type) = RustType::builder(env, func.ret.typ)
1187            .direction(ParameterDirection::Return)
1188            .try_build()
1189            && !rust_type.as_str().ends_with("GString")
1190            && !rust_type.as_str().ends_with("GAsyncResult")
1191        {
1192            imports_to_add.extend(rust_type.into_used_types());
1193        }
1194        let user_data_index = par.user_data_index.unwrap_or(0);
1195        if par.c_type != "GDestroyNotify" && c_parameters.len() <= user_data_index {
1196            warn_main!(
1197                type_tid,
1198                "`{}`: Invalid user data index of `{}`",
1199                func.name,
1200                user_data_index
1201            );
1202            *commented = true;
1203            None
1204        } else if match par.destroy_index {
1205            Some(destroy_index) => c_parameters.len() <= destroy_index,
1206            None => false,
1207        } {
1208            warn_main!(
1209                type_tid,
1210                "`{}`: Invalid destroy index of `{}`",
1211                func.name,
1212                par.destroy_index.unwrap()
1213            );
1214            *commented = true;
1215            None
1216        } else {
1217            if !*commented {
1218                for import in imports_to_add {
1219                    imports.add_used_type(&import);
1220                }
1221            }
1222            Some((
1223                Trampoline {
1224                    name: par.name.to_string(),
1225                    parameters,
1226                    ret: func.ret.clone(),
1227                    bound_name: match callback_info {
1228                        Some(x) => x.bound_name.to_string(),
1229                        None => match RustType::builder(env, par.typ)
1230                            .direction(par.direction)
1231                            .nullable(par.nullable)
1232                            .scope(par.scope)
1233                            .try_build()
1234                        {
1235                            Ok(rust_type) => rust_type.into_string(),
1236                            Err(_) => {
1237                                warn_main!(type_tid, "`{}`: unknown type", func.name);
1238                                return None;
1239                            }
1240                        },
1241                    },
1242                    bounds: Bounds::default(),
1243                    version: None,
1244                    inhibit: false,
1245                    concurrency: library::Concurrency::None,
1246                    is_notify: false,
1247                    scope: par.scope,
1248                    // If destroy callback, id doesn't matter.
1249                    user_data_index: if par.c_type != "GDestroyNotify" {
1250                        c_parameters[user_data_index].1
1251                    } else {
1252                        0
1253                    },
1254                    destroy_index: 0,
1255                    nullable: par.nullable,
1256                    type_name: env.library.type_(type_tid).get_name(),
1257                },
1258                par.destroy_index
1259                    .map(|destroy_index| c_parameters[destroy_index].1),
1260            ))
1261        }
1262    } else {
1263        None
1264    }
1265}
1266
1267pub fn find_function<'a>(env: &'a Env, c_identifier: &str) -> Option<&'a Function> {
1268    let find = |functions: &'a [Function]| -> Option<&'a Function> {
1269        for function in functions {
1270            if let Some(ref func_c_identifier) = function.c_identifier
1271                && func_c_identifier == c_identifier
1272            {
1273                return Some(function);
1274            }
1275        }
1276        None
1277    };
1278
1279    if let Some(index) = env.library.find_namespace(&env.config.library_name) {
1280        let namespace = env.library.namespace(index);
1281        if let Some(f) = find(&namespace.functions) {
1282            return Some(f);
1283        }
1284        for typ in &namespace.types {
1285            if let Some(Type::Class(class)) = typ {
1286                if let Some(f) = find(&class.functions) {
1287                    return Some(f);
1288                }
1289            } else if let Some(Type::Interface(interface)) = typ
1290                && let Some(f) = find(&interface.functions)
1291            {
1292                return Some(f);
1293            }
1294        }
1295    }
1296    None
1297}
1298
1299/// Given async function name tries to guess the name of finish function.
1300pub fn finish_function_name(mut func_name: &str) -> String {
1301    if func_name.ends_with("_async") {
1302        let len = func_name.len() - "_async".len();
1303        func_name = &func_name[0..len];
1304    }
1305    format!("{}_finish", &func_name)
1306}
1307
1308pub fn find_index_to_ignore<'a>(
1309    parameters: impl IntoIterator<Item = &'a library::Parameter>,
1310    ret: Option<&'a library::Parameter>,
1311) -> Option<usize> {
1312    parameters
1313        .into_iter()
1314        .chain(ret)
1315        .find(|param| param.array_length.is_some())
1316        .and_then(|param| param.array_length.map(|length| length as usize))
1317}
1318
1319#[cfg(test)]
1320mod tests {
1321    use super::*;
1322
1323    #[test]
1324    fn test_finish_function_name() {
1325        assert_eq!(
1326            "g_file_copy_finish",
1327            &finish_function_name("g_file_copy_async")
1328        );
1329        assert_eq!("g_bus_get_finish", &finish_function_name("g_bus_get"));
1330    }
1331}