1use std::{
2 collections::{BTreeMap, HashSet},
3 io::{Result, Write},
4};
5
6use super::{
7 child_properties, function, general,
8 general::{
9 cfg_deprecated_string, not_version_condition_no_docsrs, version_condition,
10 version_condition_no_doc, version_condition_string,
11 },
12 properties, signal, trait_impls,
13};
14use crate::{
15 analysis::{
16 self, bounds::BoundType, object::has_builder_properties, record_type::RecordType,
17 ref_mode::RefMode, rust_type::RustType, safety_assertion_mode::SafetyAssertionMode,
18 },
19 env::Env,
20 library::{self, Nullable},
21 nameutil,
22 traits::IntoString,
23};
24
25pub fn generate(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> {
26 general::start_comments(w, &env.config)?;
27 if analysis
28 .functions
29 .iter()
30 .any(|f| f.deprecated_version.is_some())
31 {
32 writeln!(w, "#![allow(deprecated)]")?;
33 }
34 general::uses(w, env, &analysis.imports, analysis.version)?;
35
36 let config = &env.config.objects[&analysis.full_name];
37 if config.default_value.is_some() {
38 log::error!(
39 "`default_value` can only be used on flags and enums. {} is neither. Ignoring \
40 `default_value`.",
41 analysis.name,
42 );
43 }
44
45 let mut namespaces = Vec::new();
49 for p in &analysis.supertypes {
50 use crate::library::*;
51 let mut versions = BTreeMap::new();
52
53 match *env.library.type_(p.type_id) {
54 Type::Interface(Interface { .. }) | Type::Class(Class { .. })
55 if !p.status.ignored() =>
56 {
57 let full_name = p.type_id.full_name(&env.library);
58 if let Some(object) = env.analysis.objects.get(&full_name) {
62 let parent_version = object.version;
63 let namespace_min_version = env
64 .config
65 .min_required_version(env, Some(object.type_id.ns_id));
66 if parent_version > analysis.version && parent_version > namespace_min_version {
67 versions
68 .entry(parent_version)
69 .and_modify(|t: &mut Vec<_>| t.push(p))
70 .or_insert_with(|| vec![p]);
71 if !versions.is_empty() {
72 namespaces.push((p.type_id.ns_id, versions));
73 }
74 }
75 }
76 }
77 _ => continue,
78 }
79 }
80
81 if namespaces.is_empty() || analysis.is_fundamental {
82 writeln!(w)?;
83 if analysis.is_fundamental {
84 general::define_fundamental_type(
85 w,
86 env,
87 &analysis.name,
88 &analysis.c_type,
89 &analysis.get_type,
90 analysis.ref_fn.as_deref(),
91 analysis.unref_fn.as_deref(),
92 &analysis.supertypes,
93 analysis.visibility,
94 analysis.type_id,
95 )?;
96 } else {
97 general::define_object_type(
98 w,
99 env,
100 &analysis.name,
101 &analysis.c_type,
102 analysis.c_class_type.as_deref(),
103 &analysis.get_type,
104 analysis.is_interface,
105 &analysis.supertypes,
106 analysis.visibility,
107 analysis.type_id,
108 )?;
109 }
110 } else {
111 let mut remove_types: HashSet<library::TypeId> = HashSet::new();
114
115 let mut previous_version = None;
116 let mut previous_ns_id = None;
117 for (ns_id, versions) in &namespaces {
118 for (&version, stypes) in versions.iter().rev() {
119 let supertypes = analysis
120 .supertypes
121 .iter()
122 .filter(|t| !remove_types.contains(&t.type_id))
123 .cloned()
124 .collect::<Vec<_>>();
125
126 writeln!(w)?;
127 if previous_version.is_some() {
128 not_version_condition_no_docsrs(
129 w,
130 env,
131 Some(*ns_id),
132 previous_version,
133 false,
134 0,
135 )?;
136 version_condition_no_doc(w, env, Some(*ns_id), version, false, 0)?;
137 } else {
138 version_condition(w, env, Some(*ns_id), version, false, 0)?;
139 }
140 general::define_object_type(
141 w,
142 env,
143 &analysis.name,
144 &analysis.c_type,
145 analysis.c_class_type.as_deref(),
146 &analysis.get_type,
147 analysis.is_interface,
148 &supertypes,
149 analysis.visibility,
150 analysis.type_id,
151 )?;
152
153 for t in stypes {
154 remove_types.insert(t.type_id);
155 }
156
157 previous_ns_id = Some(*ns_id);
158 previous_version = version;
159 }
160 }
161
162 let supertypes = analysis
164 .supertypes
165 .iter()
166 .filter(|t| !remove_types.contains(&t.type_id))
167 .cloned()
168 .collect::<Vec<_>>();
169 writeln!(w)?;
170 not_version_condition_no_docsrs(w, env, previous_ns_id, previous_version, false, 0)?;
171 general::define_object_type(
172 w,
173 env,
174 &analysis.name,
175 &analysis.c_type,
176 analysis.c_class_type.as_deref(),
177 &analysis.get_type,
178 analysis.is_interface,
179 &supertypes,
180 analysis.visibility,
181 analysis.type_id,
182 )?;
183 }
184
185 if (analysis.need_generate_inherent() && analysis.should_generate_impl_block())
186 || !analysis.final_type
187 {
188 writeln!(w)?;
189 write!(w, "impl {} {{", analysis.name)?;
190
191 if !analysis.final_type {
192 writeln!(
193 w,
194 "
195 pub const NONE: Option<&'static {}> = None;
196 ",
197 analysis.name
198 )?;
199 }
200
201 for func_analysis in &analysis.constructors() {
202 function::generate(
203 w,
204 env,
205 Some(analysis.type_id),
206 func_analysis,
207 Some(&analysis.specials),
208 analysis.version,
209 false,
210 false,
211 1,
212 )?;
213 }
214
215 if has_builder_properties(&analysis.builder_properties) {
216 let builder_name = format!("{}Builder", analysis.name);
218 writeln!(
219 w,
220 "
221 // rustdoc-stripper-ignore-next
222 /// Creates a new builder-pattern struct instance to construct [`{name}`] objects.
223 ///
224 /// This method returns an instance of [`{builder_name}`](crate::builders::{builder_name}) which can be used to create [`{name}`] objects.
225 pub fn builder() -> {builder_name} {{
226 {builder_name}::new()
227 }}
228 ",
229 name = analysis.name,
230 builder_name = builder_name
231 )?;
232 }
233
234 if !analysis.need_generate_trait() {
235 for func_analysis in &analysis.methods() {
236 function::generate(
237 w,
238 env,
239 Some(analysis.type_id),
240 func_analysis,
241 Some(&analysis.specials),
242 analysis.version,
243 false,
244 false,
245 1,
246 )?;
247 }
248
249 for property in &analysis.properties {
250 properties::generate(w, env, property, false, false, 1)?;
251 }
252
253 for child_property in &analysis.child_properties {
254 child_properties::generate(w, env, child_property, false, false, 1)?;
255 }
256 }
257
258 for func_analysis in &analysis.functions() {
259 function::generate(
260 w,
261 env,
262 Some(analysis.type_id),
263 func_analysis,
264 Some(&analysis.specials),
265 analysis.version,
266 false,
267 false,
268 1,
269 )?;
270 }
271
272 if !analysis.need_generate_trait() {
273 for signal_analysis in analysis
274 .signals
275 .iter()
276 .chain(analysis.notify_signals.iter())
277 {
278 signal::generate(w, env, signal_analysis, false, false, 1)?;
279 }
280 }
281
282 writeln!(w, "}}")?;
283
284 general::declare_default_from_new(
285 w,
286 env,
287 &analysis.name,
288 &analysis.functions,
289 has_builder_properties(&analysis.builder_properties),
290 )?;
291 }
292
293 trait_impls::generate(
294 w,
295 env,
296 &analysis.name,
297 &analysis.functions,
298 &analysis.specials,
299 if analysis.need_generate_trait() {
300 Some(&analysis.trait_name)
301 } else {
302 None
303 },
304 analysis.version,
305 None, )?;
307
308 if has_builder_properties(&analysis.builder_properties) {
309 writeln!(w)?;
310 generate_builder(w, env, analysis)?;
311 }
312
313 if analysis.concurrency != library::Concurrency::None {
314 writeln!(w)?;
315 }
316
317 match analysis.concurrency {
318 library::Concurrency::Send | library::Concurrency::SendSync => {
319 writeln!(w, "unsafe impl Send for {} {{}}", analysis.name)?;
320 }
321 _ => (),
322 }
323
324 if let library::Concurrency::SendSync = analysis.concurrency {
325 writeln!(w, "unsafe impl Sync for {} {{}}", analysis.name)?;
326 }
327
328 if analysis.need_generate_trait() {
329 writeln!(w)?;
330 generate_trait(w, env, analysis)?;
331 }
332 Ok(())
333}
334
335fn generate_builder(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> {
336 let glib_crate_name = if env.namespaces.is_glib_crate {
337 "crate"
338 } else {
339 "glib"
340 };
341
342 writeln!(
343 w,
344 "// rustdoc-stripper-ignore-next
345 /// A [builder-pattern] type to construct [`{}`] objects.
346 ///
347 /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html",
348 analysis.name,
349 )?;
350 writeln!(w, "#[must_use = \"The builder must be built to be used\"]")?;
351 writeln!(
352 w,
353 "pub struct {name}Builder {{
354 builder: {glib_name}::object::ObjectBuilder<'static, {name}>,
355 }}
356
357 impl {name}Builder {{
358 fn new() -> Self {{
359 Self {{ builder: {glib_name}::object::Object::builder() }}
360 }}",
361 name = analysis.name,
362 glib_name = glib_crate_name,
363 )?;
364 for (builder_props, super_tid) in &analysis.builder_properties {
365 for property in builder_props {
366 let direction = if property.is_get {
367 library::ParameterDirection::In
368 } else {
369 library::ParameterDirection::Out
370 };
371 let param_type = RustType::builder(env, property.typ)
372 .direction(direction)
373 .ref_mode(property.set_in_ref_mode)
374 .try_build();
375 let comment_prefix = if param_type.is_err() { "//" } else { "" };
376 let mut param_type_str = param_type.into_string();
377 let (param_type_override, bounds, conversion) = match param_type_str.as_str() {
378 "&str" => (
379 Some(format!("impl Into<{glib_crate_name}::GString>")),
380 String::new(),
381 ".into()",
382 ),
383 "&[&str]" => (
384 Some(format!("impl Into<{glib_crate_name}::StrV>")),
385 String::from(""),
386 ".into()",
387 ),
388 _ if !property.bounds.is_empty() => {
389 let (bounds, _) = function::bounds(&property.bounds, &[], false, false);
390 let param_bound = property.bounds.get_parameter_bound(&property.name);
391 let alias = param_bound.map(|bound| {
392 bound.full_type_parameter_reference(RefMode::ByRef, Nullable(false), false)
393 });
394 let conversion = param_bound.and_then(|bound| match bound.bound_type {
395 BoundType::AsRef(_) => Some(".as_ref().clone()"),
396 _ => None,
397 });
398 (alias, bounds, conversion.unwrap_or(".clone().upcast()"))
399 }
400 typ if typ.starts_with('&') => {
401 let should_clone =
402 if let crate::library::Type::Record(record) = env.type_(property.typ) {
403 match RecordType::of(record) {
404 RecordType::Boxed => "",
405 RecordType::AutoBoxed => {
406 if !record.has_copy() {
407 ""
408 } else {
409 ".clone()"
410 }
411 }
412 _ => ".clone()",
413 }
414 } else {
415 ".clone()"
416 };
417
418 (None, String::new(), should_clone)
419 }
420 _ => (None, String::new(), ""),
421 };
422 if let Some(param_type_override) = param_type_override {
423 param_type_str = param_type_override.to_string();
424 }
425 let name = nameutil::mangle_keywords(nameutil::signal_to_snake(&property.name));
426
427 let version_condition_string =
428 version_condition_string(env, Some(super_tid.ns_id), property.version, false, 1);
429 let deprecated_string =
430 cfg_deprecated_string(env, Some(*super_tid), property.deprecated_version, false, 1);
431 let version_prefix = version_condition_string
432 .map(|version| format!("{comment_prefix}{version}\n"))
433 .unwrap_or_default();
434
435 let deprecation_prefix = deprecated_string
436 .map(|version| format!("{comment_prefix}{version}\n"))
437 .unwrap_or_default();
438
439 writeln!(
440 w,
441 "
442 {version_prefix}{deprecation_prefix} {comment_prefix}pub fn {name}{bounds}(self, {name}: {param_type_str}) -> Self {{
443 {comment_prefix} Self {{ builder: self.builder.property(\"{property_name}\", {name}{conversion}), }}
444 {comment_prefix}}}",
445 property_name = property.name,
446 )?;
447 }
448 }
449
450 writeln!(
451 w,
452 "
453 // rustdoc-stripper-ignore-next
454 /// Build the [`{name}`].
455 #[must_use = \"Building the object from the builder is usually expensive and is not expected to have side effects\"]
456 pub fn build(self) -> {name} {{",
457 name = analysis.name,
458 )?;
459
460 if let Some(code) = analysis.builder_postprocess.as_ref() {
462 writeln!(w, " let ret = self.builder.build();")?;
465 writeln!(w, " {{\n {code}\n }}")?;
466 writeln!(w, " ret\n }}")?;
467 } else {
468 if env.config.generate_safety_asserts {
469 writeln!(w, "{}", SafetyAssertionMode::InMainThread)?;
470 }
471 writeln!(w, " self.builder.build() }}")?;
472 }
473 writeln!(w, "}}")
474}
475
476fn generate_trait(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> {
477 write!(
478 w,
479 "pub trait {}: IsA<{}> + 'static {{",
480 analysis.trait_name, analysis.name
481 )?;
482
483 for func_analysis in &analysis.methods() {
484 function::generate(
485 w,
486 env,
487 Some(analysis.type_id),
488 func_analysis,
489 Some(&analysis.specials),
490 analysis.version,
491 true,
492 false,
493 1,
494 )?;
495 }
496 for property in &analysis.properties {
497 properties::generate(w, env, property, true, false, 1)?;
498 }
499 for child_property in &analysis.child_properties {
500 child_properties::generate(w, env, child_property, true, false, 1)?;
501 }
502 for signal_analysis in analysis
503 .signals
504 .iter()
505 .chain(analysis.notify_signals.iter())
506 {
507 signal::generate(w, env, signal_analysis, true, false, 1)?;
508 }
509 writeln!(w, "}}")?;
510
511 writeln!(w)?;
512 writeln!(
513 w,
514 "impl<O: IsA<{}>> {} for O {{}}",
515 analysis.name, analysis.trait_name,
516 )?;
517
518 Ok(())
519}
520
521pub fn generate_reexports(
522 env: &Env,
523 analysis: &analysis::object::Info,
524 module_name: &str,
525 contents: &mut Vec<String>,
526 traits: &mut Vec<String>,
527 builders: &mut Vec<String>,
528) {
529 let mut cfgs: Vec<String> = Vec::new();
530 if let Some(cfg) = general::cfg_condition_string(analysis.cfg_condition.as_ref(), false, 0) {
531 cfgs.push(cfg);
532 }
533 if let Some(cfg) = general::version_condition_string(env, None, analysis.version, false, 0) {
534 cfgs.push(cfg);
535 }
536 if let Some(cfg) = general::cfg_deprecated_string(
537 env,
538 Some(analysis.type_id),
539 analysis.deprecated_version,
540 false,
541 0,
542 ) {
543 cfgs.push(cfg);
544 }
545
546 contents.push(String::new());
547 contents.extend_from_slice(&cfgs);
548 contents.push(format!("mod {module_name};"));
549 contents.extend_from_slice(&cfgs);
550
551 contents.push(format!(
552 "{} use self::{}::{};",
553 analysis.visibility.export_visibility(),
554 module_name,
555 analysis.name,
556 ));
557
558 if analysis.need_generate_trait() {
559 for cfg in &cfgs {
560 traits.push(format!("\t{cfg}"));
561 }
562 traits.push(format!(
563 "\tpub use super::{}::{};",
564 module_name, analysis.trait_name
565 ));
566 }
567
568 if has_builder_properties(&analysis.builder_properties) {
569 for cfg in &cfgs {
570 builders.push(format!("\t{cfg}"));
571 }
572 builders.push(format!(
573 "\tpub use super::{}::{}Builder;",
574 module_name, analysis.name
575 ));
576 }
577}