Skip to main content

nbindgen/bindgen/ir/
enumeration.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::io::Write;
6
7use syn;
8
9use crate::bindgen::config::Config;
10use crate::bindgen::dependencies::Dependencies;
11use crate::bindgen::ir::{
12    AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, GenericPath, Item,
13    ItemContainer, Path, Repr, ReprStyle, ReprType, Struct, ToCondition, Type,
14};
15use crate::bindgen::library::Library;
16use crate::bindgen::rename::{IdentifierType, RenameRule};
17use crate::bindgen::reserved;
18use crate::bindgen::utilities::find_first_some;
19use crate::bindgen::writer::{Source, SourceWriter};
20
21#[derive(Debug, Clone)]
22pub struct EnumVariant {
23    pub name: String,
24    pub export_name: String,
25    pub discriminant: Option<i64>,
26    pub body: Option<(String, Struct)>,
27    pub documentation: Documentation,
28}
29
30fn value_from_expr(val: &syn::Expr) -> Option<i64> {
31    match *val {
32        syn::Expr::Lit(ref lit) => match lit.lit {
33            syn::Lit::Int(ref lit) => lit.base10_parse::<i64>().ok(),
34            _ => None,
35        },
36        syn::Expr::Unary(ref unary) => {
37            let v = value_from_expr(&unary.expr)?;
38            match unary.op {
39                syn::UnOp::Deref(..) => None,
40                syn::UnOp::Neg(..) => v.checked_mul(-1),
41                syn::UnOp::Not(..) => v.checked_neg(),
42            }
43        }
44        _ => None,
45    }
46}
47
48impl EnumVariant {
49    pub fn load(
50        is_tagged: bool,
51        variant: &syn::Variant,
52        generic_params: GenericParams,
53        mod_cfg: Option<&Cfg>,
54    ) -> Result<Self, String> {
55        let discriminant = match variant.discriminant {
56            Some((_, ref expr)) => match value_from_expr(expr) {
57                Some(v) => Some(v),
58                None => return Err(format!("Unsupported discriminant {:?}.", expr)),
59            },
60            None => None,
61        };
62
63        fn parse_fields(
64            is_tagged: bool,
65            fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
66        ) -> Result<Vec<(String, Type, Documentation)>, String> {
67            let mut res = Vec::new();
68
69            if is_tagged {
70                res.push((
71                    "tag*".to_string(),
72                    Type::Path(GenericPath::new(Path::new("Tag"), vec![])),
73                    Documentation::none(),
74                ));
75            }
76
77            for (i, field) in fields.iter().enumerate() {
78                if let Some(ty) = Type::load(&field.ty)? {
79                    res.push((
80                        reserved::escaped(&match field.ident {
81                            Some(ref ident) => ident.to_string(),
82                            None => i.to_string(),
83                        }),
84                        ty,
85                        Documentation::load(&field.attrs),
86                    ));
87                }
88            }
89
90            Ok(res)
91        }
92
93        let body = match variant.fields {
94            syn::Fields::Unit => None,
95            syn::Fields::Named(ref fields) => {
96                let path = Path::new(format!("{}_Body", variant.ident));
97                Some(Struct::new(
98                    path,
99                    generic_params,
100                    parse_fields(is_tagged, &fields.named)?,
101                    is_tagged,
102                    true,
103                    None,
104                    false,
105                    false,
106                    Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
107                    AnnotationSet::load(&variant.attrs)?,
108                    Documentation::none(),
109                ))
110            }
111            syn::Fields::Unnamed(ref fields) => {
112                let path = Path::new(format!("{}_Body", variant.ident));
113                Some(Struct::new(
114                    path,
115                    generic_params,
116                    parse_fields(is_tagged, &fields.unnamed)?,
117                    is_tagged,
118                    true,
119                    None,
120                    false,
121                    true,
122                    Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
123                    AnnotationSet::load(&variant.attrs)?,
124                    Documentation::none(),
125                ))
126            }
127        };
128
129        Ok(EnumVariant::new(
130            variant.ident.to_string(),
131            discriminant,
132            body.map(|body| {
133                (
134                    RenameRule::SnakeCase.apply_to_pascal_case(
135                        &format!("{}", variant.ident),
136                        IdentifierType::StructMember,
137                    ),
138                    body,
139                )
140            }),
141            Documentation::load(&variant.attrs),
142        ))
143    }
144
145    pub fn new(
146        name: String,
147        discriminant: Option<i64>,
148        body: Option<(String, Struct)>,
149        documentation: Documentation,
150    ) -> Self {
151        let export_name = name.clone();
152        Self {
153            name,
154            export_name,
155            discriminant,
156            body,
157            documentation,
158        }
159    }
160
161    fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
162        if let Some((_, ref item)) = self.body {
163            item.add_dependencies(library, out);
164        }
165    }
166}
167
168impl Source for EnumVariant {
169    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
170        self.documentation.write(config, out);
171        write!(out, "{}*", self.export_name);
172        if let Some(discriminant) = self.discriminant {
173            write!(out, " = {}", discriminant);
174        }
175        out.write(",");
176    }
177}
178
179#[derive(Debug, Clone)]
180pub struct Enum {
181    pub path: Path,
182    pub export_name: String,
183    pub generic_params: GenericParams,
184    pub repr: Repr,
185    pub variants: Vec<EnumVariant>,
186    pub tag: Option<String>,
187    pub cfg: Option<Cfg>,
188    pub annotations: AnnotationSet,
189    pub documentation: Documentation,
190}
191
192impl Enum {
193    pub fn load(item: &syn::ItemEnum, mod_cfg: Option<&Cfg>) -> Result<Enum, String> {
194        let repr = Repr::load(&item.attrs)?;
195        if repr.style == ReprStyle::Rust && repr.ty.is_none() {
196            return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned());
197        }
198        // TODO: Implement translation of aligned enums.
199        if repr.align.is_some() {
200            return Err("Enum is marked with #[repr(align(...))] or #[repr(packed)].".to_owned());
201        }
202
203        let generic_params = GenericParams::new(&item.generics);
204
205        let mut variants = Vec::new();
206        let mut is_tagged = false;
207
208        for variant in item.variants.iter() {
209            let variant = EnumVariant::load(
210                repr.style == ReprStyle::Rust,
211                variant,
212                generic_params.clone(),
213                mod_cfg,
214            )?;
215            is_tagged = is_tagged || variant.body.is_some();
216            variants.push(variant);
217        }
218
219        let annotations = AnnotationSet::load(&item.attrs)?;
220
221        if let Some(names) = annotations.list("enum-trailing-values") {
222            for name in names {
223                variants.push(EnumVariant::new(name, None, None, Documentation::none()));
224            }
225        }
226
227        let path = Path::new(item.ident.to_string());
228        let tag = if is_tagged {
229            Some("Tag".to_string())
230        } else {
231            None
232        };
233        Ok(Enum::new(
234            path,
235            generic_params,
236            repr,
237            variants,
238            tag,
239            Cfg::append(mod_cfg, Cfg::load(&item.attrs)),
240            annotations,
241            Documentation::load(&item.attrs),
242        ))
243    }
244
245    pub fn new(
246        path: Path,
247        generic_params: GenericParams,
248        repr: Repr,
249        variants: Vec<EnumVariant>,
250        tag: Option<String>,
251        cfg: Option<Cfg>,
252        annotations: AnnotationSet,
253        documentation: Documentation,
254    ) -> Self {
255        let export_name = path.name().to_owned();
256        Self {
257            path,
258            export_name,
259            generic_params,
260            repr,
261            variants,
262            tag,
263            cfg,
264            annotations,
265            documentation,
266        }
267    }
268}
269
270impl Item for Enum {
271    fn path(&self) -> &Path {
272        &self.path
273    }
274
275    fn export_name(&self) -> &str {
276        &self.export_name
277    }
278
279    fn cfg(&self) -> Option<&Cfg> {
280        self.cfg.as_ref()
281    }
282
283    fn annotations(&self) -> &AnnotationSet {
284        &self.annotations
285    }
286
287    fn annotations_mut(&mut self) -> &mut AnnotationSet {
288        &mut self.annotations
289    }
290
291    fn container(&self) -> ItemContainer {
292        ItemContainer::Enum(self.clone())
293    }
294
295    fn rename_for_config(&mut self, config: &Config) {
296        config.export.rename(&mut self.export_name);
297
298        if self.tag.is_some() {
299            // it makes sense to always prefix Tag with type name in C
300            let new_tag = format!("{}_Tag", self.export_name);
301            if self.repr.style == ReprStyle::Rust {
302                for variant in &mut self.variants {
303                    if let Some((_, ref mut body)) = variant.body {
304                        let path = Path::new(new_tag.clone());
305                        let generic_path = GenericPath::new(path, vec![]);
306                        body.fields[0].1 = Type::Path(generic_path);
307                    }
308                }
309            }
310            self.tag = Some(new_tag);
311        }
312
313        for variant in &mut self.variants {
314            if let Some((_, ref mut body)) = variant.body {
315                body.rename_for_config(config);
316            }
317        }
318
319        if config.enumeration.prefix_with_name
320            || self.annotations.bool("prefix-with-name").unwrap_or(false)
321        {
322            for variant in &mut self.variants {
323                variant.export_name = format!("{}_{}", self.export_name, variant.export_name);
324                if let Some((_, ref mut body)) = variant.body {
325                    body.export_name = format!("{}_{}", self.export_name, body.export_name());
326                }
327            }
328        }
329
330        let rules = [
331            self.annotations.parse_atom::<RenameRule>("rename-all"),
332            config.enumeration.rename_variants,
333        ];
334
335        if let Some(r) = find_first_some(&rules) {
336            self.variants = self
337                .variants
338                .iter()
339                .map(|variant| {
340                    EnumVariant::new(
341                        r.apply_to_pascal_case(
342                            &variant.export_name,
343                            IdentifierType::EnumVariant(self),
344                        ),
345                        variant.discriminant,
346                        variant.body.as_ref().map(|body| {
347                            (
348                                r.apply_to_snake_case(&body.0, IdentifierType::StructMember),
349                                body.1.clone(),
350                            )
351                        }),
352                        variant.documentation.clone(),
353                    )
354                })
355                .collect();
356        }
357    }
358
359    fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
360        for variant in &self.variants {
361            variant.add_dependencies(library, out);
362        }
363    }
364}
365
366impl Source for Enum {
367    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
368        let size = self.repr.ty.map(|ty| match ty {
369            ReprType::USize => "uint",
370            ReprType::U64 => "uint64",
371            ReprType::U32 => "uint32",
372            ReprType::U16 => "uint16",
373            ReprType::U8 => "uint8",
374            ReprType::ISize => "int",
375            ReprType::I64 => "int64",
376            ReprType::I32 => "int32",
377            ReprType::I16 => "int16",
378            ReprType::I8 => "int8",
379        });
380
381        let condition = (&self.cfg).to_condition(config);
382
383        condition.write_before(config, out);
384
385        self.documentation.write(config, out);
386
387        let is_tagged = self.tag.is_some();
388
389        // If tagged, we need to emit a proper struct/union wrapper around our enum
390
391        let enum_name = if let Some(ref tag) = self.tag {
392            tag
393        } else {
394            self.export_name()
395        };
396
397        write!(out, "type {}*", enum_name);
398        write!(out, " = {}", size.unwrap_or("int"));
399
400        /*
401        out.open_brace();
402        for (i, variant) in self.variants.iter().enumerate() {
403            if i != 0 {
404                out.new_line()
405            }
406            variant.write(config, out);
407        }
408        if config.enumeration.add_sentinel(&self.annotations) {
409            out.new_line();
410            out.new_line();
411            out.write("Sentinel /* this must be last for serialization purposes. */");
412        }
413
414        */
415        // Done emitting the enum
416
417        // If tagged, we need to emit structs for the cases and union them together
418        if is_tagged {
419            // Emit the cases for the structs
420            for variant in &self.variants {
421                if let Some((_, ref body)) = variant.body {
422                    out.new_line();
423                    out.new_line();
424
425                    body.write(config, out);
426                }
427            }
428
429            out.new_line();
430            out.new_line();
431
432            // Emit the actual union
433            write!(out, "type {}*", self.export_name());
434            self.generic_params.write(config, out);
435            out.write(" = object");
436
437            out.new_line();
438            out.indent();
439
440            write!(out, "tag*: {}", enum_name);
441
442            out.new_line();
443
444            for (i, &(ref field_name, ref body)) in self
445                .variants
446                .iter()
447                .filter_map(|variant| variant.body.as_ref())
448                .enumerate()
449            {
450                if i != 0 {
451                    out.new_line();
452                }
453                write!(out, "{}*: {}", field_name, body.export_name());
454            }
455
456            out.dedent();
457        }
458
459        condition.write_after(config, out);
460    }
461}