libgir/codegen/
enums.rs

1use std::{
2    collections::HashSet,
3    io::{prelude::*, Result},
4    path::Path,
5};
6
7use super::{function, trait_impls};
8use crate::{
9    analysis::enums::Info,
10    codegen::{
11        general::{
12            self, allow_deprecated, cfg_condition, cfg_condition_no_doc, cfg_condition_string,
13            cfg_deprecated, derives, doc_alias, version_condition, version_condition_no_doc,
14            version_condition_string,
15        },
16        generate_default_impl,
17    },
18    config::gobjects::GObject,
19    env::Env,
20    file_saver,
21    library::*,
22    nameutil::{enum_member_name, use_glib_if_needed, use_glib_type},
23    traits::*,
24    version::Version,
25};
26
27pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec<String>) {
28    if !env
29        .analysis
30        .enumerations
31        .iter()
32        .any(|e| env.config.objects[&e.full_name].status.need_generate())
33    {
34        return;
35    }
36
37    let path = root_path.join("enums.rs");
38    file_saver::save_to_file(path, env.config.make_backup, |w| {
39        general::start_comments(w, &env.config)?;
40        general::uses(w, env, &env.analysis.enum_imports, None)?;
41        writeln!(w)?;
42
43        mod_rs.push("\nmod enums;".into());
44        for enum_analysis in &env.analysis.enumerations {
45            let config = &env.config.objects[&enum_analysis.full_name];
46            if !config.status.need_generate() {
47                continue;
48            }
49
50            let enum_ = enum_analysis.type_(&env.library);
51
52            if let Some(cfg) = version_condition_string(env, None, enum_.version, false, 0) {
53                mod_rs.push(cfg);
54            }
55            if let Some(cfg) = cfg_condition_string(config.cfg_condition.as_ref(), false, 0) {
56                mod_rs.push(cfg);
57            }
58            mod_rs.push(format!(
59                "{}{} use self::enums::{};",
60                enum_
61                    .deprecated_version
62                    .map(|_| "#[allow(deprecated)]\n")
63                    .unwrap_or(""),
64                enum_analysis.visibility.export_visibility(),
65                enum_.name
66            ));
67
68            generate_enum(env, w, enum_, config, enum_analysis)?;
69        }
70
71        Ok(())
72    });
73}
74
75fn generate_enum(
76    env: &Env,
77    w: &mut dyn Write,
78    enum_: &Enumeration,
79    config: &GObject,
80    analysis: &Info,
81) -> Result<()> {
82    struct Member<'a> {
83        name: String,
84        c_name: String,
85        version: Option<Version>,
86        deprecated_version: Option<Version>,
87        cfg_condition: Option<&'a String>,
88    }
89
90    let mut members: Vec<Member<'_>> = Vec::new();
91    let mut vals: HashSet<String> = HashSet::new();
92    let sys_crate_name = env.sys_crate_import(analysis.type_id);
93
94    for member in &enum_.members {
95        let member_config = config.members.matched(&member.name);
96        if member.status.ignored() || vals.contains(&member.value) {
97            continue;
98        }
99        vals.insert(member.value.clone());
100        let deprecated_version = member_config
101            .iter()
102            .find_map(|m| m.deprecated_version)
103            .or(member.deprecated_version);
104        let version = member_config
105            .iter()
106            .find_map(|m| m.version)
107            .or(member.version);
108        let cfg_condition = member_config.iter().find_map(|m| m.cfg_condition.as_ref());
109        members.push(Member {
110            name: enum_member_name(&member.name),
111            c_name: member.c_identifier.clone(),
112            version,
113            deprecated_version,
114            cfg_condition,
115        });
116    }
117
118    cfg_deprecated(
119        w,
120        env,
121        Some(analysis.type_id),
122        enum_.deprecated_version,
123        false,
124        0,
125    )?;
126    version_condition(w, env, None, enum_.version, false, 0)?;
127    cfg_condition(w, config.cfg_condition.as_ref(), false, 0)?;
128    if config.must_use {
129        writeln!(w, "#[must_use]")?;
130    }
131
132    if let Some(ref d) = config.derives {
133        derives(w, d, 1)?;
134    } else {
135        writeln!(w, "#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]")?;
136    }
137    writeln!(w, "#[derive(Clone, Copy)]")?;
138    if config.exhaustive {
139        writeln!(w, "#[repr(i32)]")?;
140    } else {
141        writeln!(w, "#[non_exhaustive]")?;
142    }
143    doc_alias(w, &enum_.c_type, "", 0)?;
144
145    writeln!(w, "{} enum {} {{", analysis.visibility, enum_.name)?;
146    for member in &members {
147        cfg_deprecated(
148            w,
149            env,
150            Some(analysis.type_id),
151            member.deprecated_version,
152            false,
153            1,
154        )?;
155        version_condition(w, env, None, member.version, false, 1)?;
156        cfg_condition(w, member.cfg_condition.as_ref(), false, 1)?;
157        // Don't generate a doc_alias if the C name is the same as the Rust one
158        if member.c_name != member.name {
159            doc_alias(w, &member.c_name, "", 1)?;
160        }
161        if config.exhaustive {
162            writeln!(
163                w,
164                "\t{} = {}::{},",
165                member.name, sys_crate_name, member.c_name
166            )?;
167        } else {
168            writeln!(w, "\t{},", member.name)?;
169        }
170    }
171
172    if !config.exhaustive {
173        writeln!(
174            w,
175            "\
176    #[doc(hidden)]
177    __Unknown(i32),",
178        )?;
179    }
180
181    writeln!(w, "}}")?;
182
183    let any_deprecated_version = enum_
184        .deprecated_version
185        .or_else(|| members.iter().find_map(|m| m.deprecated_version));
186
187    let functions = analysis
188        .functions
189        .iter()
190        .filter(|f| f.status.need_generate())
191        .collect::<Vec<_>>();
192
193    if !functions.is_empty() {
194        writeln!(w)?;
195        version_condition(w, env, None, enum_.version, false, 0)?;
196        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
197        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
198        write!(w, "impl {} {{", analysis.name)?;
199        for func_analysis in functions {
200            function::generate(
201                w,
202                env,
203                Some(analysis.type_id),
204                func_analysis,
205                Some(&analysis.specials),
206                enum_.version,
207                false,
208                false,
209                1,
210            )?;
211        }
212        writeln!(w, "}}")?;
213    }
214
215    trait_impls::generate(
216        w,
217        env,
218        &analysis.name,
219        &analysis.functions,
220        &analysis.specials,
221        None,
222        None,
223        config.cfg_condition.as_deref(),
224    )?;
225
226    writeln!(w)?;
227
228    // Only inline from_glib / into_glib implementations if there are not many enums members
229    let maybe_inline = if members.len() <= 12 || config.exhaustive {
230        "#[inline]\n"
231    } else {
232        ""
233    };
234
235    // Generate IntoGlib trait implementation.
236    version_condition(w, env, None, enum_.version, false, 0)?;
237    cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
238    allow_deprecated(w, any_deprecated_version, false, 0)?;
239    writeln!(
240        w,
241        "#[doc(hidden)]
242impl IntoGlib for {name} {{
243    type GlibType = {sys_crate_name}::{ffi_name};
244
245    {maybe_inline}fn into_glib(self) -> {sys_crate_name}::{ffi_name} {{",
246        sys_crate_name = sys_crate_name,
247        name = enum_.name,
248        ffi_name = enum_.c_type,
249        maybe_inline = maybe_inline
250    )?;
251
252    if config.exhaustive {
253        writeln!(
254            w,
255            "self as {sys_crate_name}::{ffi_name}",
256            sys_crate_name = sys_crate_name,
257            ffi_name = enum_.c_type,
258        )?;
259    } else {
260        writeln!(w, "match self {{",)?;
261        for member in &members {
262            version_condition_no_doc(w, env, None, member.version, false, 3)?;
263            cfg_condition_no_doc(w, member.cfg_condition.as_ref(), false, 3)?;
264            writeln!(
265                w,
266                "\t\t\tSelf::{} => {}::{},",
267                member.name, sys_crate_name, member.c_name
268            )?;
269        }
270        writeln!(w, "\t\t\tSelf::__Unknown(value) => value,")?;
271        writeln!(
272            w,
273            "\
274        }}"
275        )?;
276    }
277
278    writeln!(
279        w,
280        "\
281    }}
282}}
283"
284    )?;
285
286    let assert = if env.config.generate_safety_asserts {
287        "skip_assert_initialized!();\n\t\t"
288    } else {
289        ""
290    };
291
292    // Generate FromGlib trait implementation.
293    version_condition(w, env, None, enum_.version, false, 0)?;
294    cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
295    allow_deprecated(w, any_deprecated_version, false, 0)?;
296    writeln!(
297        w,
298        "#[doc(hidden)]
299impl FromGlib<{sys_crate_name}::{ffi_name}> for {name} {{
300    {maybe_inline}unsafe fn from_glib(value: {sys_crate_name}::{ffi_name}) -> Self {{
301        {assert}",
302        sys_crate_name = sys_crate_name,
303        name = enum_.name,
304        ffi_name = enum_.c_type,
305        assert = assert,
306        maybe_inline = maybe_inline
307    )?;
308    if config.exhaustive {
309        let all_members = members
310            .iter()
311            .map(|m| format!("{}::{}", sys_crate_name, m.c_name))
312            .collect::<Vec<_>>()
313            .join(", ");
314        writeln!(w, "debug_assert!([{all_members}].contains(&value));")?;
315        writeln!(w, "std::mem::transmute(value)",)?;
316    } else {
317        writeln!(w, "match value {{")?;
318        for member in &members {
319            version_condition_no_doc(w, env, None, member.version, false, 3)?;
320            cfg_condition_no_doc(w, member.cfg_condition.as_ref(), false, 3)?;
321            writeln!(
322                w,
323                "\t\t\t{}::{} => Self::{},",
324                sys_crate_name, member.c_name, member.name
325            )?;
326        }
327        writeln!(w, "\t\t\tvalue => Self::__Unknown(value),")?;
328        writeln!(
329            w,
330            "\
331        }}"
332        )?;
333    }
334
335    writeln!(
336        w,
337        "\
338    }}
339}}
340"
341    )?;
342
343    // Generate ErrorDomain trait implementation.
344    if let Some(ref domain) = enum_.error_domain {
345        let has_failed_member = members.iter().any(|m| m.name == "Failed");
346
347        version_condition(w, env, None, enum_.version, false, 0)?;
348        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
349        allow_deprecated(w, any_deprecated_version, false, 0)?;
350        writeln!(
351            w,
352            "impl {glib_error_domain} for {name} {{
353    #[inline]
354    fn domain() -> {glib_quark} {{
355        {assert}",
356            name = enum_.name,
357            glib_error_domain = use_glib_type(env, "error::ErrorDomain"),
358            glib_quark = use_glib_type(env, "Quark"),
359            assert = assert
360        )?;
361
362        match domain {
363            ErrorDomain::Quark(quark) => {
364                writeln!(
365                    w,
366                    "        static QUARK: ::std::sync::OnceLock<{0}ffi::GQuark> = ::std::sync::OnceLock::new();
367        let quark = *QUARK.get_or_init(|| unsafe {{
368            {0}ffi::g_quark_from_static_string(b\"{1}\\0\".as_ptr() as *const _)
369        }});
370        unsafe {{ from_glib(quark) }}",
371                    use_glib_if_needed(env, ""),
372                    quark,
373                )?;
374            }
375            ErrorDomain::Function(f) => {
376                writeln!(w, "        unsafe {{ from_glib({sys_crate_name}::{f}()) }}")?;
377            }
378        }
379
380        writeln!(
381            w,
382            "    }}
383
384    #[inline]
385    fn code(self) -> i32 {{
386        self.into_glib()
387    }}
388
389    #[inline]
390    #[allow(clippy::match_single_binding)]
391    fn from(code: i32) -> Option<Self> {{
392        {assert}match unsafe {{ from_glib(code) }} {{"
393        )?;
394
395        if has_failed_member && !config.exhaustive {
396            writeln!(w, "\t\t\tSelf::__Unknown(_) => Some(Self::Failed),")?;
397        }
398        writeln!(w, "\t\t\tvalue => Some(value),")?;
399
400        writeln!(
401            w,
402            "\
403        }}
404    }}
405}}
406"
407        )?;
408    }
409
410    // Generate StaticType trait implementation.
411    if let Some(ref get_type) = enum_.glib_get_type {
412        let configured_functions = config.functions.matched("get_type");
413        let version = std::iter::once(enum_.version)
414            .chain(configured_functions.iter().map(|f| f.version))
415            .max()
416            .flatten();
417
418        version_condition(w, env, None, version, false, 0)?;
419        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
420        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
421        writeln!(
422            w,
423            "impl StaticType for {name} {{
424                #[inline]",
425            name = enum_.name,
426        )?;
427        doc_alias(w, get_type, "", 1)?;
428        writeln!(
429            w,
430            "   fn static_type() -> {glib_type} {{
431                    unsafe {{ from_glib({sys_crate_name}::{get_type}()) }}
432                }}
433            }}",
434            sys_crate_name = sys_crate_name,
435            get_type = get_type,
436            glib_type = use_glib_type(env, "Type")
437        )?;
438        writeln!(w)?;
439
440        version_condition(w, env, None, version, false, 0)?;
441        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
442        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
443        writeln!(
444            w,
445            "impl {has_param_spec} for {name} {{
446                type ParamSpec = {param_spec_enum};
447                type SetValue = Self;
448                type BuilderFn = fn(&str, Self) -> {param_spec_builder}<Self>;
449    
450                fn param_spec_builder() -> Self::BuilderFn {{
451                    Self::ParamSpec::builder_with_default
452                }}
453}}",
454            name = enum_.name,
455            has_param_spec = use_glib_type(env, "HasParamSpec"),
456            param_spec_enum = use_glib_type(env, "ParamSpecEnum"),
457            param_spec_builder = use_glib_type(env, "ParamSpecEnumBuilder"),
458        )?;
459        writeln!(w)?;
460
461        version_condition(w, env, None, version, false, 0)?;
462        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
463        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
464        writeln!(
465            w,
466            "impl {valuetype} for {name} {{
467    type Type = Self;
468}}",
469            name = enum_.name,
470            valuetype = use_glib_type(env, "value::ValueType"),
471        )?;
472        writeln!(w)?;
473
474        version_condition(w, env, None, version, false, 0)?;
475        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
476        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
477        writeln!(
478            w,
479            "unsafe impl<'a> {from_value_type}<'a> for {name} {{
480    type Checker = {genericwrongvaluetypechecker}<Self>;
481
482    #[inline]
483    unsafe fn from_value(value: &'a {gvalue}) -> Self {{
484        {assert}from_glib({glib}(value.to_glib_none().0))
485    }}
486}}",
487            name = enum_.name,
488            glib = use_glib_type(env, "gobject_ffi::g_value_get_enum"),
489            gvalue = use_glib_type(env, "Value"),
490            genericwrongvaluetypechecker = use_glib_type(env, "value::GenericValueTypeChecker"),
491            assert = assert,
492            from_value_type = use_glib_type(env, "value::FromValue"),
493        )?;
494        writeln!(w)?;
495
496        version_condition(w, env, None, version, false, 0)?;
497        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
498        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
499        writeln!(
500            w,
501            "impl ToValue for {name} {{
502    #[inline]
503    fn to_value(&self) -> {gvalue} {{
504        let mut value = {gvalue}::for_value_type::<Self>();
505        unsafe {{
506            {glib}(value.to_glib_none_mut().0, self.into_glib());
507        }}
508        value
509    }}
510
511    #[inline]
512    fn value_type(&self) -> {gtype} {{
513        Self::static_type()
514    }}
515}}",
516            name = enum_.name,
517            glib = use_glib_type(env, "gobject_ffi::g_value_set_enum"),
518            gvalue = use_glib_type(env, "Value"),
519            gtype = use_glib_type(env, "Type"),
520        )?;
521        writeln!(w)?;
522
523        version_condition(w, env, None, version, false, 0)?;
524        cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?;
525        allow_deprecated(w, enum_.deprecated_version, false, 0)?;
526        writeln!(
527            w,
528            "impl From<{name}> for {gvalue} {{
529    #[inline]
530    fn from(v: {name}) -> Self {{
531        {assert}ToValue::to_value(&v)
532    }}
533}}",
534            name = enum_.name,
535            gvalue = use_glib_type(env, "Value"),
536            assert = assert,
537        )?;
538        writeln!(w)?;
539    }
540
541    generate_default_impl(
542        w,
543        env,
544        config,
545        &enum_.name,
546        enum_.version,
547        enum_.members.iter(),
548        |member| {
549            let e_member = members.iter().find(|m| m.c_name == member.c_identifier)?;
550            let member_config = config.members.matched(&member.name);
551            let version = member_config
552                .iter()
553                .find_map(|m| m.version)
554                .or(e_member.version);
555            let cfg_condition = member_config.iter().find_map(|m| m.cfg_condition.as_ref());
556            Some((version, cfg_condition, e_member.name.as_str()))
557        },
558    )?;
559
560    Ok(())
561}