libgir/codegen/
function_body_chunk.rs

1use std::collections::{hash_map::Entry, BTreeMap, HashMap};
2
3use crate::{
4    analysis::{
5        self,
6        conversion_type::ConversionType,
7        function_parameters::{
8            CParameter as AnalysisCParameter, Transformation, TransformationType,
9        },
10        functions::{find_index_to_ignore, AsyncTrampoline},
11        out_parameters::{Mode, ThrowFunctionReturnStrategy},
12        return_value,
13        rust_type::RustType,
14        safety_assertion_mode::SafetyAssertionMode,
15        trampoline_parameters,
16        trampolines::Trampoline,
17    },
18    chunk::{parameter_ffi_call_out, Chunk, Param, TupleMode},
19    env::Env,
20    library::{self, ParameterDirection, TypeId},
21    nameutil::{is_gstring, use_gio_type, use_glib_if_needed, use_glib_type},
22    traits::*,
23};
24
25#[derive(Clone, Debug)]
26enum Parameter {
27    // Used to separate in and out parameters in `add_in_array_lengths`
28    // and `generate_func_parameters`
29    In,
30    Out {
31        parameter: parameter_ffi_call_out::Parameter,
32        mem_mode: OutMemMode,
33    },
34}
35use self::Parameter::*;
36
37#[derive(Clone, Debug, Eq, PartialEq)]
38enum OutMemMode {
39    Uninitialized,
40    UninitializedNamed(String),
41    NullPtr,
42    NullMutPtr,
43}
44
45impl OutMemMode {
46    fn is_uninitialized(&self) -> bool {
47        matches!(*self, Self::Uninitialized)
48    }
49}
50
51#[derive(Clone, Default)]
52struct ReturnValue {
53    pub ret: return_value::Info,
54}
55
56#[derive(Default)]
57pub struct Builder {
58    async_trampoline: Option<AsyncTrampoline>,
59    callbacks: Vec<Trampoline>,
60    destroys: Vec<Trampoline>,
61    glib_name: String,
62    parameters: Vec<Parameter>,
63    transformations: Vec<Transformation>,
64    ret: ReturnValue,
65    outs_as_return: bool,
66    in_unsafe: bool,
67    outs_mode: Mode,
68    assertion: SafetyAssertionMode,
69}
70
71// Key: user data index
72// Value: (global position used as id, type, callbacks)
73type FuncParameters<'a> = BTreeMap<usize, FuncParameter<'a>>;
74
75struct FuncParameter<'a> {
76    pos: usize,
77    full_type: Option<(String, String)>,
78    callbacks: Vec<&'a Trampoline>,
79}
80
81impl Builder {
82    pub fn new() -> Self {
83        Default::default()
84    }
85    pub fn async_trampoline(&mut self, trampoline: &AsyncTrampoline) -> &mut Self {
86        self.async_trampoline = Some(trampoline.clone());
87        self
88    }
89    pub fn callback(&mut self, trampoline: &Trampoline) -> &mut Self {
90        self.callbacks.push(trampoline.clone());
91        self
92    }
93    pub fn destroy(&mut self, trampoline: &Trampoline) -> &mut Self {
94        self.destroys.push(trampoline.clone());
95        self
96    }
97    pub fn glib_name(&mut self, name: &str) -> &mut Self {
98        self.glib_name = name.into();
99        self
100    }
101    pub fn assertion(&mut self, assertion: SafetyAssertionMode) -> &mut Self {
102        self.assertion = assertion;
103        self
104    }
105    pub fn ret(&mut self, ret: &return_value::Info) -> &mut Self {
106        self.ret = ReturnValue { ret: ret.clone() };
107        self
108    }
109    pub fn parameter(&mut self) -> &mut Self {
110        self.parameters.push(Parameter::In);
111        self
112    }
113    pub fn out_parameter(&mut self, env: &Env, parameter: &AnalysisCParameter) -> &mut Self {
114        let mem_mode = c_type_mem_mode(env, parameter);
115        self.parameters.push(Parameter::Out {
116            parameter: parameter_ffi_call_out::Parameter::new(
117                parameter,
118                mem_mode.is_uninitialized(),
119            ),
120            mem_mode,
121        });
122        self.outs_as_return = true;
123        self
124    }
125
126    pub fn transformations(&mut self, transformations: &[Transformation]) -> &mut Self {
127        self.transformations = transformations.to_owned();
128        self
129    }
130
131    pub fn outs_mode(&mut self, mode: Mode) -> &mut Self {
132        self.outs_mode = mode;
133        self
134    }
135    pub fn in_unsafe(&mut self, in_unsafe: bool) -> &mut Self {
136        self.in_unsafe = in_unsafe;
137        self
138    }
139    pub fn generate(&self, env: &Env, bounds: &str, bounds_names: &str) -> Chunk {
140        let mut body = Vec::new();
141
142        let mut uninitialized_vars = if self.outs_as_return {
143            self.write_out_variables(&mut body, env)
144        } else {
145            Vec::new()
146        };
147
148        let mut group_by_user_data = FuncParameters::new();
149
150        // We group arguments by callbacks.
151        if !self.callbacks.is_empty() || !self.destroys.is_empty() {
152            for (pos, callback) in self.callbacks.iter().enumerate() {
153                let user_data_index = callback.user_data_index;
154                if group_by_user_data.contains_key(&user_data_index) {
155                    continue;
156                }
157                let calls = self
158                    .callbacks
159                    .iter()
160                    .filter(|c| c.user_data_index == user_data_index)
161                    .collect::<Vec<_>>();
162                group_by_user_data.insert(
163                    user_data_index,
164                    FuncParameter {
165                        pos,
166                        full_type: if calls.len() > 1 {
167                            if calls.iter().all(|c| c.scope.is_call()) {
168                                Some((
169                                    format!(
170                                        "&({})",
171                                        calls
172                                            .iter()
173                                            .map(|c| format!("&{}", c.bound_name))
174                                            .collect::<Vec<_>>()
175                                            .join(", ")
176                                    ),
177                                    format!(
178                                        "&mut ({})",
179                                        calls
180                                            .iter()
181                                            .map(|c| format!("&mut {}", c.bound_name))
182                                            .collect::<Vec<_>>()
183                                            .join(", ")
184                                    ),
185                                ))
186                            } else {
187                                let s = format!(
188                                    "Box_<({})>",
189                                    calls
190                                        .iter()
191                                        .map(|c| c.bound_name.to_string())
192                                        .collect::<Vec<_>>()
193                                        .join(", ")
194                                );
195                                Some((s.clone(), s))
196                            }
197                        } else {
198                            None
199                        },
200                        callbacks: calls,
201                    },
202                );
203            }
204        }
205
206        let call = self.generate_call(&group_by_user_data);
207        let call = self.generate_call_conversion(call, &mut uninitialized_vars);
208        let ret = self.generate_out_return(&mut uninitialized_vars);
209        let (call, ret) = self.apply_outs_mode(call, ret, &mut uninitialized_vars);
210
211        body.push(call);
212        if let Some(chunk) = ret {
213            body.push(chunk);
214        }
215
216        let mut chunks = Vec::new();
217
218        self.add_in_array_lengths(&mut chunks);
219        self.add_assertion(&mut chunks);
220
221        if !self.callbacks.is_empty() || !self.destroys.is_empty() {
222            // Key: user data index
223            // Value: the current pos in the tuple for the given argument.
224            let mut poses = HashMap::with_capacity(group_by_user_data.len());
225            for trampoline in &self.callbacks {
226                *poses
227                    .entry(&trampoline.user_data_index)
228                    .or_insert_with(|| 0) += 1;
229            }
230            let mut poses = poses
231                .into_iter()
232                .filter(|(_, x)| *x > 1)
233                .map(|(x, _)| (x, 0))
234                .collect::<HashMap<_, _>>();
235            for trampoline in &self.callbacks {
236                let user_data_index = trampoline.user_data_index;
237                let pos = poses.entry(&trampoline.user_data_index);
238                self.add_trampoline(
239                    env,
240                    &mut chunks,
241                    trampoline,
242                    &group_by_user_data[&user_data_index].full_type,
243                    match pos {
244                        Entry::Occupied(ref x) => Some(*x.get()),
245                        _ => None,
246                    },
247                    bounds,
248                    bounds_names,
249                    false,
250                );
251                pos.and_modify(|x| {
252                    *x += 1;
253                });
254            }
255            for destroy in &self.destroys {
256                self.add_trampoline(
257                    env,
258                    &mut chunks,
259                    destroy,
260                    &group_by_user_data[&destroy.user_data_index].full_type,
261                    None, // doesn't matter for destroy
262                    bounds,
263                    bounds_names,
264                    true,
265                );
266            }
267            for FuncParameter {
268                pos,
269                full_type,
270                callbacks: calls,
271            } in group_by_user_data.values()
272            {
273                if calls.len() > 1 {
274                    chunks.push(Chunk::Let {
275                        name: format!("super_callback{pos}"),
276                        is_mut: false,
277                        value: Box::new(Chunk::Custom(if poses.is_empty() {
278                            format!(
279                                "Box_::new(Box_::new(({})))",
280                                calls
281                                    .iter()
282                                    .map(|c| format!("{}_data", c.name))
283                                    .collect::<Vec<_>>()
284                                    .join(", ")
285                            )
286                        } else if calls.iter().all(|c| c.scope.is_call()) {
287                            format!(
288                                "&mut ({})",
289                                calls
290                                    .iter()
291                                    .map(|c| format!("{}_data", c.name))
292                                    .collect::<Vec<_>>()
293                                    .join(", ")
294                            )
295                        } else {
296                            format!(
297                                "Box_::new(({}))",
298                                calls
299                                    .iter()
300                                    .map(|c| format!("{}_data", c.name))
301                                    .collect::<Vec<_>>()
302                                    .join(", ")
303                            )
304                        })),
305                        type_: Some(Box::new(Chunk::Custom(
306                            full_type.clone().map(|x| x.0).unwrap(),
307                        ))),
308                    });
309                } else if !calls.is_empty() {
310                    chunks.push(Chunk::Let {
311                        name: format!("super_callback{pos}"),
312                        is_mut: false,
313                        value: Box::new(Chunk::Custom(format!(
314                            "{}{}_data",
315                            if calls[0].scope.is_call() {
316                                "&mut "
317                            } else {
318                                ""
319                            },
320                            calls[0].name
321                        ))),
322                        type_: Some(Box::new(Chunk::Custom(if calls[0].scope.is_call() {
323                            format!("&mut {}", calls[0].bound_name)
324                        } else {
325                            format!("Box_<{}>", calls[0].bound_name)
326                        }))),
327                    });
328                }
329            }
330        } else if let Some(ref trampoline) = self.async_trampoline {
331            self.add_async_trampoline(env, &mut chunks, trampoline);
332        }
333
334        chunks.push(if self.in_unsafe {
335            Chunk::Chunks(body)
336        } else {
337            Chunk::Unsafe(body)
338        });
339        Chunk::BlockHalf(chunks)
340    }
341
342    fn remove_extra_assume_init(
343        &self,
344        array_length_name: &Option<String>,
345        uninitialized_vars: &mut Vec<(String, bool)>,
346    ) {
347        // To prevent to call twice `.assume_init()` on the length variable, we need to
348        // remove them from the `uninitialized_vars` array.
349        if let Some(array_length_name) = array_length_name {
350            uninitialized_vars.retain(|(x, _)| x != array_length_name);
351        }
352    }
353
354    fn generate_initialized_value(
355        &self,
356        name: &str,
357        uninitialized_vars: &[(String, bool)],
358    ) -> Chunk {
359        if let Some(need_from_glib) = self.is_uninitialized_var(name, uninitialized_vars) {
360            Chunk::Custom(format!(
361                "{}{}.assume_init(){}",
362                if need_from_glib { "from_glib(" } else { "" },
363                name,
364                if need_from_glib { ")" } else { "" },
365            ))
366        } else {
367            Chunk::Custom(name.to_string())
368        }
369    }
370
371    fn is_uninitialized_var(
372        &self,
373        name: &str,
374        uninitialized_vars: &[(String, bool)],
375    ) -> Option<bool> {
376        uninitialized_vars
377            .iter()
378            .find(|(n, _)| n.eq(name))
379            .map(|(_, need_from_glib)| *need_from_glib)
380    }
381
382    fn add_trampoline(
383        &self,
384        env: &Env,
385        chunks: &mut Vec<Chunk>,
386        trampoline: &Trampoline,
387        full_type: &Option<(String, String)>,
388        pos: Option<usize>,
389        bounds: &str,
390        bounds_names: &str,
391        is_destroy: bool,
392    ) {
393        if !is_destroy {
394            if full_type.is_none() {
395                if trampoline.scope.is_call() {
396                    chunks.push(Chunk::Custom(format!(
397                        "let mut {0}_data: {1} = {0};",
398                        trampoline.name, trampoline.bound_name
399                    )));
400                } else {
401                    chunks.push(Chunk::Custom(format!(
402                        "let {0}_data: Box_<{1}> = Box_::new({0});",
403                        trampoline.name, trampoline.bound_name
404                    )));
405                }
406            } else if trampoline.scope.is_call() {
407                chunks.push(Chunk::Custom(format!(
408                    "let mut {0}_data: &mut {1} = &mut {0};",
409                    trampoline.name, trampoline.bound_name
410                )));
411            } else {
412                chunks.push(Chunk::Custom(format!(
413                    "let {0}_data: {1} = {0};",
414                    trampoline.name, trampoline.bound_name
415                )));
416            }
417        }
418
419        let mut body = Vec::new();
420        let mut arguments = Vec::new();
421
422        for par in &trampoline.parameters.transformations {
423            if par.name == "this"
424                || trampoline.parameters.c_parameters[par.ind_c].is_real_gpointer(env)
425            {
426                continue;
427            }
428            let ty_name = match RustType::try_new(env, par.typ) {
429                Ok(x) => x.into_string(),
430                _ => String::new(),
431            };
432            let nullable = trampoline.parameters.rust_parameters[par.ind_rust].nullable;
433            let is_basic = add_chunk_for_type(env, par.typ, par, &mut body, &ty_name, nullable);
434            if is_gstring(&ty_name) {
435                if *nullable {
436                    arguments.push(Chunk::Name(format!(
437                        "(*{}).as_ref().map(|s| s.as_str())",
438                        par.name
439                    )));
440                } else {
441                    arguments.push(Chunk::Name(format!("{}.as_str()", par.name)));
442                }
443                continue;
444            }
445            if *nullable && !is_basic {
446                arguments.push(Chunk::Name(format!("{}.as_ref().as_ref()", par.name)));
447                continue;
448            }
449            arguments.push(Chunk::Name(format!(
450                "{}{}",
451                if is_basic { "" } else { "&" },
452                par.name
453            )));
454        }
455
456        let func = trampoline
457            .parameters
458            .c_parameters
459            .last()
460            .map_or_else(|| "Unknown".to_owned(), |p| p.name.clone());
461
462        if let Some(full_type) = full_type {
463            if is_destroy || trampoline.scope.is_async() {
464                body.push(Chunk::Let {
465                    name: format!("{}callback", if is_destroy { "_" } else { "" }),
466                    is_mut: false,
467                    value: Box::new(Chunk::Custom(format!("Box_::from_raw({func} as *mut _)"))),
468                    type_: Some(Box::new(Chunk::Custom(full_type.1.clone()))),
469                });
470            } else {
471                body.push(Chunk::Let {
472                    name: "callback".to_owned(),
473                    is_mut: false,
474                    value: Box::new(Chunk::Custom(format!(
475                        "{}*({} as *mut _)",
476                        if !trampoline.scope.is_call() {
477                            "&"
478                        } else if pos.is_some() {
479                            "&mut "
480                        } else {
481                            ""
482                        },
483                        func
484                    ))),
485                    type_: Some(Box::new(Chunk::Custom(
486                        if !trampoline.scope.is_async() && !trampoline.scope.is_call() {
487                            format!(
488                                "&{}",
489                                full_type
490                                    .1
491                                    .strip_prefix("Box_<")
492                                    .unwrap()
493                                    .strip_suffix(">")
494                                    .unwrap()
495                            )
496                        } else {
497                            full_type.1.clone()
498                        },
499                    ))),
500                });
501                if trampoline.scope.is_async() {
502                    body.push(Chunk::Custom(format!(
503                        "let callback = callback{}{};",
504                        if let Some(pos) = pos {
505                            format!(".{pos}")
506                        } else {
507                            String::new()
508                        },
509                        if *trampoline.nullable {
510                            ".expect(\"cannot get closure...\")"
511                        } else {
512                            ""
513                        }
514                    )));
515                } else if !trampoline.scope.is_call() {
516                    if *trampoline.nullable {
517                        body.push(Chunk::Custom(format!(
518                            "if let Some(ref callback) = callback{} {{",
519                            if let Some(pos) = pos {
520                                format!(".{pos}")
521                            } else {
522                                String::new()
523                            }
524                        )));
525                    } else {
526                        body.push(Chunk::Custom(format!(
527                            "let callback = &callback{};",
528                            if let Some(pos) = pos {
529                                format!(".{pos}")
530                            } else {
531                                String::new()
532                            }
533                        )));
534                    }
535                } else if !trampoline.scope.is_async() && *trampoline.nullable {
536                    body.push(Chunk::Custom(format!(
537                        "if let Some(ref {}callback) = {} {{",
538                        if trampoline.scope.is_call() {
539                            "mut "
540                        } else {
541                            ""
542                        },
543                        if let Some(pos) = pos {
544                            format!("(*callback).{pos}")
545                        } else {
546                            "*callback".to_owned()
547                        }
548                    )));
549                }
550            }
551        } else {
552            body.push(Chunk::Let {
553                name: format!("{}callback", if is_destroy { "_" } else { "" }),
554                is_mut: false,
555                value: Box::new(Chunk::Custom(
556                    if is_destroy || trampoline.scope.is_async() {
557                        format!("Box_::from_raw({} as *mut {})", func, trampoline.bound_name)
558                    } else if trampoline.scope.is_call() {
559                        format!("{} as *mut {}", func, trampoline.bound_name)
560                    } else {
561                        format!("&*({} as *mut {})", func, trampoline.bound_name)
562                    },
563                )),
564                type_: None,
565            });
566            if !is_destroy && *trampoline.nullable {
567                if trampoline.scope.is_async() {
568                    body.push(Chunk::Custom(
569                        "let callback = (*callback).expect(\"cannot get closure...\");".to_owned(),
570                    ));
571                } else {
572                    body.push(Chunk::Custom(format!(
573                        "if let Some(ref {}callback) = {} {{",
574                        if trampoline.scope.is_call() {
575                            "mut "
576                        } else {
577                            ""
578                        },
579                        if let Some(pos) = pos {
580                            format!("(*callback).{pos}")
581                        } else {
582                            "*callback".to_owned()
583                        }
584                    )));
585                }
586            }
587        }
588        if !is_destroy {
589            use crate::writer::to_code::ToCode;
590            body.push(Chunk::Custom(format!(
591                "{}({})",
592                if !*trampoline.nullable {
593                    "(*callback)"
594                } else if trampoline.scope.is_async() {
595                    "callback"
596                } else {
597                    "\tcallback"
598                },
599                arguments
600                    .iter()
601                    .flat_map(|arg| arg.to_code(env))
602                    .collect::<Vec<_>>()
603                    .join(", "),
604            )));
605            if !trampoline.scope.is_async() && *trampoline.nullable {
606                body.push(Chunk::Custom("} else {".to_owned()));
607                body.push(Chunk::Custom(
608                    "\tpanic!(\"cannot get closure...\")".to_owned(),
609                ));
610                body.push(Chunk::Custom("}".to_owned()));
611            }
612            if trampoline.ret.c_type != "void" {
613                use crate::codegen::trampoline_to_glib::TrampolineToGlib;
614
615                body.push(Chunk::Custom(trampoline.ret.trampoline_to_glib(env)));
616            }
617        }
618
619        let extern_func = Chunk::ExternCFunc {
620            name: format!("{}_func", trampoline.name),
621            parameters: trampoline
622                .parameters
623                .c_parameters
624                .iter()
625                .skip(1) // to skip the generated this
626                .map(|p| {
627                    if p.is_real_gpointer(env) {
628                        Param {
629                            name: p.name.clone(),
630                            typ: use_glib_if_needed(env, "ffi::gpointer"),
631                        }
632                    } else {
633                        Param {
634                            name: p.name.clone(),
635                            typ: crate::analysis::ffi_type::ffi_type(env, p.typ, &p.c_type)
636                                .expect("failed to write c_type")
637                                .into_string(),
638                        }
639                    }
640                })
641                .collect::<Vec<_>>(),
642            body: Box::new(Chunk::Chunks(body)),
643            return_value: if trampoline.ret.c_type != "void" {
644                let p = &trampoline.ret;
645                Some(
646                    crate::analysis::ffi_type::ffi_type(env, p.typ, &p.c_type)
647                        .expect("failed to write c_type")
648                        .into_string(),
649                )
650            } else {
651                None
652            },
653            bounds: bounds.to_owned(),
654        };
655
656        chunks.push(extern_func);
657        let bounds_str = if bounds_names.is_empty() {
658            String::new()
659        } else {
660            format!("::<{bounds_names}>")
661        };
662        if !is_destroy {
663            if *trampoline.nullable {
664                chunks.push(Chunk::Custom(format!(
665                    "let {0} = if {0}_data.is_some() {{ Some({0}_func{1} as _) }} else {{ None }};",
666                    trampoline.name, bounds_str
667                )));
668            } else {
669                chunks.push(Chunk::Custom(format!(
670                    "let {0} = Some({0}_func{1} as _);",
671                    trampoline.name, bounds_str
672                )));
673            }
674        } else {
675            chunks.push(Chunk::Custom(format!(
676                "let destroy_call{} = Some({}_func{} as _);",
677                trampoline.destroy_index, trampoline.name, bounds_str
678            )));
679        }
680    }
681
682    fn add_async_trampoline(
683        &self,
684        env: &Env,
685        chunks: &mut Vec<Chunk>,
686        trampoline: &AsyncTrampoline,
687    ) {
688        chunks.push(Chunk::Custom(String::from(
689            r#"
690                let main_context = glib::MainContext::ref_thread_default();
691                let is_main_context_owner = main_context.is_owner();
692                let has_acquired_main_context = (!is_main_context_owner)
693                    .then(|| main_context.acquire().ok())
694                    .flatten();
695                assert!(
696                    is_main_context_owner || has_acquired_main_context.is_some(),
697                    "Async operations only allowed if the thread is owning the MainContext"
698                );
699        "#,
700        )));
701
702        chunks.push(Chunk::Let {
703            name: "user_data".to_string(),
704            is_mut: false,
705            value: Box::new(Chunk::Custom(format!(
706                "Box_::new({}::new(callback))",
707                use_glib_type(env, "thread_guard::ThreadGuard")
708            ))),
709            type_: Some(Box::new(Chunk::Custom(format!(
710                "Box_<{}<{}>>",
711                use_glib_type(env, "thread_guard::ThreadGuard"),
712                trampoline.bound_name
713            )))),
714        });
715
716        let mut finish_args = vec![];
717        let mut uninitialized_vars = Vec::new();
718        if trampoline.is_method {
719            finish_args.push(Chunk::Cast {
720                name: "_source_object".to_string(),
721                type_: "*mut _".to_string(),
722            });
723        }
724        let mut found_async_result = false;
725        finish_args.extend(
726            trampoline
727                .output_params
728                .iter()
729                .filter(|out| {
730                    out.lib_par.direction == ParameterDirection::Out
731                        || out.lib_par.typ.full_name(&env.library) == "Gio.AsyncResult"
732                })
733                .map(|out| {
734                    if out.lib_par.typ.full_name(&env.library) == "Gio.AsyncResult" {
735                        found_async_result = true;
736                        return Chunk::Name("res".to_string());
737                    }
738                    let kind = type_mem_mode(env, &out.lib_par);
739                    let mut par: parameter_ffi_call_out::Parameter = out.into();
740                    if kind.is_uninitialized() {
741                        par.is_uninitialized = true;
742                        uninitialized_vars.push((
743                            out.lib_par.name.clone(),
744                            self.check_if_need_glib_conversion(env, out.lib_par.typ),
745                        ));
746                    }
747                    Chunk::FfiCallOutParameter { par }
748                }),
749        );
750        assert!(
751            found_async_result,
752            "The check *wasn't* performed in analysis part: Guillaume was wrong!"
753        );
754        let index_to_ignore = find_index_to_ignore(
755            trampoline.output_params.iter().map(|par| &par.lib_par),
756            trampoline.ffi_ret.as_ref().map(|ret| &ret.lib_par),
757        );
758        let mut result: Vec<_> = trampoline
759            .output_params
760            .iter()
761            .enumerate()
762            .filter(|&(index, out)| {
763                out.lib_par.direction == ParameterDirection::Out
764                    && out.lib_par.name != "error"
765                    && Some(index) != index_to_ignore
766            })
767            .map(|(_, out)| {
768                let mem_mode = c_type_mem_mode_lib(
769                    env,
770                    out.lib_par.typ,
771                    out.lib_par.caller_allocates,
772                    out.lib_par.transfer,
773                );
774                let value = self.generate_initialized_value(&out.lib_par.name, &uninitialized_vars);
775                if let OutMemMode::UninitializedNamed(_) = mem_mode {
776                    value
777                } else {
778                    let array_length_name = self.array_length(out).cloned();
779                    self.remove_extra_assume_init(&array_length_name, &mut uninitialized_vars);
780                    Chunk::FromGlibConversion {
781                        mode: out.into(),
782                        array_length_name,
783                        value: Box::new(value),
784                    }
785                }
786            })
787            .collect();
788
789        if let Some(ref ffi_ret) = trampoline.ffi_ret {
790            let mem_mode = c_type_mem_mode_lib(
791                env,
792                ffi_ret.lib_par.typ,
793                ffi_ret.lib_par.caller_allocates,
794                ffi_ret.lib_par.transfer,
795            );
796            let value = Chunk::Name("ret".to_string());
797            if let OutMemMode::UninitializedNamed(_) = mem_mode {
798                result.insert(0, value);
799            } else {
800                let array_length_name = self.array_length(ffi_ret).cloned();
801                self.remove_extra_assume_init(&array_length_name, &mut uninitialized_vars);
802                result.insert(
803                    0,
804                    Chunk::FromGlibConversion {
805                        mode: ffi_ret.into(),
806                        array_length_name,
807                        value: Box::new(value),
808                    },
809                );
810            }
811        }
812        // If the trampoline doesn't have a GError parameter
813        let has_error_parameter = self
814            .async_trampoline
815            .as_ref()
816            .map(|a| a.has_error_parameter)
817            .unwrap_or_default();
818
819        let result = Chunk::Tuple(result, TupleMode::WithUnit);
820
821        let mut body = if has_error_parameter {
822            vec![Chunk::Let {
823                name: "error".to_string(),
824                is_mut: true,
825                value: Box::new(Chunk::NullMutPtr),
826                type_: None,
827            }]
828        } else {
829            vec![]
830        };
831        let output_vars = trampoline
832            .output_params
833            .iter()
834            .filter(|out| {
835                out.lib_par.direction == ParameterDirection::Out && out.lib_par.name != "error"
836            })
837            .map(|out| Chunk::Let {
838                name: out.lib_par.name.clone(),
839                is_mut: true,
840                value: Box::new(type_mem_mode(env, &out.lib_par)),
841                type_: None,
842            });
843        body.extend(output_vars);
844
845        if trampoline.ffi_ret.is_some() {
846            let ret_name = if has_error_parameter {
847                "ret"
848            } else {
849                "result" // Needed as in case of an error param we would have
850                         // let result = if error.is_null() { Ok()} else {
851                         // Err()};
852            };
853
854            body.push(Chunk::Let {
855                name: ret_name.to_string(),
856                is_mut: false,
857                value: Box::new(Chunk::FfiCall {
858                    name: trampoline.finish_func_name.clone(),
859                    params: finish_args,
860                    ignore_return: false,
861                }),
862                type_: None,
863            });
864        } else {
865            body.push(Chunk::FfiCall {
866                name: trampoline.finish_func_name.clone(),
867                params: finish_args,
868                ignore_return: true,
869            });
870        }
871
872        if has_error_parameter {
873            body.push(Chunk::Let {
874                name: "result".to_string(),
875                is_mut: false,
876                value: Box::new(Chunk::ErrorResultReturn {
877                    ret: None,
878                    value: Box::new(result),
879                }),
880                type_: None,
881            });
882        }
883        body.push(Chunk::Let {
884            name: "callback".to_string(),
885            is_mut: false,
886            value: Box::new(Chunk::Custom("Box_::from_raw(user_data as *mut _)".into())),
887            type_: Some(Box::new(Chunk::Custom(format!(
888                "Box_<{}<{}>>",
889                use_glib_type(env, "thread_guard::ThreadGuard"),
890                trampoline.bound_name
891            )))),
892        });
893        body.push(Chunk::Let {
894            name: "callback".to_string(),
895            is_mut: false,
896            value: Box::new(Chunk::Custom("callback.into_inner()".into())),
897            type_: Some(Box::new(Chunk::Custom(format!(
898                "{}",
899                trampoline.bound_name
900            )))),
901        });
902        body.push(Chunk::Call {
903            func_name: "callback".to_string(),
904            arguments: vec![Chunk::Name("result".to_string())],
905        });
906
907        let parameters = vec![
908            Param {
909                name: "_source_object".to_string(),
910                typ: format!("*mut {}", use_glib_type(env, "gobject_ffi::GObject")),
911            },
912            Param {
913                name: "res".to_string(),
914                typ: format!("*mut {}", use_gio_type(env, "ffi::GAsyncResult")),
915            },
916            Param {
917                name: "user_data".to_string(),
918                typ: use_glib_if_needed(env, "ffi::gpointer"),
919            },
920        ];
921
922        chunks.push(Chunk::ExternCFunc {
923            name: format!(
924                "{}<{}: {}>",
925                trampoline.name, trampoline.bound_name, trampoline.callback_type
926            ),
927            parameters,
928            body: Box::new(Chunk::Chunks(body.clone())),
929            return_value: None,
930            bounds: String::new(),
931        });
932        let chunk = Chunk::Let {
933            name: "callback".to_string(),
934            is_mut: false,
935            value: Box::new(Chunk::Name(format!(
936                "{}::<{}>",
937                trampoline.name, trampoline.bound_name
938            ))),
939            type_: None,
940        };
941        chunks.push(chunk);
942    }
943
944    fn array_length(&self, param: &analysis::Parameter) -> Option<&String> {
945        self.async_trampoline.as_ref().and_then(|trampoline| {
946            param
947                .lib_par
948                .array_length
949                .map(|index| &trampoline.output_params[index as usize].lib_par.name)
950        })
951    }
952
953    fn add_assertion(&self, chunks: &mut Vec<Chunk>) {
954        match self.assertion {
955            SafetyAssertionMode::None => (),
956            x => chunks.insert(0, Chunk::AssertInit(x)),
957        }
958    }
959
960    fn add_in_array_lengths(&self, chunks: &mut Vec<Chunk>) {
961        for trans in &self.transformations {
962            if let TransformationType::Length {
963                ref array_name,
964                ref array_length_name,
965                ..
966            } = trans.transformation_type
967            {
968                if let In = self.parameters[trans.ind_c] {
969                    let value = Chunk::Custom(format!("{array_name}.len() as _"));
970                    chunks.push(Chunk::Let {
971                        name: array_length_name.clone(),
972                        is_mut: false,
973                        value: Box::new(value),
974                        type_: None,
975                    });
976                }
977            }
978        }
979    }
980
981    fn generate_call(&self, calls: &FuncParameters<'_>) -> Chunk {
982        let params = self.generate_func_parameters(calls);
983        Chunk::FfiCall {
984            name: self.glib_name.clone(),
985            params,
986            ignore_return: false,
987        }
988    }
989    fn generate_call_conversion(
990        &self,
991        call: Chunk,
992        uninitialized_vars: &mut Vec<(String, bool)>,
993    ) -> Chunk {
994        let array_length_name = self.find_array_length_name("");
995        self.remove_extra_assume_init(&array_length_name, uninitialized_vars);
996        Chunk::FfiCallConversion {
997            ret: self.ret.ret.clone(),
998            array_length_name,
999            call: Box::new(call),
1000        }
1001    }
1002    fn generate_func_parameters(&self, calls: &FuncParameters<'_>) -> Vec<Chunk> {
1003        let mut params = Vec::new();
1004        for trans in &self.transformations {
1005            if !trans.transformation_type.is_to_glib() {
1006                continue;
1007            }
1008            let par = &self.parameters[trans.ind_c];
1009            let chunk = match par {
1010                In => Chunk::FfiCallParameter {
1011                    transformation_type: trans.transformation_type.clone(),
1012                },
1013                Out { parameter, .. } => Chunk::FfiCallOutParameter {
1014                    par: parameter.clone(),
1015                },
1016            };
1017            params.push(chunk);
1018        }
1019        let mut to_insert = Vec::new();
1020        for (user_data_index, FuncParameter { pos, callbacks, .. }) in calls.iter() {
1021            let all_call = callbacks.iter().all(|c| c.scope.is_call());
1022            to_insert.push((
1023                *user_data_index,
1024                Chunk::FfiCallParameter {
1025                    transformation_type: TransformationType::ToGlibDirect {
1026                        name: if all_call {
1027                            format!("super_callback{pos} as *mut _ as *mut _")
1028                        } else {
1029                            format!("Box_::into_raw(super_callback{pos}) as *mut _")
1030                        },
1031                    },
1032                },
1033            ));
1034        }
1035        for destroy in &self.destroys {
1036            to_insert.push((
1037                destroy.destroy_index,
1038                Chunk::FfiCallParameter {
1039                    transformation_type: TransformationType::ToGlibDirect {
1040                        name: format!("destroy_call{}", destroy.destroy_index),
1041                    },
1042                },
1043            ));
1044        }
1045        to_insert.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
1046        for (pos, data) in to_insert {
1047            params.insert(pos, data);
1048        }
1049        params
1050    }
1051    fn get_outs(&self) -> Vec<&Parameter> {
1052        self.parameters
1053            .iter()
1054            .filter(|par| matches!(*par, Out { .. }))
1055            .collect()
1056    }
1057    fn get_outs_without_error(&self) -> Vec<&Parameter> {
1058        self.parameters
1059            .iter()
1060            .filter(|par| {
1061                if let Out { parameter, .. } = par {
1062                    !parameter.is_error
1063                } else {
1064                    false
1065                }
1066            })
1067            .collect()
1068    }
1069    fn check_if_need_glib_conversion(&self, env: &Env, typ: TypeId) -> bool {
1070        // TODO: maybe improve this part to potentially handle more cases than just
1071        // glib::Pid?
1072        matches!(
1073            env.type_(typ),
1074            library::Type::Alias(a) if a.c_identifier == "GPid"
1075        )
1076    }
1077    fn write_out_variables(&self, body: &mut Vec<Chunk>, env: &Env) -> Vec<(String, bool)> {
1078        let mut uninitialized_vars = Vec::new();
1079        for par in self.get_outs() {
1080            if let Out {
1081                parameter,
1082                mem_mode,
1083            } = par
1084            {
1085                let val = self.get_uninitialized(mem_mode);
1086                if val.is_uninitialized() {
1087                    uninitialized_vars.push((
1088                        parameter.name.clone(),
1089                        self.check_if_need_glib_conversion(env, parameter.typ),
1090                    ));
1091                }
1092                let chunk = Chunk::Let {
1093                    name: parameter.name.clone(),
1094                    is_mut: true,
1095                    value: Box::new(val),
1096                    type_: None,
1097                };
1098                body.push(chunk);
1099            }
1100        }
1101        uninitialized_vars
1102    }
1103    fn get_uninitialized(&self, mem_mode: &OutMemMode) -> Chunk {
1104        use self::OutMemMode::*;
1105        match mem_mode {
1106            Uninitialized => Chunk::Uninitialized,
1107            UninitializedNamed(ref name) => Chunk::UninitializedNamed { name: name.clone() },
1108            NullPtr => Chunk::NullPtr,
1109            NullMutPtr => Chunk::NullMutPtr,
1110        }
1111    }
1112
1113    fn generate_out_return(&self, uninitialized_vars: &mut Vec<(String, bool)>) -> Option<Chunk> {
1114        if !self.outs_as_return {
1115            return None;
1116        }
1117        let outs = self.get_outs_without_error();
1118        let mut chs: Vec<Chunk> = Vec::with_capacity(outs.len());
1119        for par in outs {
1120            if let Out {
1121                parameter,
1122                mem_mode,
1123            } = par
1124            {
1125                if self.transformations.iter().any(|tr| {
1126                    matches!(
1127                        &tr.transformation_type,
1128                        TransformationType::Length {
1129                             array_length_name,
1130                            ..
1131                        } if array_length_name == &parameter.name
1132                    )
1133                }) {
1134                    continue;
1135                }
1136
1137                chs.push(self.out_parameter_to_return(parameter, mem_mode, uninitialized_vars));
1138            }
1139        }
1140        let chunk = Chunk::Tuple(chs, TupleMode::Auto);
1141        Some(chunk)
1142    }
1143    fn out_parameter_to_return(
1144        &self,
1145        parameter: &parameter_ffi_call_out::Parameter,
1146        mem_mode: &OutMemMode,
1147        uninitialized_vars: &mut Vec<(String, bool)>,
1148    ) -> Chunk {
1149        let value = self.generate_initialized_value(&parameter.name, uninitialized_vars);
1150        if let OutMemMode::UninitializedNamed(_) = mem_mode {
1151            value
1152        } else {
1153            let array_length_name = self.find_array_length_name(&parameter.name);
1154            self.remove_extra_assume_init(&array_length_name, uninitialized_vars);
1155            Chunk::FromGlibConversion {
1156                mode: parameter.into(),
1157                array_length_name,
1158                value: Box::new(value),
1159            }
1160        }
1161    }
1162    fn apply_outs_mode(
1163        &self,
1164        call: Chunk,
1165        ret: Option<Chunk>,
1166        uninitialized_vars: &mut Vec<(String, bool)>,
1167    ) -> (Chunk, Option<Chunk>) {
1168        use crate::analysis::out_parameters::Mode::*;
1169        match self.outs_mode {
1170            None => (call, ret),
1171            Normal => (call, ret),
1172            Optional => {
1173                let call = Chunk::Let {
1174                    name: "ret".into(),
1175                    is_mut: false,
1176                    value: Box::new(call),
1177                    type_: Option::None,
1178                };
1179                let ret = ret.expect("No return in optional outs mode");
1180                let ret = Chunk::OptionalReturn {
1181                    condition: "ret".into(),
1182                    value: Box::new(ret),
1183                };
1184                (call, Some(ret))
1185            }
1186            Combined => {
1187                let call = Chunk::Let {
1188                    name: "ret".into(),
1189                    is_mut: false,
1190                    value: Box::new(call),
1191                    type_: Option::None,
1192                };
1193                let mut ret = ret.expect("No return in combined outs mode");
1194                if let Chunk::Tuple(ref mut vec, _) = ret {
1195                    vec.insert(0, Chunk::Custom("ret".into()));
1196                }
1197                (call, Some(ret))
1198            }
1199            Throws(return_strategy) => {
1200                // extracting original FFI function call
1201                let (boxed_call, array_length_name, ret_info) = if let Chunk::FfiCallConversion {
1202                    call: inner,
1203                    array_length_name,
1204                    ret: ret_info,
1205                } = call
1206                {
1207                    (inner, array_length_name, ret_info)
1208                } else {
1209                    panic!("Call without Chunk::FfiCallConversion")
1210                };
1211                self.remove_extra_assume_init(&array_length_name, uninitialized_vars);
1212                let (name, assert_safe_ret) = match return_strategy {
1213                    ThrowFunctionReturnStrategy::ReturnResult => ("ret", Option::None),
1214                    ThrowFunctionReturnStrategy::CheckError => {
1215                        ("is_ok", Some(Box::new(Chunk::AssertErrorSanity)))
1216                    }
1217                    ThrowFunctionReturnStrategy::Void => ("_", Option::None),
1218                };
1219                let call = Chunk::Let {
1220                    name: name.into(),
1221                    is_mut: false,
1222                    value: boxed_call,
1223                    type_: Option::None,
1224                };
1225                let mut ret = ret.expect("No return in throws outs mode");
1226
1227                if let Chunk::Tuple(vec, mode) = &mut ret {
1228                    *mode = TupleMode::WithUnit;
1229                    if return_strategy == ThrowFunctionReturnStrategy::ReturnResult {
1230                        let val = Chunk::Custom("ret".into());
1231                        let conv = Chunk::FfiCallConversion {
1232                            call: Box::new(val),
1233                            array_length_name,
1234                            ret: ret_info,
1235                        };
1236                        vec.insert(0, conv);
1237                    }
1238                } else {
1239                    panic!("Return is not Tuple")
1240                }
1241                ret = Chunk::ErrorResultReturn {
1242                    ret: assert_safe_ret,
1243                    value: Box::new(ret),
1244                };
1245                (call, Some(ret))
1246            }
1247        }
1248    }
1249
1250    fn find_array_length_name(&self, array_name_: &str) -> Option<String> {
1251        self.transformations.iter().find_map(|tr| {
1252            if let TransformationType::Length {
1253                ref array_name,
1254                ref array_length_name,
1255                ..
1256            } = tr.transformation_type
1257            {
1258                if array_name == array_name_ {
1259                    Some(array_length_name.clone())
1260                } else {
1261                    None
1262                }
1263            } else {
1264                None
1265            }
1266        })
1267    }
1268}
1269
1270fn c_type_mem_mode_lib(
1271    env: &Env,
1272    typ: library::TypeId,
1273    caller_allocates: bool,
1274    transfer: library::Transfer,
1275) -> OutMemMode {
1276    use self::OutMemMode::*;
1277    match ConversionType::of(env, typ) {
1278        ConversionType::Pointer => {
1279            if caller_allocates {
1280                UninitializedNamed(RustType::try_new(env, typ).unwrap().into_string())
1281            } else {
1282                use crate::library::Type::*;
1283                let type_ = env.library.type_(typ);
1284                match type_ {
1285                    Basic(
1286                        library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename,
1287                    ) => {
1288                        if transfer == library::Transfer::Full {
1289                            NullMutPtr
1290                        } else {
1291                            NullPtr
1292                        }
1293                    }
1294                    _ => NullMutPtr,
1295                }
1296            }
1297        }
1298        _ => Uninitialized,
1299    }
1300}
1301
1302fn c_type_mem_mode(env: &Env, parameter: &AnalysisCParameter) -> OutMemMode {
1303    c_type_mem_mode_lib(
1304        env,
1305        parameter.typ,
1306        parameter.caller_allocates,
1307        parameter.transfer,
1308    )
1309}
1310
1311fn type_mem_mode(env: &Env, parameter: &library::Parameter) -> Chunk {
1312    match ConversionType::of(env, parameter.typ) {
1313        ConversionType::Pointer => {
1314            if parameter.caller_allocates {
1315                Chunk::UninitializedNamed {
1316                    name: RustType::try_new(env, parameter.typ).unwrap().into_string(),
1317                }
1318            } else {
1319                use crate::library::Type::*;
1320                let type_ = env.library.type_(parameter.typ);
1321                match type_ {
1322                    Basic(
1323                        library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename,
1324                    ) => {
1325                        if parameter.transfer == library::Transfer::Full {
1326                            Chunk::NullMutPtr
1327                        } else {
1328                            Chunk::NullPtr
1329                        }
1330                    }
1331                    _ => Chunk::NullMutPtr,
1332                }
1333            }
1334        }
1335        _ => Chunk::Uninitialized,
1336    }
1337}
1338
1339fn add_chunk_for_type(
1340    env: &Env,
1341    typ_: library::TypeId,
1342    par: &trampoline_parameters::Transformation,
1343    body: &mut Vec<Chunk>,
1344    ty_name: &str,
1345    nullable: library::Nullable,
1346) -> bool {
1347    let type_ = env.type_(typ_);
1348    match type_ {
1349        library::Type::Basic(x) if !x.requires_conversion() => true,
1350        library::Type::Basic(library::Basic::Boolean) => {
1351            body.push(Chunk::Custom(format!(
1352                "let {0} = from_glib({0});",
1353                par.name
1354            )));
1355            true
1356        }
1357        library::Type::Basic(library::Basic::UniChar) => {
1358            body.push(Chunk::Custom(format!(
1359                "let {0} = std::convert::TryFrom::try_from({0})\
1360                     .expect(\"conversion from an invalid Unicode value attempted\");",
1361                par.name
1362            )));
1363            true
1364        }
1365        library::Type::Alias(_) if ty_name == "glib::Pid" => {
1366            body.push(Chunk::Custom(format!(
1367                "let {0} = from_glib({0});",
1368                par.name
1369            )));
1370            true
1371        }
1372        library::Type::Alias(x) => add_chunk_for_type(env, x.typ, par, body, ty_name, nullable),
1373        x => {
1374            let (begin, end) =
1375                crate::codegen::trampoline_from_glib::from_glib_xxx(par.transfer, true);
1376
1377            let type_name;
1378            if is_gstring(ty_name) {
1379                if *nullable {
1380                    if par.conversion_type == ConversionType::Borrow {
1381                        type_name = String::from(": Borrowed<Option<glib::GString>>");
1382                    } else {
1383                        type_name = String::from(": Option<glib::GString>");
1384                    }
1385                } else if par.conversion_type == ConversionType::Borrow {
1386                    type_name = String::from(": Borrowed<glib::GString>");
1387                } else {
1388                    type_name = String::from(": GString");
1389                }
1390            } else if par.transfer == library::Transfer::None && *nullable {
1391                if par.conversion_type == ConversionType::Borrow {
1392                    type_name = format!(": Borrowed<Option<{ty_name}>>");
1393                } else {
1394                    type_name = format!(": Option<{ty_name}>");
1395                }
1396            } else {
1397                type_name = String::new();
1398            }
1399
1400            body.push(Chunk::Custom(format!(
1401                "let {1}{3} = {0}{1}{2};",
1402                begin, par.name, end, type_name
1403            )));
1404            x.is_basic()
1405        }
1406    }
1407}