1use 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 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 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 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 if is_tagged {
419 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 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}