Skip to main content

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