1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
use std::{collections::BTreeMap, str::FromStr};

use crate::{
    analysis::{functions::Info as FuncInfo, imports::Imports},
    codegen::Visibility,
    config::GObject,
    library::{Type as LibType, TypeId},
    version::Version,
};

#[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)]
pub enum Type {
    Compare,
    Copy,
    Equal,
    Free,
    Ref,
    Display,
    Unref,
    Hash,
}

impl FromStr for Type {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "compare" => Ok(Self::Compare),
            "copy" => Ok(Self::Copy),
            "equal" => Ok(Self::Equal),
            "free" | "destroy" => Ok(Self::Free),
            "is_equal" => Ok(Self::Equal),
            "ref" | "ref_" => Ok(Self::Ref),
            "unref" => Ok(Self::Unref),
            "hash" => Ok(Self::Hash),
            _ => Err(format!("Unknown type '{s}'")),
        }
    }
}

#[derive(Debug, Clone)]
pub struct TraitInfo {
    pub glib_name: String,
    pub version: Option<Version>,
    pub first_parameter_mut: bool,
}

type TraitInfos = BTreeMap<Type, TraitInfo>;

#[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)]
pub enum FunctionType {
    StaticStringify,
}

#[derive(Debug, Clone)]
pub struct FunctionInfo {
    pub type_: FunctionType,
    pub version: Option<Version>,
}

type FunctionInfos = BTreeMap<String, FunctionInfo>;

#[derive(Debug, Default)]
pub struct Infos {
    traits: TraitInfos,
    functions: FunctionInfos,
}

impl Infos {
    pub fn traits(&self) -> &TraitInfos {
        &self.traits
    }

    pub fn traits_mut(&mut self) -> &mut TraitInfos {
        &mut self.traits
    }

    pub fn has_trait(&self, type_: Type) -> bool {
        self.traits.contains_key(&type_)
    }

    pub fn functions(&self) -> &FunctionInfos {
        &self.functions
    }
}

/// Returns true on functions that take an instance as single argument and
/// return a string as result.
fn is_stringify(func: &mut FuncInfo, parent_type: &LibType, obj: &GObject) -> bool {
    if func.parameters.c_parameters.len() != 1 {
        return false;
    }
    if !func.parameters.c_parameters[0].instance_parameter {
        return false;
    }

    if let Some(ret) = func.ret.parameter.as_mut() {
        if ret.lib_par.typ != TypeId::tid_utf8() {
            return false;
        }

        if func.name == "to_string" {
            // Rename to to_str to make sure it doesn't clash with ToString::to_string
            assert!(func.new_name.is_none(), "A `to_string` function can't be renamed manually. It's automatically renamed to `to_str`");
            func.new_name = Some("to_str".to_owned());

            // As to not change old code behaviour, assume non-nullability outside
            // enums and flags only, and exclusively for to_string. Function inside
            // enums and flags have been appropriately marked in Gir.
            if !obj.trust_return_value_nullability
                && !matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
            {
                *ret.lib_par.nullable = false;
            }
        }

        // Cannot generate Display implementation for Option<>
        !*ret.lib_par.nullable
    } else {
        false
    }
}

fn update_func(func: &mut FuncInfo, type_: Type) -> bool {
    if !func.commented {
        use self::Type::*;
        match type_ {
            Copy | Free | Ref | Unref => func.hidden = true,
            Hash | Compare | Equal => func.visibility = Visibility::Private,
            Display => func.visibility = Visibility::Public,
        };
    }
    true
}

pub fn extract(functions: &mut [FuncInfo], parent_type: &LibType, obj: &GObject) -> Infos {
    let mut specials = Infos::default();
    let mut has_copy = false;
    let mut has_free = false;
    let mut destroy = None;

    for (pos, func) in functions.iter_mut().enumerate() {
        if is_stringify(func, parent_type, obj) {
            let return_transfer_none = func
                .ret
                .parameter
                .as_ref()
                .is_some_and(|ret| ret.lib_par.transfer == crate::library::Transfer::None);

            // Assume only enumerations and bitfields can return static strings
            let returns_static_ref = return_transfer_none
                && matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_))
                // We cannot mandate returned lifetime if this is not generated.
                // (And this prevents an unused glib::GStr from being emitted below)
                && func.status.need_generate();

            if returns_static_ref {
                // Override the function with a &'static (non allocating) -returning string
                // if the transfer type is none and it matches the above heuristics.
                specials.functions.insert(
                    func.glib_name.clone(),
                    FunctionInfo {
                        type_: FunctionType::StaticStringify,
                        version: func.version,
                    },
                );
            }

            // Some stringifying functions can serve as Display implementation
            if matches!(
                func.name.as_str(),
                "to_string" | "to_str" | "name" | "get_name"
            ) {
                // FUTURE: Decide which function gets precedence if multiple Display prospects
                // exist.
                specials.traits.insert(
                    Type::Display,
                    TraitInfo {
                        glib_name: func.glib_name.clone(),
                        version: func.version,
                        first_parameter_mut: false,
                    },
                );
            }
        } else if let Ok(type_) = func.name.parse() {
            if func.name == "destroy" {
                destroy = Some((func.glib_name.clone(), pos));
                continue;
            }
            if !update_func(func, type_) {
                continue;
            }
            if func.name == "copy" {
                has_copy = true;
            } else if func.name == "free" {
                has_free = true;
            }

            let first_parameter_mut = func
                .parameters
                .c_parameters
                .first()
                .is_some_and(|p| p.ref_mode == super::ref_mode::RefMode::ByRefMut);

            specials.traits.insert(
                type_,
                TraitInfo {
                    glib_name: func.glib_name.clone(),
                    version: func.version,
                    first_parameter_mut,
                },
            );
        }
    }

    if has_copy && !has_free {
        if let Some((glib_name, pos)) = destroy {
            let ty_ = Type::from_str("destroy").unwrap();
            let func = &mut functions[pos];
            update_func(func, ty_);
            specials.traits.insert(
                ty_,
                TraitInfo {
                    glib_name,
                    version: func.version,
                    first_parameter_mut: true,
                },
            );
        }
    }

    specials
}

// Some special functions (e.g. `copy` on refcounted types) should be exposed
pub fn unhide(functions: &mut [FuncInfo], specials: &Infos, type_: Type) {
    if let Some(func) = specials.traits().get(&type_) {
        let func = functions
            .iter_mut()
            .find(|f| f.glib_name == func.glib_name && !f.commented);
        if let Some(func) = func {
            func.visibility = Visibility::Public;
            func.hidden = false;
        }
    }
}

pub fn analyze_imports(specials: &Infos, imports: &mut Imports) {
    for (type_, info) in specials.traits() {
        use self::Type::*;
        match type_ {
            Copy if info.first_parameter_mut => {
                imports.add_with_version("glib::translate::*", info.version);
            }
            Compare => {
                imports.add_with_version("glib::translate::*", info.version);
            }
            Equal => imports.add_with_version("glib::translate::*", info.version),
            _ => {}
        }
    }
    for info in specials.functions().values() {
        match info.type_ {
            FunctionType::StaticStringify => {
                imports.add_with_version("glib::GStr", info.version);
            }
        }
    }
}