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                    if par.c_type != "GDestroyNotify" {
389                        to_glib_extras.insert(pos, to_glib_extra);
390                    }
391                }
392                callback_info
393            } else {
394                None
395            };
396
397            if rust_type.is_function() {
398                if par.c_type != "GDestroyNotify" {
399                    let callback_parameters_config = configured_functions.iter().find_map(|f| {
400                        f.parameters
401                            .iter()
402                            .find(|p| p.ident.is_match(&par.name))
403                            .map(|p| &p.callback_parameters)
404                    });
405                    if let Some((mut callback, destroy_index)) = analyze_callback(
406                        func_name,
407                        type_tid,
408                        env,
409                        par,
410                        &callback_info,
411                        commented,
412                        imports,
413                        &c_parameters,
414                        rust_type,
415                        callback_parameters_config,
416                    ) {
417                        if let Some(destroy_index) = destroy_index {
418                            let user_data = cross_user_data_check
419                                .entry(destroy_index)
420                                .or_insert_with(|| callback.user_data_index);
421                            if *user_data != callback.user_data_index {
422                                warn_main!(
423                                    type_tid,
424                                    "`{}`: Different destructors cannot share the same user data",
425                                    func_name
426                                );
427                                *commented = true;
428                            }
429                            callback.destroy_index = destroy_index;
430                        } else {
431                            user_data_indexes.insert(callback.user_data_index);
432                            to_remove.push(callback.user_data_index);
433                        }
434                        callbacks.push(callback);
435                        to_replace.push((pos, par.typ));
436                        continue;
437                    }
438                } else if let Some((mut callback, _)) = analyze_callback(
439                    func_name,
440                    type_tid,
441                    env,
442                    par,
443                    &callback_info,
444                    commented,
445                    imports,
446                    &c_parameters,
447                    rust_type,
448                    None,
449                ) {
450                    // We just assume that for API "cleanness", the destroy callback will always
451                    // be |-> *after* <-| the initial callback.
452                    if let Some(user_data_index) = cross_user_data_check.get(&pos) {
453                        callback.user_data_index = *user_data_index;
454                        callback.destroy_index = pos;
455                    } else {
456                        warn_main!(
457                            type_tid,
458                            "`{}`: no user data point to the destroy callback",
459                            func_name,
460                        );
461                        *commented = true;
462                    }
463                    // We check if the user trampoline is there. If so, we change the destroy
464                    // nullable value if needed.
465                    if !find_callback_bound_to_destructor(callbacks, &mut callback, pos) {
466                        // Maybe the linked callback is after so we store it just in case...
467                        destructors_to_update.push((pos, destroys.len()));
468                    }
469                    destroys.push(callback);
470                    to_remove.push(pos);
471                    continue;
472                }
473            }
474            if !*commented {
475                *commented |= RustType::builder(env, par.typ)
476                    .direction(par.direction)
477                    .scope(par.scope)
478                    .try_from_glib(&par.try_from_glib)
479                    .try_build_param()
480                    .is_err();
481            }
482        }
483        for (destroy_index, pos_in_destroys) in destructors_to_update {
484            if !find_callback_bound_to_destructor(
485                callbacks,
486                &mut destroys[pos_in_destroys],
487                destroy_index,
488            ) {
489                warn_main!(
490                    type_tid,
491                    "`{}`: destructor without linked callback",
492                    func_name
493                );
494            }
495        }
496    }
497
498    // Check for cross "user data".
499    if cross_user_data_check
500        .values()
501        .collect::<Vec<_>>()
502        .windows(2)
503        .any(|a| a[0] == a[1])
504    {
505        *commented = true;
506        warn_main!(
507            type_tid,
508            "`{}`: Different user data share the same destructors",
509            func.name
510        );
511    }
512
513    if !destroys.is_empty() || !callbacks.is_empty() {
514        for (pos, typ) in to_replace {
515            let ty = env.library.type_(typ);
516            params[pos].typ = typ;
517            params[pos].c_type = ty.get_glib_name().unwrap().to_owned();
518        }
519        let mut s = to_remove
520            .iter()
521            .chain(cross_user_data_check.values())
522            .collect::<HashSet<_>>() // To prevent duplicates.
523            .into_iter()
524            .collect::<Vec<_>>();
525        s.sort(); // We need to sort the array, otherwise the indexes won't be working
526                  // anymore.
527        for pos in s.iter().rev() {
528            params.remove(**pos);
529        }
530        *parameters = function_parameters::analyze(
531            env,
532            params,
533            configured_functions,
534            disable_length_detect,
535            false,
536            in_trait,
537        );
538    } else {
539        warn_main!(
540            type_tid,
541            "`{}`: this is supposed to be a callback function but no callback was found...",
542            func.name
543        );
544        *commented = true;
545    }
546}
547
548fn analyze_function(
549    env: &Env,
550    obj: &config::gobjects::GObject,
551    func_name: &str,
552    name: String,
553    status: GStatus,
554    func: &library::Function,
555    type_tid: Option<library::TypeId>,
556    in_trait: bool,
557    is_boxed: bool,
558    configured_functions: &[&config::functions::Function],
559    imports: &mut Imports,
560) -> Info {
561    let ns_id = type_tid.map_or(MAIN_NAMESPACE, |t| t.ns_id);
562    let type_tid = type_tid.unwrap_or_default();
563    let r#async = func.finish_func.is_some()
564        || func.parameters.iter().any(|parameter| {
565            parameter.scope == ParameterScope::Async && parameter.c_type == "GAsyncReadyCallback"
566        });
567    let has_callback_parameter = !r#async
568        && func
569            .parameters
570            .iter()
571            .any(|par| env.library.type_(par.typ).is_function());
572    let concurrency = match env.library.type_(type_tid) {
573        library::Type::Class(_) | library::Type::Interface(_) | library::Type::Record(_) => {
574            obj.concurrency
575        }
576        _ => library::Concurrency::SendSync,
577    };
578
579    let mut commented = false;
580    let mut bounds: Bounds = Default::default();
581    let mut to_glib_extras = HashMap::<usize, String>::new();
582    let mut used_types: Vec<String> = Vec::with_capacity(4);
583    let mut trampoline = None;
584    let mut callbacks = Vec::new();
585    let mut destroys = Vec::new();
586    let mut async_future = None;
587
588    if !r#async
589        && !has_callback_parameter
590        && func
591            .parameters
592            .iter()
593            .any(|par| par.c_type == "GDestroyNotify")
594    {
595        // In here, We have a DestroyNotify callback but no other callback is provided.
596        // A good example of this situation is this function:
597        // https://developer.gnome.org/gio/stable/GTlsPassword.html#g-tls-password-set-value-full
598        warn_main!(
599            type_tid,
600            "Function \"{}\" with destroy callback without callbacks",
601            func.name
602        );
603        commented = true;
604    }
605
606    let mut new_name = configured_functions.iter().find_map(|f| f.rename.clone());
607    let is_constructor = configured_functions.iter().find_map(|f| f.is_constructor);
608
609    let bypass_auto_rename = configured_functions.iter().any(|f| f.bypass_auto_rename);
610    let is_constructor = is_constructor.unwrap_or(false);
611    if !bypass_auto_rename && new_name.is_none() {
612        if func.kind == library::FunctionKind::Constructor || is_constructor {
613            if func.kind == library::FunctionKind::Constructor && is_constructor {
614                warn_main!(
615                    type_tid,
616                    "`{}`: config forces 'constructor' on an already gir-annotated 'constructor'",
617                    func_name
618                );
619            }
620
621            if name.starts_with("new_from")
622                || name.starts_with("new_with")
623                || name.starts_with("new_for")
624            {
625                new_name = Some(name[4..].to_string());
626            }
627        } else {
628            let nb_in_params = func
629                .parameters
630                .iter()
631                .filter(|param| library::ParameterDirection::In == param.direction)
632                .fold(0, |acc, _| acc + 1);
633            let is_bool_getter = (func.parameters.len() == nb_in_params)
634                && (func.ret.typ == library::TypeId::tid_bool()
635                    || func.ret.typ == library::TypeId::tid_c_bool());
636            new_name = getter_rules::try_rename_would_be_getter(&name, is_bool_getter)
637                .ok()
638                .map(getter_rules::NewName::unwrap);
639        }
640    }
641
642    let version = configured_functions
643        .iter()
644        .filter_map(|f| f.version)
645        .min()
646        .or(func.version);
647
648    let version = env.config.filter_version(version);
649    let deprecated_version = func.deprecated_version;
650    let visibility = configured_functions
651        .iter()
652        .find_map(|f| f.visibility)
653        .unwrap_or_default();
654    let cfg_condition = configured_functions
655        .iter()
656        .find_map(|f| f.cfg_condition.clone());
657    let doc_hidden = configured_functions.iter().any(|f| f.doc_hidden);
658    let doc_trait_name = configured_functions
659        .iter()
660        .find_map(|f| f.doc_trait_name.clone());
661    let doc_struct_name = configured_functions
662        .iter()
663        .find_map(|f| f.doc_struct_name.clone());
664    let doc_ignore_parameters = configured_functions
665        .iter()
666        .find(|f| !f.doc_ignore_parameters.is_empty())
667        .map(|f| f.doc_ignore_parameters.clone())
668        .unwrap_or_default();
669    let disable_length_detect = configured_functions.iter().any(|f| f.disable_length_detect);
670    let no_future = configured_functions.iter().any(|f| f.no_future);
671    let unsafe_ = configured_functions.iter().any(|f| f.unsafe_);
672    let assertion = configured_functions.iter().find_map(|f| f.assertion);
673
674    let imports = &mut imports.with_defaults(version, &cfg_condition);
675
676    let ret = return_value::analyze(
677        env,
678        obj,
679        func,
680        type_tid,
681        configured_functions,
682        &mut used_types,
683        imports,
684    );
685    commented |= ret.commented;
686
687    let mut params = func.parameters.clone();
688    let mut parameters = function_parameters::analyze(
689        env,
690        &params,
691        configured_functions,
692        disable_length_detect,
693        r#async,
694        in_trait,
695    );
696    parameters.analyze_return(env, &ret.parameter);
697
698    if let Some(ref f) = ret.parameter {
699        if let Type::Function(_) = env.library.type_(f.lib_par.typ) {
700            if env.config.work_mode.is_normal() {
701                warn!("Function \"{}\" returns callback", func.name);
702                commented = true;
703            }
704        }
705    }
706
707    fixup_special_functions(
708        env,
709        name.as_str(),
710        type_tid,
711        is_boxed,
712        in_trait,
713        &mut parameters,
714    );
715
716    // Key: destroy callback index
717    // Value: associated user data index
718    let mut cross_user_data_check: HashMap<usize, usize> = HashMap::new();
719    let mut user_data_indexes: HashSet<usize> = HashSet::new();
720
721    if status.need_generate() {
722        if !has_callback_parameter {
723            let mut to_remove = Vec::new();
724            let mut correction_instance = 0;
725            for par in parameters.c_parameters.iter() {
726                if par.scope.is_none() {
727                    continue;
728                }
729                if let Some(index) = par.user_data_index {
730                    to_remove.push(index);
731                }
732                if let Some(index) = par.destroy_index {
733                    to_remove.push(index);
734                }
735            }
736            for (pos, par) in parameters.c_parameters.iter().enumerate() {
737                if par.instance_parameter {
738                    correction_instance = 1;
739                }
740
741                if r#async
742                    && pos >= correction_instance
743                    && to_remove.contains(&(pos - correction_instance))
744                {
745                    continue;
746                }
747                assert!(
748                    !par.instance_parameter || pos == 0,
749                    "Wrong instance parameter in {}",
750                    func.c_identifier.as_ref().unwrap()
751                );
752                if let Ok(rust_type) = RustType::builder(env, par.typ)
753                    .direction(par.direction)
754                    .try_from_glib(&par.try_from_glib)
755                    .try_build()
756                {
757                    if !rust_type.as_str().ends_with("GString") || par.c_type == "gchar***" {
758                        used_types.extend(rust_type.into_used_types());
759                    }
760                }
761                let (to_glib_extra, callback_info) = bounds.add_for_parameter(
762                    env,
763                    func,
764                    par,
765                    r#async,
766                    library::Concurrency::None,
767                    configured_functions,
768                );
769                if let Some(to_glib_extra) = to_glib_extra {
770                    to_glib_extras.insert(pos, to_glib_extra);
771                }
772
773                analyze_async(
774                    env,
775                    func,
776                    type_tid,
777                    new_name.as_ref().unwrap_or(&name),
778                    callback_info,
779                    &mut commented,
780                    &mut trampoline,
781                    no_future,
782                    &mut async_future,
783                    configured_functions,
784                    &parameters,
785                );
786                let type_error = !(r#async
787                    && *env.library.type_(par.typ) == Type::Basic(library::Basic::Pointer))
788                    && RustType::builder(env, par.typ)
789                        .direction(par.direction)
790                        .scope(par.scope)
791                        .try_from_glib(&par.try_from_glib)
792                        .try_build_param()
793                        .is_err();
794                if type_error {
795                    commented = true;
796                }
797            }
798            if r#async && trampoline.is_none() {
799                commented = true;
800            }
801        } else {
802            analyze_callbacks(
803                env,
804                func,
805                &mut cross_user_data_check,
806                &mut user_data_indexes,
807                &mut parameters,
808                &mut used_types,
809                &mut bounds,
810                &mut to_glib_extras,
811                imports,
812                &mut destroys,
813                &mut callbacks,
814                &mut params,
815                configured_functions,
816                disable_length_detect,
817                in_trait,
818                &mut commented,
819                concurrency,
820                type_tid,
821            );
822        }
823    }
824
825    for par in &parameters.rust_parameters {
826        // Disallow basic arrays without length
827        let is_len_for_par = |t: &Transformation| {
828            if let TransformationType::Length { ref array_name, .. } = t.transformation_type {
829                array_name == &par.name
830            } else {
831                false
832            }
833        };
834        if is_carray_with_direct_elements(env, par.typ)
835            && !parameters.transformations.iter().any(is_len_for_par)
836        {
837            commented = true;
838        }
839    }
840
841    let (outs, unsupported_outs) = out_parameters::analyze(
842        env,
843        func,
844        &parameters.c_parameters,
845        &ret,
846        configured_functions,
847    );
848    if unsupported_outs {
849        warn_main!(
850            type_tid,
851            "Function {} has unsupported outs",
852            func.c_identifier.as_ref().unwrap_or(&func.name)
853        );
854        commented = true;
855    }
856
857    if r#async && status.need_generate() && !commented {
858        imports.add("std::boxed::Box as Box_");
859        imports.add("std::pin::Pin");
860
861        if let Some(ref trampoline) = trampoline {
862            for out in &trampoline.output_params {
863                if let Ok(rust_type) = RustType::builder(env, out.lib_par.typ)
864                    .direction(ParameterDirection::Out)
865                    .try_build()
866                {
867                    used_types.extend(rust_type.into_used_types());
868                }
869            }
870            if let Some(ref out) = trampoline.ffi_ret {
871                if let Ok(rust_type) = RustType::builder(env, out.lib_par.typ)
872                    .direction(ParameterDirection::Return)
873                    .try_build()
874                {
875                    used_types.extend(rust_type.into_used_types());
876                }
877            }
878        }
879    }
880
881    if status.need_generate() && !commented {
882        if (!destroys.is_empty() || !callbacks.is_empty())
883            && callbacks.iter().any(|c| !c.scope.is_call())
884        {
885            imports.add("std::boxed::Box as Box_");
886        }
887
888        for transformation in &mut parameters.transformations {
889            if let Some(to_glib_extra) = to_glib_extras.get(&transformation.ind_c) {
890                transformation
891                    .transformation_type
892                    .set_to_glib_extra(to_glib_extra);
893            }
894        }
895
896        imports.add("crate::ffi");
897
898        imports.add_used_types(&used_types);
899        if ret.base_tid.is_some() {
900            imports.add("glib::prelude::*");
901        }
902
903        if func.name.parse::<special_functions::Type>().is_err()
904            || parameters.c_parameters.iter().any(|p| p.move_)
905        {
906            imports.add("glib::translate::*");
907        }
908        bounds.update_imports(imports);
909    }
910
911    let is_method = func.kind == library::FunctionKind::Method;
912    let assertion =
913        assertion.unwrap_or_else(|| SafetyAssertionMode::of(env, is_method, &parameters));
914
915    let generate_doc = configured_functions.iter().all(|f| f.generate_doc);
916
917    Info {
918        name,
919        func_name: func_name.to_string(),
920        new_name,
921        glib_name: func.c_identifier.as_ref().unwrap().clone(),
922        status,
923        kind: func.kind,
924        visibility,
925        type_name: RustType::try_new(env, type_tid),
926        parameters,
927        ret,
928        bounds,
929        outs,
930        version,
931        deprecated_version,
932        not_version: None,
933        cfg_condition,
934        assertion,
935        doc_hidden,
936        doc_trait_name,
937        doc_struct_name,
938        doc_ignore_parameters,
939        r#async,
940        unsafe_,
941        trampoline,
942        async_future,
943        callbacks,
944        destroys,
945        remove_params: cross_user_data_check.values().copied().collect::<Vec<_>>(),
946        commented,
947        hidden: false,
948        ns_id,
949        generate_doc,
950        get_property: func.get_property.clone(),
951        set_property: func.set_property.clone(),
952    }
953}
954
955pub fn is_carray_with_direct_elements(env: &Env, typ: library::TypeId) -> bool {
956    match *env.library.type_(typ) {
957        Type::CArray(inner_tid) => {
958            use super::conversion_type::ConversionType;
959            matches!(env.library.type_(inner_tid), Type::Basic(..) if ConversionType::of(env, inner_tid) == ConversionType::Direct)
960        }
961        _ => false,
962    }
963}
964
965fn analyze_async(
966    env: &Env,
967    func: &library::Function,
968    type_tid: library::TypeId,
969    codegen_name: &str,
970    callback_info: Option<CallbackInfo>,
971    commented: &mut bool,
972    trampoline: &mut Option<AsyncTrampoline>,
973    no_future: bool,
974    async_future: &mut Option<AsyncFuture>,
975    configured_functions: &[&config::functions::Function],
976    parameters: &function_parameters::Parameters,
977) -> bool {
978    if let Some(CallbackInfo {
979        callback_type,
980        success_parameters,
981        error_parameters,
982        bound_name,
983    }) = callback_info
984    {
985        // Checks for /*Ignored*/ or other error comments
986        *commented |= callback_type.contains("/*");
987        let func_name = func.c_identifier.as_ref().unwrap();
988        let finish_func_name = if let Some(finish_func_name) = &func.finish_func {
989            finish_func_name.to_string()
990        } else {
991            finish_function_name(func_name)
992        };
993        let mut output_params = vec![];
994        let mut ffi_ret = None;
995        if let Some(function) = find_function(env, &finish_func_name) {
996            if use_function_return_for_result(
997                env,
998                function.ret.typ,
999                &func.name,
1000                configured_functions,
1001            ) {
1002                ffi_ret = Some(analysis::Parameter::from_return_value(
1003                    env,
1004                    &function.ret,
1005                    configured_functions,
1006                ));
1007            }
1008
1009            for param in &function.parameters {
1010                let mut lib_par = param.clone();
1011                if nameutil::needs_mangling(&param.name) {
1012                    lib_par.name = nameutil::mangle_keywords(&*param.name).into_owned();
1013                }
1014                let configured_parameters = configured_functions.matched_parameters(&lib_par.name);
1015                output_params.push(analysis::Parameter::from_parameter(
1016                    env,
1017                    &lib_par,
1018                    &configured_parameters,
1019                ));
1020            }
1021        }
1022        if trampoline.is_some() || async_future.is_some() {
1023            warn_main!(
1024                type_tid,
1025                "{}: Cannot handle callbacks and async parameters at the same time for the \
1026                 moment",
1027                func.name
1028            );
1029            *commented = true;
1030            return false;
1031        }
1032        if !*commented && success_parameters.is_empty() {
1033            if success_parameters.is_empty() {
1034                warn_main!(
1035                    type_tid,
1036                    "{}: missing success parameters for async future",
1037                    func.name
1038                );
1039            }
1040            *commented = true;
1041            return false;
1042        }
1043        let is_method = func.kind == FunctionKind::Method;
1044
1045        *trampoline = Some(AsyncTrampoline {
1046            is_method,
1047            has_error_parameter: error_parameters.is_some(),
1048            name: format!("{codegen_name}_trampoline"),
1049            finish_func_name: format!("{}::{}", env.main_sys_crate_name(), finish_func_name),
1050            callback_type,
1051            bound_name,
1052            output_params,
1053            ffi_ret,
1054        });
1055
1056        if !no_future {
1057            *async_future = Some(AsyncFuture {
1058                is_method,
1059                name: format!("{}_future", codegen_name.trim_end_matches("_async")),
1060                success_parameters,
1061                error_parameters,
1062                assertion: match SafetyAssertionMode::of(env, is_method, parameters) {
1063                    SafetyAssertionMode::None => SafetyAssertionMode::None,
1064                    // "_future" functions calls the "async" one which has the init check, so no
1065                    // need to do it twice.
1066                    _ => SafetyAssertionMode::Skip,
1067                },
1068            });
1069        }
1070        true
1071    } else {
1072        false
1073    }
1074}
1075
1076fn analyze_callback(
1077    func_name: &str,
1078    type_tid: library::TypeId,
1079    env: &Env,
1080    par: &CParameter,
1081    callback_info: &Option<CallbackInfo>,
1082    commented: &mut bool,
1083    imports: &mut Imports,
1084    c_parameters: &[(&CParameter, usize)],
1085    rust_type: &Type,
1086    callback_parameters_config: Option<&config::functions::CallbackParameters>,
1087) -> Option<(Trampoline, Option<usize>)> {
1088    let mut imports_to_add = Vec::new();
1089
1090    if let Type::Function(func) = rust_type {
1091        if par.c_type != "GDestroyNotify" {
1092            if let Some(user_data) = par.user_data_index {
1093                if user_data >= c_parameters.len() {
1094                    warn_main!(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                    return None;
1100                } else if !is_gpointer(&c_parameters[user_data].0.c_type) {
1101                    *commented = true;
1102                    warn_main!(
1103                        type_tid,
1104                        "function `{}`'s callback `{}` has invalid user data",
1105                        func_name,
1106                        par.name
1107                    );
1108                    return None;
1109                }
1110            } else {
1111                *commented = true;
1112                warn_main!(
1113                    type_tid,
1114                    "function `{}`'s callback `{}` without associated user data",
1115                    func_name,
1116                    par.name
1117                );
1118                return None;
1119            }
1120            if let Some(destroy_index) = par.destroy_index {
1121                if destroy_index >= c_parameters.len() {
1122                    warn_main!(
1123                        type_tid,
1124                        "function `{}` has an invalid destroy index of {} when there are {} \
1125                         parameters",
1126                        func_name,
1127                        destroy_index,
1128                        c_parameters.len()
1129                    );
1130                    return None;
1131                }
1132                if c_parameters[destroy_index].0.c_type != "GDestroyNotify" {
1133                    *commented = true;
1134                    warn_main!(
1135                        type_tid,
1136                        "function `{}`'s callback `{}` has invalid destroy callback",
1137                        func_name,
1138                        par.name
1139                    );
1140                    return None;
1141                }
1142            }
1143        }
1144
1145        // If we don't have a "user data" parameter, we can't get the closure so there's
1146        // nothing we can do...
1147        if par.c_type != "GDestroyNotify"
1148            && (func.parameters.is_empty() || !func.parameters.iter().any(|c| c.closure.is_some()))
1149        {
1150            *commented = true;
1151            warn_main!(
1152                type_tid,
1153                "Closure type `{}` doesn't provide user data for function {}",
1154                par.c_type,
1155                func_name,
1156            );
1157            return None;
1158        }
1159
1160        let parameters = crate::analysis::trampoline_parameters::analyze(
1161            env,
1162            &func.parameters,
1163            par.typ,
1164            &[],
1165            callback_parameters_config,
1166        );
1167        if par.c_type != "GDestroyNotify" && !*commented {
1168            *commented |= func.parameters.iter().any(|p| {
1169                if p.closure.is_none() {
1170                    crate::analysis::trampolines::type_error(env, p).is_some()
1171                } else {
1172                    false
1173                }
1174            });
1175        }
1176        for p in &parameters.rust_parameters {
1177            if let Ok(rust_type) = RustType::builder(env, p.typ)
1178                .direction(p.direction)
1179                .nullable(p.nullable)
1180                .try_from_glib(&p.try_from_glib)
1181                .try_build()
1182            {
1183                imports_to_add.extend(rust_type.into_used_types());
1184            }
1185        }
1186        if let Ok(rust_type) = RustType::builder(env, func.ret.typ)
1187            .direction(ParameterDirection::Return)
1188            .try_build()
1189        {
1190            if !rust_type.as_str().ends_with("GString")
1191                && !rust_type.as_str().ends_with("GAsyncResult")
1192            {
1193                imports_to_add.extend(rust_type.into_used_types());
1194            }
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        for function in functions {
1272            if let Some(ref func_c_identifier) = function.c_identifier {
1273                if func_c_identifier == c_identifier {
1274                    return Some(function);
1275                }
1276            }
1277        }
1278        None
1279    };
1280
1281    if let Some(index) = env.library.find_namespace(&env.config.library_name) {
1282        let namespace = env.library.namespace(index);
1283        if let Some(f) = find(&namespace.functions) {
1284            return Some(f);
1285        }
1286        for typ in &namespace.types {
1287            if let Some(Type::Class(class)) = typ {
1288                if let Some(f) = find(&class.functions) {
1289                    return Some(f);
1290                }
1291            } else if let Some(Type::Interface(interface)) = typ {
1292                if let Some(f) = find(&interface.functions) {
1293                    return Some(f);
1294                }
1295            }
1296        }
1297    }
1298    None
1299}
1300
1301/// Given async function name tries to guess the name of finish function.
1302pub fn finish_function_name(mut func_name: &str) -> String {
1303    if func_name.ends_with("_async") {
1304        let len = func_name.len() - "_async".len();
1305        func_name = &func_name[0..len];
1306    }
1307    format!("{}_finish", &func_name)
1308}
1309
1310pub fn find_index_to_ignore<'a>(
1311    parameters: impl IntoIterator<Item = &'a library::Parameter>,
1312    ret: Option<&'a library::Parameter>,
1313) -> Option<usize> {
1314    parameters
1315        .into_iter()
1316        .chain(ret)
1317        .find(|param| param.array_length.is_some())
1318        .and_then(|param| param.array_length.map(|length| length as usize))
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323    use super::*;
1324
1325    #[test]
1326    fn test_finish_function_name() {
1327        assert_eq!(
1328            "g_file_copy_finish",
1329            &finish_function_name("g_file_copy_async")
1330        );
1331        assert_eq!("g_bus_get_finish", &finish_function_name("g_bus_get"));
1332    }
1333}