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 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 })
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 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, 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, generate_doc: obj.generate_doc,
397 })
398 } else {
399 None
400 }
401 } else {
402 None
403 };
404
405 (getter, setter, notify_signal)
406}
407
408fn 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 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}