libgir/codegen/
return_value.rs

1use std::cmp;
2
3use crate::{
4    analysis::{
5        self, conversion_type::ConversionType, namespaces, out_parameters::Mode,
6        rust_type::RustType, try_from_glib::TryFromGlib,
7    },
8    env::Env,
9    library::{self, ParameterDirection, TypeId},
10    nameutil::{is_gstring, mangle_keywords, use_glib_type},
11    traits::*,
12};
13
14pub trait ToReturnValue {
15    fn to_return_value(
16        &self,
17        env: &Env,
18        try_from_glib: &TryFromGlib,
19        is_trampoline: bool,
20    ) -> Option<String>;
21}
22
23impl ToReturnValue for library::Parameter {
24    fn to_return_value(
25        &self,
26        env: &Env,
27        try_from_glib: &TryFromGlib,
28        is_trampoline: bool,
29    ) -> Option<String> {
30        let mut name = RustType::builder(env, self.typ)
31            .direction(self.direction)
32            .nullable(self.nullable)
33            .scope(self.scope)
34            .try_from_glib(try_from_glib)
35            .try_build_param()
36            .into_string();
37        if is_trampoline
38            && self.direction == library::ParameterDirection::Return
39            && is_gstring(&name)
40        {
41            name = "String".to_owned();
42        }
43        let type_str = match ConversionType::of(env, self.typ) {
44            ConversionType::Unknown => format!("/*Unknown conversion*/{name}"),
45            // TODO: records as in gtk_container_get_path_for_child
46            _ => name,
47        };
48
49        Some(type_str)
50    }
51}
52
53impl ToReturnValue for analysis::return_value::Info {
54    fn to_return_value(
55        &self,
56        env: &Env,
57        try_from_glib: &TryFromGlib,
58        is_trampoline: bool,
59    ) -> Option<String> {
60        let par = self.parameter.as_ref()?;
61        par.lib_par
62            .to_return_value(env, try_from_glib, is_trampoline)
63            .map(|type_name| {
64                if self.nullable_return_is_error.is_some() && type_name.starts_with("Option<") {
65                    // Change `Option<T>` to `Result<T, glib::BoolError>`
66                    format!(
67                        "Result<{}, {}BoolError>",
68                        &type_name[7..(type_name.len() - 1)],
69                        if env.namespaces.glib_ns_id == namespaces::MAIN {
70                            ""
71                        } else {
72                            "glib::"
73                        }
74                    )
75                } else {
76                    type_name
77                }
78            })
79    }
80}
81
82/// Returns the `TypeId` of the returned types from the provided function.
83pub fn out_parameter_types(analysis: &analysis::functions::Info) -> Vec<TypeId> {
84    // If it returns an error, there is nothing for us to check.
85    if analysis.ret.bool_return_is_error.is_some()
86        || analysis.ret.nullable_return_is_error.is_some()
87    {
88        return Vec::new();
89    }
90
91    if !analysis.outs.is_empty() {
92        let num_out_args = analysis
93            .outs
94            .iter()
95            .filter(|out| out.lib_par.array_length.is_none())
96            .count();
97        let num_out_sizes = analysis
98            .outs
99            .iter()
100            .filter(|out| out.lib_par.array_length.is_some())
101            .count();
102        // We need to differentiate between array(s)'s size arguments and normal ones.
103        // If we have 2 "normal" arguments and one "size" argument, we still
104        // need to wrap them into "()" so we take that into account. If the
105        // opposite, it means that there are two arguments in any case so
106        // we need "()" too.
107        let num_outs = std::cmp::max(num_out_args, num_out_sizes);
108        match analysis.outs.mode {
109            Mode::Normal | Mode::Combined => {
110                let array_lengths: Vec<_> = analysis
111                    .outs
112                    .iter()
113                    .filter_map(|out| out.lib_par.array_length)
114                    .collect();
115                let mut ret_params = Vec::with_capacity(num_outs);
116
117                for out in analysis.outs.iter().filter(|out| !out.lib_par.is_error) {
118                    // The actual return value is inserted with an empty name at position 0
119                    if !out.lib_par.name.is_empty() {
120                        let mangled_par_name =
121                            crate::nameutil::mangle_keywords(out.lib_par.name.as_str());
122                        let param_pos = analysis
123                            .parameters
124                            .c_parameters
125                            .iter()
126                            .enumerate()
127                            .find_map(|(pos, orig_par)| {
128                                if orig_par.name == mangled_par_name {
129                                    Some(pos)
130                                } else {
131                                    None
132                                }
133                            })
134                            .unwrap();
135                        if array_lengths.contains(&(param_pos as u32)) {
136                            continue;
137                        }
138                    }
139                    ret_params.push(out.lib_par.typ);
140                }
141                ret_params
142            }
143            _ => Vec::new(),
144        }
145    } else if let Some(typ) = analysis.ret.parameter.as_ref().map(|out| out.lib_par.typ) {
146        vec![typ]
147    } else {
148        Vec::new()
149    }
150}
151
152fn out_parameter_as_return_parts(
153    analysis: &analysis::functions::Info,
154    env: &Env,
155) -> (&'static str, String) {
156    use crate::analysis::out_parameters::Mode::*;
157    let num_out_args = analysis
158        .outs
159        .iter()
160        .filter(|out| out.lib_par.array_length.is_none())
161        .count();
162    let num_out_sizes = analysis
163        .outs
164        .iter()
165        .filter(|out| out.lib_par.array_length.is_some())
166        .count();
167    // We need to differentiate between array(s)'s size arguments and normal ones.
168    // If we have 2 "normal" arguments and one "size" argument, we still need to
169    // wrap them into "()" so we take that into account. If the opposite, it
170    // means that there are two arguments in any case so we need "()" too.
171    let num_outs = cmp::max(num_out_args, num_out_sizes);
172    match analysis.outs.mode {
173        Normal | Combined => {
174            if num_outs > 1 {
175                ("(", ")".to_owned())
176            } else {
177                ("", String::new())
178            }
179        }
180        Optional => {
181            if num_outs > 1 {
182                if analysis.ret.nullable_return_is_error.is_some() {
183                    (
184                        "Result<(",
185                        format!("), {}>", use_glib_type(env, "BoolError")),
186                    )
187                } else {
188                    ("Option<(", ")>".to_owned())
189                }
190            } else if analysis.ret.nullable_return_is_error.is_some() {
191                ("Result<", format!(", {}>", use_glib_type(env, "BoolError")))
192            } else {
193                ("Option<", ">".to_owned())
194            }
195        }
196        Throws(..) => {
197            if num_outs == 1 + 1 {
198                // if only one parameter except "glib::Error"
199                ("Result<", format!(", {}>", use_glib_type(env, "Error")))
200            } else {
201                ("Result<(", format!("), {}>", use_glib_type(env, "Error")))
202            }
203        }
204        None => unreachable!(),
205    }
206}
207
208pub fn out_parameters_as_return(env: &Env, analysis: &analysis::functions::Info) -> String {
209    let (prefix, suffix) = out_parameter_as_return_parts(analysis, env);
210    let mut return_str = String::with_capacity(100);
211    return_str.push_str(" -> ");
212    return_str.push_str(prefix);
213
214    let array_lengths: Vec<_> = analysis
215        .outs
216        .iter()
217        .filter_map(|out| out.lib_par.array_length)
218        .collect();
219
220    let mut skip = 0;
221    for (pos, out) in analysis
222        .outs
223        .iter()
224        .filter(|out| !out.lib_par.is_error)
225        .enumerate()
226    {
227        // The actual return value is inserted with an empty name at position 0
228        if !out.lib_par.name.is_empty() {
229            let mangled_par_name = mangle_keywords(out.lib_par.name.as_str());
230            let param_pos = analysis
231                .parameters
232                .c_parameters
233                .iter()
234                .enumerate()
235                .find_map(|(pos, orig_par)| {
236                    if orig_par.name == mangled_par_name {
237                        Some(pos)
238                    } else {
239                        None
240                    }
241                })
242                .unwrap();
243            if array_lengths.contains(&(param_pos as u32)) {
244                skip += 1;
245                continue;
246            }
247        }
248
249        if pos > skip {
250            return_str.push_str(", ");
251        }
252        let s = out_parameter_as_return(out, env);
253        return_str.push_str(&s);
254    }
255    return_str.push_str(&suffix);
256    return_str
257}
258
259fn out_parameter_as_return(out: &analysis::Parameter, env: &Env) -> String {
260    // TODO: upcasts?
261    let name = RustType::builder(env, out.lib_par.typ)
262        .direction(ParameterDirection::Return)
263        .nullable(out.lib_par.nullable)
264        .scope(out.lib_par.scope)
265        .try_from_glib(&out.try_from_glib)
266        .try_build_param()
267        .into_string();
268    match ConversionType::of(env, out.lib_par.typ) {
269        ConversionType::Unknown => format!("/*Unknown conversion*/{name}"),
270        _ => name,
271    }
272}