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 if env.is_totally_deprecated(Some(type_tid.ns_id), version)
187 || version <= prop_version
188 {
189 readable = false;
192 } else {
193 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 if env.is_totally_deprecated(Some(type_tid.ns_id), version) || version <= prop_version {
209 writable = false;
212 } else {
213 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 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, 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, 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
410fn 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 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}