libgir/analysis/
record.rs

1use std::ops::Deref;
2
3use log::info;
4
5use super::{imports::Imports, info_base::InfoBase, record_type::RecordType, *};
6use crate::{
7    config::{
8        derives::{Derive, Derives},
9        gobjects::GObject,
10    },
11    env::Env,
12    library,
13    nameutil::*,
14    traits::*,
15    version::Version,
16};
17
18#[derive(Debug, Default)]
19pub struct Info {
20    pub base: InfoBase,
21    pub glib_get_type: Option<(String, Option<Version>)>,
22    pub is_boxed: bool,
23    pub derives: Derives,
24    pub boxed_inline: bool,
25    pub init_function_expression: Option<String>,
26    pub copy_into_function_expression: Option<String>,
27    pub clear_function_expression: Option<String>,
28}
29
30impl Deref for Info {
31    type Target = InfoBase;
32
33    fn deref(&self) -> &InfoBase {
34        &self.base
35    }
36}
37
38impl Info {
39    // TODO: add test in tests/ for panic
40    pub fn type_<'a>(&self, library: &'a library::Library) -> &'a library::Record {
41        let type_ = library
42            .type_(self.type_id)
43            .maybe_ref()
44            .unwrap_or_else(|| panic!("{} is not a record.", self.full_name));
45        type_
46    }
47}
48
49fn filter_derives(derives: &[Derive], names: &[&str]) -> Derives {
50    derives
51        .iter()
52        .filter_map(|derive| {
53            let new_names = derive
54                .names
55                .iter()
56                .filter(|n| !names.contains(&n.as_str()))
57                .map(Clone::clone)
58                .collect::<Vec<_>>();
59
60            if !new_names.is_empty() {
61                Some(Derive {
62                    names: new_names,
63                    cfg_condition: derive.cfg_condition.clone(),
64                })
65            } else {
66                None
67            }
68        })
69        .collect()
70}
71
72pub fn new(env: &Env, obj: &GObject) -> Option<Info> {
73    info!("Analyzing record {}", obj.name);
74    let full_name = obj.name.clone();
75
76    let record_tid = env.library.find_type(0, &full_name)?;
77
78    let type_ = env.type_(record_tid);
79
80    let name: String = split_namespace_name(&full_name).1.into();
81
82    let record: &library::Record = type_.maybe_ref()?;
83
84    let is_boxed = matches!(
85        RecordType::of(record),
86        RecordType::Boxed | RecordType::AutoBoxed
87    );
88    let boxed_inline = obj.boxed_inline;
89
90    let mut imports = Imports::with_defined(&env.library, &name);
91    imports.add("crate::ffi");
92
93    let mut functions = functions::analyze(
94        env,
95        &record.functions,
96        Some(record_tid),
97        false,
98        is_boxed,
99        obj,
100        &mut imports,
101        None,
102        None,
103    );
104    let specials = special_functions::extract(&mut functions, type_, obj);
105
106    let version = obj.version.or(record.version);
107    let deprecated_version = record.deprecated_version;
108
109    let is_shared = specials.has_trait(special_functions::Type::Ref)
110        && specials.has_trait(special_functions::Type::Unref);
111    if is_shared {
112        // `copy` will duplicate a struct while `clone` just adds a reference
113        special_functions::unhide(&mut functions, &specials, special_functions::Type::Copy);
114    };
115
116    let mut derives = if let Some(ref derives) = obj.derives {
117        if boxed_inline
118            && !derives.is_empty()
119            && !derives
120                .iter()
121                .all(|ds| ds.names.is_empty() || ds.names.iter().all(|n| n == "Debug"))
122        {
123            panic!("Can't automatically derive traits other than `Debug` for BoxedInline records");
124        }
125        derives.clone()
126    } else if !boxed_inline {
127        let derives = vec![Derive {
128            names: vec![
129                "Debug".into(),
130                "PartialEq".into(),
131                "Eq".into(),
132                "PartialOrd".into(),
133                "Ord".into(),
134                "Hash".into(),
135            ],
136            cfg_condition: None,
137        }];
138
139        derives
140    } else {
141        // boxed_inline
142        vec![]
143    };
144
145    for special in specials.traits().keys() {
146        match special {
147            special_functions::Type::Compare => {
148                derives = filter_derives(&derives, &["PartialOrd", "Ord", "PartialEq", "Eq"]);
149            }
150            special_functions::Type::Equal => {
151                derives = filter_derives(&derives, &["PartialEq", "Eq"]);
152            }
153            special_functions::Type::Hash => {
154                derives = filter_derives(&derives, &["Hash"]);
155            }
156            _ => (),
157        }
158    }
159
160    special_functions::analyze_imports(&specials, &mut imports);
161
162    let glib_get_type = if let Some(ref glib_get_type) = record.glib_get_type {
163        let configured_functions = obj.functions.matched("get_type");
164        let get_type_version = configured_functions
165            .iter()
166            .map(|f| f.version)
167            .max()
168            .flatten();
169
170        Some((glib_get_type.clone(), get_type_version))
171    } else {
172        None
173    };
174
175    // Check if we have to make use of the GType and the generic
176    // boxed functions.
177    if !is_shared
178        && (!specials.has_trait(special_functions::Type::Copy)
179            || !specials.has_trait(special_functions::Type::Free))
180    {
181        if let Some((_, get_type_version)) = glib_get_type {
182            // FIXME: Ideally we would update it here but that requires fixing *all* the
183            // versions of functions in this and other types that use this type somewhere in
184            // the signature. Similar code exists for before the analysis already but that
185            // doesn't apply directly here.
186            //
187            // As the get_type function only has a version if explicitly configured let's
188            // just panic here. It's easy enough for the user to move the
189            // version configuration from the function to the type.
190            assert!(
191                get_type_version <= version,
192                "Have to use get_type function for {full_name} but version is higher than for the type ({get_type_version:?} > {version:?})"
193            );
194        } else {
195            error!("Missing memory management functions for {}", full_name);
196        }
197    }
198
199    let base = InfoBase {
200        full_name,
201        type_id: record_tid,
202        name,
203        functions,
204        specials,
205        imports,
206        version,
207        deprecated_version,
208        cfg_condition: obj.cfg_condition.clone(),
209        concurrency: obj.concurrency,
210        visibility: obj.visibility,
211    };
212
213    let info = Info {
214        base,
215        glib_get_type,
216        derives,
217        is_boxed,
218        boxed_inline,
219        init_function_expression: obj.init_function_expression.clone(),
220        copy_into_function_expression: obj.copy_into_function_expression.clone(),
221        clear_function_expression: obj.clear_function_expression.clone(),
222    };
223
224    Some(info)
225}