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