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    pub cfg_condition: Option<String>,
36}
37
38pub fn analyze(
39    env: &Env,
40    props: &[library::Property],
41    supertypes_props: &[&library::Property],
42    type_tid: library::TypeId,
43    generate_trait: bool,
44    is_fundamental: bool,
45    obj: &GObject,
46    imports: &mut Imports,
47    signatures: &Signatures,
48    deps: &[library::TypeId],
49    functions: &[crate::analysis::functions::Info],
50) -> (Vec<Property>, Vec<signals::Info>) {
51    let mut properties = Vec::new();
52    let mut notify_signals = Vec::new();
53
54    for prop in props {
55        let configured_properties = obj.properties.matched(&prop.name);
56        if !configured_properties
57            .iter()
58            .all(|f| f.status.need_generate())
59        {
60            continue;
61        }
62
63        if env.is_totally_deprecated(Some(type_tid.ns_id), prop.deprecated_version) {
64            continue;
65        }
66
67        if supertypes_props
68            .iter()
69            .any(|p| p.name == prop.name && p.typ == prop.typ)
70        {
71            continue;
72        }
73
74        let (getter, setter, notify_signal) = analyze_property(
75            env,
76            prop,
77            type_tid,
78            &configured_properties,
79            generate_trait,
80            is_fundamental,
81            obj,
82            imports,
83            signatures,
84            deps,
85            functions,
86        );
87
88        if let Some(notify_signal) = notify_signal {
89            notify_signals.push(notify_signal);
90        }
91
92        if let Some(prop) = getter {
93            properties.push(prop);
94        }
95        if let Some(prop) = setter {
96            properties.push(prop);
97        }
98    }
99
100    (properties, notify_signals)
101}
102
103fn analyze_property(
104    env: &Env,
105    prop: &library::Property,
106    type_tid: library::TypeId,
107    configured_properties: &[&config::properties::Property],
108    generate_trait: bool,
109    is_fundamental: bool,
110    obj: &GObject,
111    imports: &mut Imports,
112    signatures: &Signatures,
113    deps: &[library::TypeId],
114    functions: &[crate::analysis::functions::Info],
115) -> (Option<Property>, Option<Property>, Option<signals::Info>) {
116    let type_name = type_tid.full_name(&env.library);
117    let name = prop.name.clone();
118
119    let prop_version = configured_properties
120        .iter()
121        .filter_map(|f| f.version)
122        .min()
123        .or(prop.version)
124        .or(Some(env.config.min_cfg_version));
125
126    let cfg_condition = configured_properties
127        .iter()
128        .find_map(|p| p.cfg_condition.clone());
129
130    let generate = configured_properties.iter().find_map(|f| f.generate);
131    let generate_set = generate.is_some();
132    let generate = generate.unwrap_or_else(PropertyGenerateFlags::all);
133
134    let imports = &mut imports.with_defaults(prop_version, &cfg_condition);
135    imports.add("glib::translate::*");
136
137    let type_string = RustType::try_new(env, prop.typ);
138    let name_for_func = nameutil::signal_to_snake(&name);
139
140    let mut get_prop_name = Some(format!("get_property_{name_for_func}"));
141
142    let bypass_auto_rename = configured_properties.iter().any(|f| f.bypass_auto_rename);
143    let (check_get_func_names, mut get_func_name) = if bypass_auto_rename {
144        (
145            vec![format!("get_{name_for_func}")],
146            get_prop_name.take().expect("defined 10 lines above"),
147        )
148    } else {
149        get_func_name(&name_for_func, prop.typ == library::TypeId::tid_bool())
150    };
151
152    let mut set_func_name = format!("set_{name_for_func}");
153    let mut set_prop_name = Some(format!("set_property_{name_for_func}"));
154
155    let mut readable = prop.readable;
156    let mut writable = if prop.construct_only {
157        false
158    } else {
159        prop.writable
160    };
161    let mut notifiable = !prop.construct_only;
162    if generate_set && generate.contains(PropertyGenerateFlags::GET) && !readable {
163        warn!("Attempt to generate getter for notreadable property \"{type_name}.{name}\"");
164    }
165    if generate_set && generate.contains(PropertyGenerateFlags::SET) && !writable {
166        warn!("Attempt to generate setter for nonwritable property \"{type_name}.{name}\"");
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            cfg_condition: cfg_condition.clone(),
275        })
276    } else {
277        None
278    };
279
280    let setter = if writable && (!has_setter || prop.version < setter_func.and_then(|s| s.version))
281    {
282        if let Ok(rust_type) = RustType::builder(env, prop.typ)
283            .direction(library::ParameterDirection::In)
284            .try_build()
285        {
286            imports.add_used_types(rust_type.used_types());
287        }
288        if type_string.is_ok() {
289            imports.add("glib::prelude::*");
290        }
291        let set_bound = PropertyBound::get(env, prop.typ);
292        if type_string.is_ok() && set_bound.is_some() {
293            imports.add("glib::prelude::*");
294            if !*nullable {
295                // TODO: support non-nullable setter if found any
296                warn!(
297                    "Non nullable setter for property generated as nullable \"{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            cfg_condition: cfg_condition.clone(),
328        })
329    } else {
330        None
331    };
332
333    if !generate_trait && (writable || readable || notifiable) {
334        imports.add("glib::prelude::*");
335    }
336
337    let notify_signal = if notifiable {
338        let mut used_types: Vec<String> = Vec::with_capacity(4);
339        let trampoline = trampolines::analyze(
340            env,
341            &library::Signal {
342                name: format!("notify::{name}"),
343                parameters: Vec::new(),
344                ret: library::Parameter {
345                    name: String::new(),
346                    typ: env
347                        .library
348                        .find_type(library::INTERNAL_NAMESPACE, "none")
349                        .unwrap(),
350                    c_type: "none".into(),
351                    instance_parameter: false,
352                    direction: library::ParameterDirection::Return,
353                    transfer: library::Transfer::None,
354                    caller_allocates: false,
355                    nullable: library::Nullable(false),
356                    array_length: None,
357                    is_error: false,
358                    doc: None,
359                    scope: library::ParameterScope::None,
360                    closure: None,
361                    destroy: None,
362                },
363                is_action: false,
364                is_detailed: false, /* well, technically this *is* an instance of a detailed
365                                     * signal, but we "pre-detailed" it */
366                version: prop_version,
367                deprecated_version: prop.deprecated_version,
368                doc: None,
369                doc_deprecated: None,
370            },
371            type_tid,
372            generate_trait,
373            is_fundamental,
374            &[],
375            obj,
376            &mut used_types,
377            prop_version,
378        );
379
380        if trampoline.is_ok() {
381            imports.add_used_types(&used_types);
382            if generate_trait {
383                imports.add("glib::prelude::*");
384            }
385            imports.add("glib::signal::{connect_raw, SignalHandlerId}");
386            imports.add("std::boxed::Box as Box_");
387
388            Some(signals::Info {
389                connect_name: format!("connect_{name_for_func}_notify"),
390                signal_name: format!("notify::{name}"),
391                trampoline,
392                action_emit_name: None,
393                version: prop_version,
394                deprecated_version: prop.deprecated_version,
395                doc_hidden: false,
396                is_detailed: false, // see above comment
397                generate_doc: obj.generate_doc,
398                cfg_condition,
399            })
400        } else {
401            None
402        }
403    } else {
404        None
405    };
406
407    (getter, setter, notify_signal)
408}
409
410/// Returns (the list of get functions to check, the desired get function name).
411fn get_func_name(prop_name: &str, is_bool_getter: bool) -> (Vec<String>, String) {
412    let get_rename_res = getter_rules::try_rename_getter_suffix(prop_name, is_bool_getter);
413    match get_rename_res {
414        Ok(new_name) => {
415            let new_name = new_name.unwrap();
416            let mut check_get_func_names = vec![
417                format!("get_{prop_name}"),
418                prop_name.to_string(),
419                format!("get_{new_name}"),
420                new_name.clone(),
421            ];
422
423            if is_bool_getter {
424                check_get_func_names.push(format!("is_{prop_name}"));
425                check_get_func_names.push(format!("is_{new_name}"));
426            }
427            (check_get_func_names, new_name)
428        }
429        Err(_) => {
430            let mut check_get_func_names = vec![format!("get_{prop_name}"), prop_name.to_string()];
431
432            // Name is reserved
433            let get_func_name = if is_bool_getter {
434                let get_func_name = format!("is_{prop_name}");
435                check_get_func_names.push(get_func_name.clone());
436                get_func_name
437            } else {
438                format!("get_{prop_name}")
439            };
440            (check_get_func_names, get_func_name)
441        }
442    }
443}
444
445pub fn get_property_ref_modes(
446    env: &Env,
447    prop: &library::Property,
448) -> (RefMode, RefMode, library::Nullable) {
449    let get_out_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::Return);
450    let mut set_in_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::In);
451    if set_in_ref_mode == RefMode::ByRefMut {
452        set_in_ref_mode = RefMode::ByRef;
453    }
454    let nullable = library::Nullable(set_in_ref_mode.is_ref());
455    (get_out_ref_mode, set_in_ref_mode, nullable)
456}