libgir/analysis/
properties.rs

1use log::warn;
2
3use crate::{
4    analysis::{
5        bounds::{Bounds, PropertyBound},
6        imports::Imports,
7        ref_mode::RefMode,
8        rust_type::RustType,
9        signals,
10        signatures::{Signature, Signatures},
11        trampolines,
12    },
13    config::{self, gobjects::GStatus, GObject, PropertyGenerateFlags},
14    env::Env,
15    library, nameutil,
16    traits::*,
17    version::Version,
18};
19
20#[derive(Debug)]
21pub struct Property {
22    pub name: String,
23    pub var_name: String,
24    pub typ: library::TypeId,
25    pub is_get: bool,
26    pub func_name: String,
27    pub func_name_alias: Option<String>,
28    pub nullable: library::Nullable,
29    pub get_out_ref_mode: RefMode,
30    pub set_in_ref_mode: RefMode,
31    pub bounds: Bounds,
32    pub set_bound: Option<PropertyBound>,
33    pub version: Option<Version>,
34    pub deprecated_version: Option<Version>,
35}
36
37pub fn analyze(
38    env: &Env,
39    props: &[library::Property],
40    supertypes_props: &[&library::Property],
41    type_tid: library::TypeId,
42    generate_trait: bool,
43    is_fundamental: bool,
44    obj: &GObject,
45    imports: &mut Imports,
46    signatures: &Signatures,
47    deps: &[library::TypeId],
48    functions: &[crate::analysis::functions::Info],
49) -> (Vec<Property>, Vec<signals::Info>) {
50    let mut properties = Vec::new();
51    let mut notify_signals = Vec::new();
52
53    for prop in props {
54        let configured_properties = obj.properties.matched(&prop.name);
55        if !configured_properties
56            .iter()
57            .all(|f| f.status.need_generate())
58        {
59            continue;
60        }
61
62        if env.is_totally_deprecated(Some(type_tid.ns_id), prop.deprecated_version) {
63            continue;
64        }
65
66        if supertypes_props
67            .iter()
68            .any(|p| p.name == prop.name && p.typ == prop.typ)
69        {
70            continue;
71        }
72
73        let (getter, setter, notify_signal) = analyze_property(
74            env,
75            prop,
76            type_tid,
77            &configured_properties,
78            generate_trait,
79            is_fundamental,
80            obj,
81            imports,
82            signatures,
83            deps,
84            functions,
85        );
86
87        if let Some(notify_signal) = notify_signal {
88            notify_signals.push(notify_signal);
89        }
90
91        if let Some(prop) = getter {
92            properties.push(prop);
93        }
94        if let Some(prop) = setter {
95            properties.push(prop);
96        }
97    }
98
99    (properties, notify_signals)
100}
101
102fn analyze_property(
103    env: &Env,
104    prop: &library::Property,
105    type_tid: library::TypeId,
106    configured_properties: &[&config::properties::Property],
107    generate_trait: bool,
108    is_fundamental: bool,
109    obj: &GObject,
110    imports: &mut Imports,
111    signatures: &Signatures,
112    deps: &[library::TypeId],
113    functions: &[crate::analysis::functions::Info],
114) -> (Option<Property>, Option<Property>, Option<signals::Info>) {
115    let type_name = type_tid.full_name(&env.library);
116    let name = prop.name.clone();
117
118    let prop_version = configured_properties
119        .iter()
120        .filter_map(|f| f.version)
121        .min()
122        .or(prop.version)
123        .or(Some(env.config.min_cfg_version));
124    let generate = configured_properties.iter().find_map(|f| f.generate);
125    let generate_set = generate.is_some();
126    let generate = generate.unwrap_or_else(PropertyGenerateFlags::all);
127
128    let imports = &mut imports.with_defaults(prop_version, &None);
129    imports.add("glib::translate::*");
130
131    let type_string = RustType::try_new(env, prop.typ);
132    let name_for_func = nameutil::signal_to_snake(&name);
133
134    let mut get_prop_name = Some(format!("get_property_{name_for_func}"));
135
136    let bypass_auto_rename = configured_properties.iter().any(|f| f.bypass_auto_rename);
137    let (check_get_func_names, mut get_func_name) = if bypass_auto_rename {
138        (
139            vec![format!("get_{name_for_func}")],
140            get_prop_name.take().expect("defined 10 lines above"),
141        )
142    } else {
143        get_func_name(&name_for_func, prop.typ == library::TypeId::tid_bool())
144    };
145
146    let mut set_func_name = format!("set_{name_for_func}");
147    let mut set_prop_name = Some(format!("set_property_{name_for_func}"));
148
149    let mut readable = prop.readable;
150    let mut writable = if prop.construct_only {
151        false
152    } else {
153        prop.writable
154    };
155    let mut notifiable = !prop.construct_only;
156    if generate_set && generate.contains(PropertyGenerateFlags::GET) && !readable {
157        warn!(
158            "Attempt to generate getter for notreadable property \"{}.{}\"",
159            type_name, name
160        );
161    }
162    if generate_set && generate.contains(PropertyGenerateFlags::SET) && !writable {
163        warn!(
164            "Attempt to generate setter for nonwritable property \"{}.{}\"",
165            type_name, name
166        );
167    }
168    readable &= generate.contains(PropertyGenerateFlags::GET);
169    writable &= generate.contains(PropertyGenerateFlags::SET);
170    if generate_set {
171        notifiable = generate.contains(PropertyGenerateFlags::NOTIFY);
172    }
173
174    if readable {
175        for check_get_func_name in check_get_func_names {
176            let (has, version) = Signature::has_for_property(
177                env,
178                &check_get_func_name,
179                true,
180                prop.typ,
181                signatures,
182                deps,
183            );
184            if has {
185                // There is a matching get func
186                if env.is_totally_deprecated(Some(type_tid.ns_id), version)
187                    || version <= prop_version
188                {
189                    // And its availability covers the property's availability
190                    // => don't generate the get property.
191                    readable = false;
192                } else {
193                    // The property is required in earlier versions than the getter
194                    // => we need both, but there will be a name clash due to auto renaming
195                    // => keep the get_property name.
196                    if let Some(get_prop_name) = get_prop_name.take() {
197                        get_func_name = get_prop_name;
198                    }
199                }
200            }
201        }
202    }
203    if writable {
204        let (has, version) =
205            Signature::has_for_property(env, &set_func_name, false, prop.typ, signatures, deps);
206        if has {
207            // There is a matching set func
208            if env.is_totally_deprecated(Some(type_tid.ns_id), version) || version <= prop_version {
209                // And its availability covers the property's availability
210                // => don't generate the set property.
211                writable = false;
212            } else {
213                // The property is required in earlier versions than the setter
214                // => we need both, but there will be a name clash due to auto renaming
215                // => keep the set_property name.
216                if let Some(set_prop_name) = set_prop_name.take() {
217                    set_func_name = set_prop_name;
218                }
219            }
220        }
221    }
222
223    let (get_out_ref_mode, set_in_ref_mode, nullable) = get_property_ref_modes(env, prop);
224
225    let getter_func = functions.iter().find(|f| {
226        f.get_property.as_ref() == Some(&prop.name) && Some(&f.name) == prop.getter.as_ref()
227    });
228    let setter_func = functions.iter().find(|f| {
229        f.set_property.as_ref() == Some(&prop.name) && Some(&f.name) == prop.setter.as_ref()
230    });
231
232    let has_getter =
233        getter_func.is_some_and(|g| matches!(g.status, GStatus::Generate | GStatus::Manual));
234    let has_setter =
235        setter_func.is_some_and(|s| matches!(s.status, GStatus::Generate | GStatus::Manual));
236
237    let getter = if readable && (!has_getter || prop.version < getter_func.and_then(|g| g.version))
238    {
239        if let Ok(rust_type) = RustType::builder(env, prop.typ)
240            .direction(library::ParameterDirection::Out)
241            .try_build()
242        {
243            imports.add_used_types(rust_type.used_types());
244        }
245        if type_string.is_ok() {
246            imports.add("glib::prelude::*");
247        }
248
249        let mut getter_version = prop_version;
250        if has_getter {
251            let getter = getter_func.unwrap();
252            get_func_name = getter.new_name.as_ref().unwrap_or(&getter.name).to_string();
253            get_prop_name = Some(getter.name.clone());
254            getter_version = getter.version.map(|mut g| {
255                g.as_opposite();
256                g
257            });
258        }
259
260        Some(Property {
261            name: name.clone(),
262            var_name: nameutil::mangle_keywords(&*name_for_func).into_owned(),
263            typ: prop.typ,
264            is_get: true,
265            func_name: get_func_name,
266            func_name_alias: get_prop_name,
267            nullable,
268            get_out_ref_mode,
269            set_in_ref_mode,
270            set_bound: None,
271            bounds: Bounds::default(),
272            version: getter_version,
273            deprecated_version: prop.deprecated_version,
274        })
275    } else {
276        None
277    };
278
279    let setter = if writable && (!has_setter || prop.version < setter_func.and_then(|s| s.version))
280    {
281        if let Ok(rust_type) = RustType::builder(env, prop.typ)
282            .direction(library::ParameterDirection::In)
283            .try_build()
284        {
285            imports.add_used_types(rust_type.used_types());
286        }
287        if type_string.is_ok() {
288            imports.add("glib::prelude::*");
289        }
290        let set_bound = PropertyBound::get(env, prop.typ);
291        if type_string.is_ok() && set_bound.is_some() {
292            imports.add("glib::prelude::*");
293            if !*nullable {
294                // TODO: support non-nullable setter if found any
295                warn!(
296                    "Non nullable setter for property generated as nullable \"{}.{}\"",
297                    type_name, name
298                );
299            }
300        }
301
302        let mut setter_version = prop_version;
303        if has_setter {
304            let setter = setter_func.unwrap();
305            set_func_name = setter.new_name.as_ref().unwrap_or(&setter.name).to_string();
306            set_prop_name = Some(setter.name.clone());
307            setter_version = setter.version.map(|mut s| {
308                s.as_opposite();
309                s
310            });
311        }
312
313        Some(Property {
314            name: name.clone(),
315            var_name: nameutil::mangle_keywords(&*name_for_func).into_owned(),
316            typ: prop.typ,
317            is_get: false,
318            func_name: set_func_name,
319            func_name_alias: set_prop_name,
320            nullable,
321            get_out_ref_mode,
322            set_in_ref_mode,
323            set_bound,
324            bounds: Bounds::default(),
325            version: setter_version,
326            deprecated_version: prop.deprecated_version,
327        })
328    } else {
329        None
330    };
331
332    if !generate_trait && (writable || readable || notifiable) {
333        imports.add("glib::prelude::*");
334    }
335
336    let notify_signal = if notifiable {
337        let mut used_types: Vec<String> = Vec::with_capacity(4);
338        let trampoline = trampolines::analyze(
339            env,
340            &library::Signal {
341                name: format!("notify::{name}"),
342                parameters: Vec::new(),
343                ret: library::Parameter {
344                    name: String::new(),
345                    typ: env
346                        .library
347                        .find_type(library::INTERNAL_NAMESPACE, "none")
348                        .unwrap(),
349                    c_type: "none".into(),
350                    instance_parameter: false,
351                    direction: library::ParameterDirection::Return,
352                    transfer: library::Transfer::None,
353                    caller_allocates: false,
354                    nullable: library::Nullable(false),
355                    array_length: None,
356                    is_error: false,
357                    doc: None,
358                    scope: library::ParameterScope::None,
359                    closure: None,
360                    destroy: None,
361                },
362                is_action: false,
363                is_detailed: false, /* well, technically this *is* an instance of a detailed
364                                     * signal, but we "pre-detailed" it */
365                version: prop_version,
366                deprecated_version: prop.deprecated_version,
367                doc: None,
368                doc_deprecated: None,
369            },
370            type_tid,
371            generate_trait,
372            is_fundamental,
373            &[],
374            obj,
375            &mut used_types,
376            prop_version,
377        );
378
379        if trampoline.is_ok() {
380            imports.add_used_types(&used_types);
381            if generate_trait {
382                imports.add("glib::prelude::*");
383            }
384            imports.add("glib::signal::{connect_raw, SignalHandlerId}");
385            imports.add("std::boxed::Box as Box_");
386
387            Some(signals::Info {
388                connect_name: format!("connect_{name_for_func}_notify"),
389                signal_name: format!("notify::{name}"),
390                trampoline,
391                action_emit_name: None,
392                version: prop_version,
393                deprecated_version: prop.deprecated_version,
394                doc_hidden: false,
395                is_detailed: false, // see above comment
396                generate_doc: obj.generate_doc,
397            })
398        } else {
399            None
400        }
401    } else {
402        None
403    };
404
405    (getter, setter, notify_signal)
406}
407
408/// Returns (the list of get functions to check, the desired get function name).
409fn get_func_name(prop_name: &str, is_bool_getter: bool) -> (Vec<String>, String) {
410    let get_rename_res = getter_rules::try_rename_getter_suffix(prop_name, is_bool_getter);
411    match get_rename_res {
412        Ok(new_name) => {
413            let new_name = new_name.unwrap();
414            let mut check_get_func_names = vec![
415                format!("get_{prop_name}"),
416                prop_name.to_string(),
417                format!("get_{new_name}"),
418                new_name.clone(),
419            ];
420
421            if is_bool_getter {
422                check_get_func_names.push(format!("is_{prop_name}"));
423                check_get_func_names.push(format!("is_{new_name}"));
424            }
425            (check_get_func_names, new_name)
426        }
427        Err(_) => {
428            let mut check_get_func_names = vec![format!("get_{prop_name}"), prop_name.to_string()];
429
430            // Name is reserved
431            let get_func_name = if is_bool_getter {
432                let get_func_name = format!("is_{prop_name}");
433                check_get_func_names.push(get_func_name.clone());
434                get_func_name
435            } else {
436                format!("get_{prop_name}")
437            };
438            (check_get_func_names, get_func_name)
439        }
440    }
441}
442
443pub fn get_property_ref_modes(
444    env: &Env,
445    prop: &library::Property,
446) -> (RefMode, RefMode, library::Nullable) {
447    let get_out_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::Return);
448    let mut set_in_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::In);
449    if set_in_ref_mode == RefMode::ByRefMut {
450        set_in_ref_mode = RefMode::ByRef;
451    }
452    let nullable = library::Nullable(set_in_ref_mode.is_ref());
453    (get_out_ref_mode, set_in_ref_mode, nullable)
454}