libgir/analysis/
bounds.rs

1use std::{collections::vec_deque::VecDeque, slice::Iter};
2
3use crate::{
4    analysis::{
5        function_parameters::CParameter,
6        functions::{find_function, find_index_to_ignore, finish_function_name},
7        imports::Imports,
8        out_parameters::use_function_return_for_result,
9        ref_mode::RefMode,
10        rust_type::RustType,
11    },
12    config,
13    consts::TYPE_PARAMETERS_START,
14    env::Env,
15    library::{Basic, Class, Concurrency, Function, ParameterDirection, Type, TypeId},
16    traits::IntoString,
17};
18
19#[derive(Clone, Eq, Debug, PartialEq)]
20pub enum BoundType {
21    NoWrapper,
22    // lifetime
23    IsA(Option<char>),
24    // lifetime <- shouldn't be used but just in case...
25    AsRef(Option<char>),
26}
27
28impl BoundType {
29    pub fn need_isa(&self) -> bool {
30        matches!(*self, Self::IsA(_))
31    }
32
33    // TODO: This is just a heuristic for now, based on what we do in codegen!
34    // Theoretically the surrounding function should determine whether it needs to
35    // reuse an alias (ie. to use in `call_func::<P, Q, R>`) or not.
36    // In the latter case an `impl` is generated instead of a type name/alias.
37    pub fn has_alias(&self) -> bool {
38        matches!(*self, Self::NoWrapper)
39    }
40}
41
42#[derive(Clone, Eq, Debug, PartialEq)]
43pub struct Bound {
44    pub bound_type: BoundType,
45    pub parameter_name: String,
46    /// Bound does not have an alias when `param: impl type_str` is used
47    pub alias: Option<char>,
48    pub type_str: String,
49    pub callback_modified: bool,
50}
51
52#[derive(Clone, Debug)]
53pub struct Bounds {
54    unused: VecDeque<char>,
55    used: Vec<Bound>,
56    lifetimes: Vec<char>,
57}
58
59impl Default for Bounds {
60    fn default() -> Bounds {
61        Bounds {
62            unused: (TYPE_PARAMETERS_START..)
63                .take_while(|x| *x <= 'Z')
64                .collect(),
65            used: Vec::new(),
66            lifetimes: Vec::new(),
67        }
68    }
69}
70
71#[derive(Debug, Clone)]
72pub struct CallbackInfo {
73    pub callback_type: String,
74    pub success_parameters: String,
75    pub error_parameters: Option<String>,
76    pub bound_name: char,
77}
78
79impl Bounds {
80    pub fn add_for_parameter(
81        &mut self,
82        env: &Env,
83        func: &Function,
84        par: &CParameter,
85        r#async: bool,
86        concurrency: Concurrency,
87        configured_functions: &[&config::functions::Function],
88    ) -> (Option<String>, Option<CallbackInfo>) {
89        let type_name = RustType::builder(env, par.typ)
90            .ref_mode(RefMode::ByRefFake)
91            .try_build();
92        if type_name.is_err() {
93            return (None, None);
94        }
95        let mut type_string = type_name.into_string();
96        let mut callback_info = None;
97        let mut ret = None;
98        let mut need_is_into_check = false;
99
100        if !par.instance_parameter && par.direction != ParameterDirection::Out {
101            if let Some(bound_type) = Bounds::type_for(env, par.typ) {
102                ret = Some(Bounds::get_to_glib_extra(
103                    &bound_type,
104                    *par.nullable,
105                    par.instance_parameter,
106                    par.move_,
107                ));
108                if r#async && (par.name == "callback" || par.name.ends_with("_callback")) {
109                    let func_name = func.c_identifier.as_ref().unwrap();
110                    let finish_func_name = if let Some(finish_func_name) = &func.finish_func {
111                        finish_func_name.to_string()
112                    } else {
113                        finish_function_name(func_name)
114                    };
115                    if let Some(function) = find_function(env, &finish_func_name) {
116                        // FIXME: This should work completely based on the analysis of the finish()
117                        // function but that a) happens afterwards and b) is
118                        // not accessible from here either.
119                        let mut out_parameters =
120                            find_out_parameters(env, function, configured_functions);
121                        if use_function_return_for_result(
122                            env,
123                            function.ret.typ,
124                            &func.name,
125                            configured_functions,
126                        ) {
127                            let nullable = configured_functions
128                                .iter()
129                                .find_map(|f| f.ret.nullable)
130                                .unwrap_or(function.ret.nullable);
131
132                            out_parameters.insert(
133                                0,
134                                RustType::builder(env, function.ret.typ)
135                                    .direction(function.ret.direction)
136                                    .nullable(nullable)
137                                    .try_build()
138                                    .into_string(),
139                            );
140                        }
141                        let parameters = format_out_parameters(&out_parameters);
142                        let error_type = find_error_type(env, function);
143                        if let Some(ref error) = error_type {
144                            type_string =
145                                format!("FnOnce(Result<{parameters}, {error}>) + 'static");
146                        } else {
147                            type_string = format!("FnOnce({parameters}) + 'static");
148                        }
149                        let bound_name = *self.unused.front().unwrap();
150                        callback_info = Some(CallbackInfo {
151                            callback_type: type_string.clone(),
152                            success_parameters: parameters,
153                            error_parameters: error_type,
154                            bound_name,
155                        });
156                    }
157                } else if par.c_type == "GDestroyNotify" || env.library.type_(par.typ).is_function()
158                {
159                    need_is_into_check = par.c_type != "GDestroyNotify";
160                    if let Type::Function(_) = env.library.type_(par.typ) {
161                        let callback_parameters_config =
162                            configured_functions.iter().find_map(|f| {
163                                f.parameters
164                                    .iter()
165                                    .find(|p| p.ident.is_match(&par.name))
166                                    .map(|p| &p.callback_parameters)
167                            });
168
169                        let mut rust_ty = RustType::builder(env, par.typ)
170                            .direction(par.direction)
171                            .scope(par.scope)
172                            .concurrency(concurrency);
173                        if let Some(callback_parameters_config) = callback_parameters_config {
174                            rust_ty =
175                                rust_ty.callback_parameters_config(callback_parameters_config);
176                        }
177                        type_string = rust_ty
178                            .try_from_glib(&par.try_from_glib)
179                            .try_build()
180                            .into_string();
181                        let bound_name = *self.unused.front().unwrap();
182                        callback_info = Some(CallbackInfo {
183                            callback_type: type_string.clone(),
184                            success_parameters: String::new(),
185                            error_parameters: None,
186                            bound_name,
187                        });
188                    }
189                }
190                if (!need_is_into_check || !*par.nullable) && par.c_type != "GDestroyNotify" {
191                    self.add_parameter(&par.name, &type_string, bound_type, r#async);
192                }
193            }
194        } else if par.instance_parameter {
195            if let Some(bound_type) = Bounds::type_for(env, par.typ) {
196                ret = Some(Bounds::get_to_glib_extra(
197                    &bound_type,
198                    *par.nullable,
199                    true,
200                    par.move_,
201                ));
202            }
203        }
204
205        (ret, callback_info)
206    }
207
208    pub fn type_for(env: &Env, type_id: TypeId) -> Option<BoundType> {
209        use self::BoundType::*;
210        match env.library.type_(type_id) {
211            Type::Basic(Basic::Filename | Basic::OsString) => Some(AsRef(None)),
212            Type::Class(Class {
213                is_fundamental: true,
214                ..
215            }) => Some(AsRef(None)),
216            Type::Class(Class {
217                final_type: true, ..
218            }) => None,
219            Type::Class(Class {
220                final_type: false, ..
221            }) => Some(IsA(None)),
222            Type::Interface(..) => Some(IsA(None)),
223            Type::List(_) | Type::SList(_) | Type::CArray(_) => None,
224            Type::Function(_) => Some(NoWrapper),
225            _ => None,
226        }
227    }
228
229    pub fn get_to_glib_extra(
230        bound_type: &BoundType,
231        nullable: bool,
232        instance_parameter: bool,
233        move_: bool,
234    ) -> String {
235        use self::BoundType::*;
236        match bound_type {
237            AsRef(_) if move_ && nullable => ".map(|p| p.as_ref().clone().upcast())".to_owned(),
238            AsRef(_) if nullable => ".as_ref().map(|p| p.as_ref())".to_owned(),
239            AsRef(_) if move_ => ".upcast()".to_owned(),
240            AsRef(_) => ".as_ref()".to_owned(),
241            IsA(_) if move_ && nullable => ".map(|p| p.upcast())".to_owned(),
242            IsA(_) if nullable && !instance_parameter => ".map(|p| p.as_ref())".to_owned(),
243            IsA(_) if move_ => ".upcast()".to_owned(),
244            IsA(_) => ".as_ref()".to_owned(),
245            _ => String::new(),
246        }
247    }
248
249    pub fn add_parameter(
250        &mut self,
251        name: &str,
252        type_str: &str,
253        mut bound_type: BoundType,
254        r#async: bool,
255    ) {
256        if r#async && name == "callback" {
257            bound_type = BoundType::NoWrapper;
258        }
259        if self.used.iter().any(|n| n.parameter_name == name) {
260            return;
261        }
262        let alias = bound_type
263            .has_alias()
264            .then(|| self.unused.pop_front().expect("No free type aliases!"));
265        self.used.push(Bound {
266            bound_type,
267            parameter_name: name.to_owned(),
268            alias,
269            type_str: type_str.to_owned(),
270            callback_modified: false,
271        });
272    }
273
274    pub fn get_parameter_bound(&self, name: &str) -> Option<&Bound> {
275        self.iter().find(move |n| n.parameter_name == name)
276    }
277
278    pub fn update_imports(&self, imports: &mut Imports) {
279        // TODO: import with versions
280        use self::BoundType::*;
281        for used in &self.used {
282            match used.bound_type {
283                NoWrapper => (),
284                IsA(_) => imports.add("glib::prelude::*"),
285                AsRef(_) => imports.add_used_type(&used.type_str),
286            }
287        }
288    }
289
290    pub fn is_empty(&self) -> bool {
291        self.used.is_empty()
292    }
293
294    pub fn iter(&self) -> Iter<'_, Bound> {
295        self.used.iter()
296    }
297
298    pub fn iter_lifetimes(&self) -> Iter<'_, char> {
299        self.lifetimes.iter()
300    }
301}
302
303#[derive(Clone, Debug)]
304pub struct PropertyBound {
305    pub alias: char,
306    pub type_str: String,
307}
308
309impl PropertyBound {
310    pub fn get(env: &Env, type_id: TypeId) -> Option<Self> {
311        let type_ = env.type_(type_id);
312        if type_.is_final_type() {
313            return None;
314        }
315        Some(Self {
316            alias: TYPE_PARAMETERS_START,
317            type_str: RustType::builder(env, type_id)
318                .ref_mode(RefMode::ByRefFake)
319                .try_build()
320                .into_string(),
321        })
322    }
323}
324
325fn find_out_parameters(
326    env: &Env,
327    function: &Function,
328    configured_functions: &[&config::functions::Function],
329) -> Vec<String> {
330    let index_to_ignore = find_index_to_ignore(&function.parameters, Some(&function.ret));
331    function
332        .parameters
333        .iter()
334        .enumerate()
335        .filter(|&(index, param)| {
336            Some(index) != index_to_ignore
337                && param.direction == ParameterDirection::Out
338                && param.name != "error"
339        })
340        .map(|(_, param)| {
341            // FIXME: This should work completely based on the analysis of the finish()
342            // function but that a) happens afterwards and b) is not accessible
343            // from here either.
344            let nullable = configured_functions
345                .iter()
346                .find_map(|f| {
347                    f.parameters
348                        .iter()
349                        .filter(|p| p.ident.is_match(&param.name))
350                        .find_map(|p| p.nullable)
351                })
352                .unwrap_or(param.nullable);
353
354            RustType::builder(env, param.typ)
355                .direction(param.direction)
356                .nullable(nullable)
357                .try_build()
358                .into_string()
359        })
360        .collect()
361}
362
363fn format_out_parameters(parameters: &[String]) -> String {
364    if parameters.len() == 1 {
365        parameters[0].to_string()
366    } else {
367        format!("({})", parameters.join(", "))
368    }
369}
370
371fn find_error_type(env: &Env, function: &Function) -> Option<String> {
372    let error_param = function
373        .parameters
374        .iter()
375        .find(|param| param.direction.is_out() && param.is_error)?;
376    if let Type::Record(_) = env.type_(error_param.typ) {
377        Some(
378            RustType::builder(env, error_param.typ)
379                .direction(error_param.direction)
380                .try_build()
381                .into_string(),
382        )
383    } else {
384        None
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn get_new_all() {
394        let mut bounds: Bounds = Default::default();
395        let typ = BoundType::IsA(None);
396        bounds.add_parameter("a", "", typ.clone(), false);
397        assert_eq!(bounds.iter().len(), 1);
398        // Don't add second time
399        bounds.add_parameter("a", "", typ.clone(), false);
400        assert_eq!(bounds.iter().len(), 1);
401        bounds.add_parameter("b", "", typ.clone(), false);
402        bounds.add_parameter("c", "", typ.clone(), false);
403        bounds.add_parameter("d", "", typ.clone(), false);
404        bounds.add_parameter("e", "", typ.clone(), false);
405        bounds.add_parameter("f", "", typ.clone(), false);
406        bounds.add_parameter("g", "", typ.clone(), false);
407        bounds.add_parameter("h", "", typ.clone(), false);
408        assert_eq!(bounds.iter().len(), 8);
409        bounds.add_parameter("h", "", typ.clone(), false);
410        assert_eq!(bounds.iter().len(), 8);
411        bounds.add_parameter("i", "", typ.clone(), false);
412        bounds.add_parameter("j", "", typ.clone(), false);
413        bounds.add_parameter("k", "", typ, false);
414    }
415
416    #[test]
417    #[should_panic(expected = "No free type aliases!")]
418    fn exhaust_type_parameters() {
419        let mut bounds: Bounds = Default::default();
420        let typ = BoundType::NoWrapper;
421        for c in 'a'..='l' {
422            // Should panic on `l` because all type parameters are exhausted
423            bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false);
424        }
425    }
426
427    #[test]
428    fn get_parameter_bound() {
429        let mut bounds: Bounds = Default::default();
430        let typ = BoundType::NoWrapper;
431        bounds.add_parameter("a", "", typ.clone(), false);
432        bounds.add_parameter("b", "", typ.clone(), false);
433        let bound = bounds.get_parameter_bound("a").unwrap();
434        // `NoWrapper `bounds are expected to have an alias:
435        assert_eq!(bound.alias, Some('P'));
436        assert_eq!(bound.bound_type, typ);
437        let bound = bounds.get_parameter_bound("b").unwrap();
438        assert_eq!(bound.alias, Some('Q'));
439        assert_eq!(bound.bound_type, typ);
440        assert_eq!(bounds.get_parameter_bound("c"), None);
441    }
442
443    #[test]
444    fn impl_bound() {
445        let mut bounds: Bounds = Default::default();
446        let typ = BoundType::IsA(None);
447        bounds.add_parameter("a", "", typ.clone(), false);
448        bounds.add_parameter("b", "", typ.clone(), false);
449        let bound = bounds.get_parameter_bound("a").unwrap();
450        // `IsA` is simplified to an inline `foo: impl IsA<Bar>` and
451        // lacks an alias/type-parameter:
452        assert_eq!(bound.alias, None);
453        assert_eq!(bound.bound_type, typ);
454
455        let typ = BoundType::AsRef(None);
456        bounds.add_parameter("c", "", typ.clone(), false);
457        let bound = bounds.get_parameter_bound("c").unwrap();
458        // Same `impl AsRef<Foo>` simplification as `IsA`:
459        assert_eq!(bound.alias, None);
460        assert_eq!(bound.bound_type, typ);
461    }
462}