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