libgir/analysis/
special_functions.rs

1use std::{collections::BTreeMap, str::FromStr};
2
3use crate::{
4    analysis::{functions::Info as FuncInfo, imports::Imports},
5    codegen::Visibility,
6    config::GObject,
7    library::{Type as LibType, TypeId},
8    version::Version,
9};
10
11#[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)]
12pub enum Type {
13    Compare,
14    Copy,
15    Equal,
16    Free,
17    Ref,
18    Display,
19    Unref,
20    Hash,
21}
22
23impl FromStr for Type {
24    type Err = String;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        match s {
28            "compare" => Ok(Self::Compare),
29            "copy" => Ok(Self::Copy),
30            "equal" => Ok(Self::Equal),
31            "free" | "destroy" => Ok(Self::Free),
32            "is_equal" => Ok(Self::Equal),
33            "ref" | "ref_" => Ok(Self::Ref),
34            "unref" => Ok(Self::Unref),
35            "hash" => Ok(Self::Hash),
36            _ => Err(format!("Unknown type '{s}'")),
37        }
38    }
39}
40
41#[derive(Debug, Clone)]
42pub struct TraitInfo {
43    pub glib_name: String,
44    pub version: Option<Version>,
45    pub first_parameter_mut: bool,
46}
47
48type TraitInfos = BTreeMap<Type, TraitInfo>;
49
50#[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)]
51pub enum FunctionType {
52    StaticStringify,
53}
54
55#[derive(Debug, Clone)]
56pub struct FunctionInfo {
57    pub type_: FunctionType,
58    pub version: Option<Version>,
59}
60
61type FunctionInfos = BTreeMap<String, FunctionInfo>;
62
63#[derive(Debug, Default)]
64pub struct Infos {
65    traits: TraitInfos,
66    functions: FunctionInfos,
67}
68
69impl Infos {
70    pub fn traits(&self) -> &TraitInfos {
71        &self.traits
72    }
73
74    pub fn traits_mut(&mut self) -> &mut TraitInfos {
75        &mut self.traits
76    }
77
78    pub fn has_trait(&self, type_: Type) -> bool {
79        self.traits.contains_key(&type_)
80    }
81
82    pub fn functions(&self) -> &FunctionInfos {
83        &self.functions
84    }
85}
86
87/// Returns true on functions that take an instance as single argument and
88/// return a string as result.
89fn is_stringify(func: &mut FuncInfo, parent_type: &LibType, obj: &GObject) -> bool {
90    if func.parameters.c_parameters.len() != 1 {
91        return false;
92    }
93    if !func.parameters.c_parameters[0].instance_parameter {
94        return false;
95    }
96
97    if let Some(ret) = func.ret.parameter.as_mut() {
98        if ret.lib_par.typ != TypeId::tid_utf8() {
99            return false;
100        }
101
102        if func.name == "to_string" {
103            // Rename to to_str to make sure it doesn't clash with ToString::to_string
104            assert!(func.new_name.is_none(), "A `to_string` function can't be renamed manually. It's automatically renamed to `to_str`");
105            func.new_name = Some("to_str".to_owned());
106
107            // As to not change old code behaviour, assume non-nullability outside
108            // enums and flags only, and exclusively for to_string. Function inside
109            // enums and flags have been appropriately marked in Gir.
110            if !obj.trust_return_value_nullability
111                && !matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
112            {
113                *ret.lib_par.nullable = false;
114            }
115        }
116
117        // Cannot generate Display implementation for Option<>
118        !*ret.lib_par.nullable
119    } else {
120        false
121    }
122}
123
124fn update_func(func: &mut FuncInfo, type_: Type) -> bool {
125    if !func.commented {
126        use self::Type::*;
127        match type_ {
128            Copy | Free | Ref | Unref => func.hidden = true,
129            Hash | Compare | Equal => func.visibility = Visibility::Private,
130            Display => func.visibility = Visibility::Public,
131        };
132    }
133    true
134}
135
136pub fn extract(functions: &mut [FuncInfo], parent_type: &LibType, obj: &GObject) -> Infos {
137    let mut specials = Infos::default();
138    let mut has_copy = false;
139    let mut has_free = false;
140    let mut destroy = None;
141
142    for (pos, func) in functions.iter_mut().enumerate() {
143        if is_stringify(func, parent_type, obj) {
144            let return_transfer_none = func
145                .ret
146                .parameter
147                .as_ref()
148                .is_some_and(|ret| ret.lib_par.transfer == crate::library::Transfer::None);
149
150            // Assume only enumerations and bitfields can return static strings
151            let returns_static_ref = return_transfer_none
152                && matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
153                // We cannot mandate returned lifetime if this is not generated.
154                // (And this prevents an unused glib::GStr from being emitted below)
155                && func.status.need_generate();
156
157            if returns_static_ref {
158                // Override the function with a &'static (non allocating) -returning string
159                // if the transfer type is none and it matches the above heuristics.
160                specials.functions.insert(
161                    func.glib_name.clone(),
162                    FunctionInfo {
163                        type_: FunctionType::StaticStringify,
164                        version: func.version,
165                    },
166                );
167            }
168
169            // Some stringifying functions can serve as Display implementation
170            if matches!(
171                func.name.as_str(),
172                "to_string" | "to_str" | "name" | "get_name"
173            ) {
174                // FUTURE: Decide which function gets precedence if multiple Display prospects
175                // exist.
176                specials.traits.insert(
177                    Type::Display,
178                    TraitInfo {
179                        glib_name: func.glib_name.clone(),
180                        version: func.version,
181                        first_parameter_mut: false,
182                    },
183                );
184            }
185        } else if let Ok(type_) = func.name.parse() {
186            if func.name == "destroy" {
187                destroy = Some((func.glib_name.clone(), pos));
188                continue;
189            }
190            if !update_func(func, type_) {
191                continue;
192            }
193            if func.name == "copy" {
194                has_copy = true;
195            } else if func.name == "free" {
196                has_free = true;
197            }
198
199            let first_parameter_mut = func
200                .parameters
201                .c_parameters
202                .first()
203                .is_some_and(|p| p.ref_mode == super::ref_mode::RefMode::ByRefMut);
204
205            specials.traits.insert(
206                type_,
207                TraitInfo {
208                    glib_name: func.glib_name.clone(),
209                    version: func.version,
210                    first_parameter_mut,
211                },
212            );
213        }
214    }
215
216    if has_copy && !has_free {
217        if let Some((glib_name, pos)) = destroy {
218            let ty_ = Type::from_str("destroy").unwrap();
219            let func = &mut functions[pos];
220            update_func(func, ty_);
221            specials.traits.insert(
222                ty_,
223                TraitInfo {
224                    glib_name,
225                    version: func.version,
226                    first_parameter_mut: true,
227                },
228            );
229        }
230    }
231
232    specials
233}
234
235// Some special functions (e.g. `copy` on refcounted types) should be exposed
236pub fn unhide(functions: &mut [FuncInfo], specials: &Infos, type_: Type) {
237    if let Some(func) = specials.traits().get(&type_) {
238        let func = functions
239            .iter_mut()
240            .find(|f| f.glib_name == func.glib_name && !f.commented);
241        if let Some(func) = func {
242            func.visibility = Visibility::Public;
243            func.hidden = false;
244        }
245    }
246}
247
248pub fn analyze_imports(specials: &Infos, imports: &mut Imports) {
249    for (type_, info) in specials.traits() {
250        use self::Type::*;
251        match type_ {
252            Copy if info.first_parameter_mut => {
253                imports.add_with_version("glib::translate::*", info.version);
254            }
255            Compare => {
256                imports.add_with_version("glib::translate::*", info.version);
257            }
258            Equal => imports.add_with_version("glib::translate::*", info.version),
259            _ => {}
260        }
261    }
262    for info in specials.functions().values() {
263        match info.type_ {
264            FunctionType::StaticStringify => {
265                imports.add_with_version("glib::GStr", info.version);
266            }
267        }
268    }
269}