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!(
105                func.new_name.is_none(),
106                "A `to_string` function can't be renamed manually. It's automatically renamed to `to_str`"
107            );
108            func.new_name = Some("to_str".to_owned());
109
110            // As to not change old code behaviour, assume non-nullability outside
111            // enums and flags only, and exclusively for to_string. Function inside
112            // enums and flags have been appropriately marked in Gir.
113            if !obj.trust_return_value_nullability
114                && !matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
115            {
116                *ret.lib_par.nullable = false;
117            }
118        }
119
120        // Cannot generate Display implementation for Option<>
121        !*ret.lib_par.nullable
122    } else {
123        false
124    }
125}
126
127fn update_func(func: &mut FuncInfo, type_: Type) -> bool {
128    if !func.commented {
129        use self::Type::*;
130        match type_ {
131            Copy | Free | Ref | Unref => func.hidden = true,
132            Hash | Compare | Equal => func.visibility = Visibility::Private,
133            Display => func.visibility = Visibility::Public,
134        };
135    }
136    true
137}
138
139pub fn extract(functions: &mut [FuncInfo], parent_type: &LibType, obj: &GObject) -> Infos {
140    let mut specials = Infos::default();
141    let mut has_copy = false;
142    let mut has_free = false;
143    let mut destroy = None;
144
145    for (pos, func) in functions.iter_mut().enumerate() {
146        if is_stringify(func, parent_type, obj) {
147            let return_transfer_none = func
148                .ret
149                .parameter
150                .as_ref()
151                .is_some_and(|ret| ret.lib_par.transfer == crate::library::Transfer::None);
152
153            // Assume only enumerations and bitfields can return static strings
154            let returns_static_ref = return_transfer_none
155                && matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
156                // We cannot mandate returned lifetime if this is not generated.
157                // (And this prevents an unused glib::GStr from being emitted below)
158                && func.status.need_generate();
159
160            if returns_static_ref {
161                // Override the function with a &'static (non allocating) -returning string
162                // if the transfer type is none and it matches the above heuristics.
163                specials.functions.insert(
164                    func.glib_name.clone(),
165                    FunctionInfo {
166                        type_: FunctionType::StaticStringify,
167                        version: func.version,
168                    },
169                );
170            }
171
172            // Some stringifying functions can serve as Display implementation
173            if matches!(
174                func.name.as_str(),
175                "to_string" | "to_str" | "name" | "get_name"
176            ) {
177                // FUTURE: Decide which function gets precedence if multiple Display prospects
178                // exist.
179                specials.traits.insert(
180                    Type::Display,
181                    TraitInfo {
182                        glib_name: func.glib_name.clone(),
183                        version: func.version,
184                        first_parameter_mut: false,
185                    },
186                );
187            }
188        } else if let Ok(type_) = func.name.parse() {
189            if func.name == "destroy" {
190                destroy = Some((func.glib_name.clone(), pos));
191                continue;
192            }
193            if !update_func(func, type_) {
194                continue;
195            }
196            if func.name == "copy" {
197                has_copy = true;
198            } else if func.name == "free" {
199                has_free = true;
200            }
201
202            let first_parameter_mut = func
203                .parameters
204                .c_parameters
205                .first()
206                .is_some_and(|p| p.ref_mode == super::ref_mode::RefMode::ByRefMut);
207
208            specials.traits.insert(
209                type_,
210                TraitInfo {
211                    glib_name: func.glib_name.clone(),
212                    version: func.version,
213                    first_parameter_mut,
214                },
215            );
216        }
217    }
218
219    if has_copy
220        && !has_free
221        && let Some((glib_name, pos)) = destroy
222    {
223        let ty_ = Type::from_str("destroy").unwrap();
224        let func = &mut functions[pos];
225        update_func(func, ty_);
226        specials.traits.insert(
227            ty_,
228            TraitInfo {
229                glib_name,
230                version: func.version,
231                first_parameter_mut: true,
232            },
233        );
234    }
235
236    specials
237}
238
239// Some special functions (e.g. `copy` on refcounted types) should be exposed
240pub fn unhide(functions: &mut [FuncInfo], specials: &Infos, type_: Type) {
241    if let Some(func) = specials.traits().get(&type_) {
242        let func = functions
243            .iter_mut()
244            .find(|f| f.glib_name == func.glib_name && !f.commented);
245        if let Some(func) = func {
246            func.visibility = Visibility::Public;
247            func.hidden = false;
248        }
249    }
250}
251
252pub fn analyze_imports(specials: &Infos, imports: &mut Imports) {
253    for (type_, info) in specials.traits() {
254        use self::Type::*;
255        match type_ {
256            Copy if info.first_parameter_mut => {
257                imports.add_with_version("glib::translate::*", info.version);
258            }
259            Compare => {
260                imports.add_with_version("glib::translate::*", info.version);
261            }
262            Equal => imports.add_with_version("glib::translate::*", info.version),
263            _ => {}
264        }
265    }
266    for info in specials.functions().values() {
267        match info.type_ {
268            FunctionType::StaticStringify => {
269                imports.add_with_version("glib::GStr", info.version);
270            }
271        }
272    }
273}