libgir/codegen/
object.rs

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    // Collect all supertypes that were added at a later time. The `glib::wrapper!`
46    // call needs to be done multiple times with different `#[cfg]` directives
47    // if there is a difference.
48    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                // TODO: Might want to add a configuration on the object to override this per
59                // supertype in case the supertype existed in older versions but newly became on
60                // for this very type.
61                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        // Write the `glib::wrapper!` calls from the highest version to the lowest and
112        // remember which supertypes have to be removed for the next call.
113        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        // Write the base `glib::wrapper!`.
163        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            // generate builder method that returns the corresponding builder
217            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, // There is no need for #[cfg()] since it's applied on the whole file.
306    )?;
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    // The split allows us to not have clippy::let_and_return lint disabled
461    if let Some(code) = analysis.builder_postprocess.as_ref() {
462        // We don't generate an assertion macro for the case where you have a build post-process
463        // as it is only used to initialize gtk in gtk::ApplicationBuilder which is too early to assert anything
464        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}