blob: b1b6c044f4a9e9827762a53227b9338c84371fa3 [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::io::Write;
use syn::ext::IdentExt;
use crate::bindgen::config::{Config, Language};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, AnnotationValue, Cfg, ConditionWrite, Documentation, Field, GenericArgument,
GenericParams, GenericPath, Item, ItemContainer, Literal, Path, Repr, ReprStyle, Struct,
ToCondition, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::mangle;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::rename::{IdentifierType, RenameRule};
use crate::bindgen::reserved;
use crate::bindgen::writer::{ListType, Source, SourceWriter};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum VariantBody {
Empty(AnnotationSet),
Body {
/// The variant field / export name.
name: String,
/// The struct with all the items.
body: Struct,
/// A separate named struct is not created for this variant,
/// an unnamed struct is inlined at the point of use instead.
/// This is a reasonable thing to do only for tuple variants with a single field.
inline: bool,
/// Generated cast methods return the variant's only field instead of the variant itself.
/// For backward compatibility casts are inlined in a slightly
/// larger set of cases than whole variants.
inline_casts: bool,
},
}
impl VariantBody {
fn empty() -> Self {
Self::Empty(AnnotationSet::new())
}
fn annotations(&self) -> &AnnotationSet {
match *self {
Self::Empty(ref anno) => anno,
Self::Body { ref body, .. } => &body.annotations,
}
}
fn is_empty(&self) -> bool {
match *self {
Self::Empty(..) => true,
Self::Body { .. } => false,
}
}
fn specialize(
&self,
generic_values: &[GenericArgument],
mappings: &[(&Path, &GenericArgument)],
config: &Config,
) -> Self {
match *self {
Self::Empty(ref annos) => Self::Empty(annos.clone()),
Self::Body {
ref name,
ref body,
inline,
inline_casts,
} => Self::Body {
name: name.clone(),
body: body.specialize(generic_values, mappings, config),
inline,
inline_casts,
},
}
}
}
#[derive(Debug, Clone)]
pub struct EnumVariant {
pub name: String,
pub export_name: String,
pub discriminant: Option<Literal>,
pub body: VariantBody,
pub cfg: Option<Cfg>,
pub documentation: Documentation,
}
impl EnumVariant {
fn load(
inline_tag_field: bool,
variant: &syn::Variant,
generic_params: GenericParams,
mod_cfg: Option<&Cfg>,
self_path: &Path,
enum_annotations: &AnnotationSet,
config: &Config,
) -> Result<Self, String> {
let discriminant = match variant.discriminant {
Some((_, ref expr)) => Some(Literal::load(expr)?),
None => None,
};
fn parse_fields(
inline_tag_field: bool,
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
self_path: &Path,
inline_name: Option<&str>,
) -> Result<Vec<Field>, String> {
let mut res = Vec::new();
if inline_tag_field {
res.push(Field::from_name_and_type(
inline_name.map_or_else(|| "tag".to_string(), |name| format!("{}_tag", name)),
Type::Path(GenericPath::new(Path::new("Tag"), vec![])),
));
}
for (i, field) in fields.iter().enumerate() {
if let Some(mut ty) = Type::load(&field.ty)? {
ty.replace_self_with(self_path);
res.push(Field {
name: inline_name.map_or_else(
|| match field.ident {
Some(ref ident) => ident.unraw().to_string(),
None => i.to_string(),
},
|name| name.to_string(),
),
ty,
cfg: Cfg::load(&field.attrs),
annotations: AnnotationSet::load(&field.attrs)?,
documentation: Documentation::load(&field.attrs),
});
}
}
Ok(res)
}
let variant_cfg = Cfg::append(mod_cfg, Cfg::load(&variant.attrs));
let mut annotations = AnnotationSet::load(&variant.attrs)?;
if let Some(b) = enum_annotations.bool("derive-ostream") {
annotations.add_default("derive-ostream", AnnotationValue::Bool(b));
}
let body_rule = enum_annotations
.parse_atom::<RenameRule>("rename-variant-name-fields")
.unwrap_or(config.enumeration.rename_variant_name_fields);
let body = match variant.fields {
syn::Fields::Unit => VariantBody::Empty(annotations),
syn::Fields::Named(ref fields) => {
let path = Path::new(format!("{}_Body", variant.ident));
let name = body_rule
.apply(
&variant.ident.unraw().to_string(),
IdentifierType::StructMember,
)
.into_owned();
VariantBody::Body {
body: Struct::new(
path,
generic_params,
parse_fields(inline_tag_field, &fields.named, self_path, None)?,
inline_tag_field,
true,
None,
false,
None,
annotations,
Documentation::none(),
),
name,
inline: false,
inline_casts: false,
}
}
syn::Fields::Unnamed(ref fields) => {
let path = Path::new(format!("{}_Body", variant.ident));
let name = body_rule
.apply(
&variant.ident.unraw().to_string(),
IdentifierType::StructMember,
)
.into_owned();
let inline_casts = fields.unnamed.len() == 1;
// In C++ types with destructors cannot be put into unnamed structs like the
// inlining requires, and it's hard to detect such types.
// Besides that for C++ we generate casts/getters that can be used instead of
// direct field accesses and also have a benefit of being checked.
// As a result we don't currently inline variant definitions in C++ mode at all.
let inline = inline_casts && config.language != Language::Cxx;
let inline_name = if inline { Some(&*name) } else { None };
VariantBody::Body {
body: Struct::new(
path,
generic_params,
parse_fields(inline_tag_field, &fields.unnamed, self_path, inline_name)?,
inline_tag_field,
true,
None,
false,
None,
annotations,
Documentation::none(),
),
name,
inline,
inline_casts,
}
}
};
Ok(EnumVariant::new(
variant.ident.unraw().to_string(),
discriminant,
body,
variant_cfg,
Documentation::load(&variant.attrs),
))
}
pub fn new(
name: String,
discriminant: Option<Literal>,
body: VariantBody,
cfg: Option<Cfg>,
documentation: Documentation,
) -> Self {
let export_name = name.clone();
Self {
name,
export_name,
discriminant,
body,
cfg,
documentation,
}
}
fn simplify_standard_types(&mut self, config: &Config) {
if let VariantBody::Body { ref mut body, .. } = self.body {
body.simplify_standard_types(config);
}
}
fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
if let VariantBody::Body { ref body, .. } = self.body {
body.add_dependencies(library, out);
}
}
fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
if let VariantBody::Body { ref mut body, .. } = self.body {
body.resolve_declaration_types(resolver);
}
}
fn specialize(
&self,
generic_values: &[GenericArgument],
mappings: &[(&Path, &GenericArgument)],
config: &Config,
) -> Self {
Self::new(
mangle::mangle_name(&self.name, generic_values, &config.export.mangle),
self.discriminant.clone(),
self.body.specialize(generic_values, mappings, config),
self.cfg.clone(),
self.documentation.clone(),
)
}
fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
if let VariantBody::Body { ref body, .. } = self.body {
body.add_monomorphs(library, out);
}
}
fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
if let VariantBody::Body { ref mut body, .. } = self.body {
body.mangle_paths(monomorphs);
}
}
}
impl Source for EnumVariant {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
let condition = self.cfg.to_condition(config);
// Cython doesn't support conditional enum variants.
if config.language != Language::Cython {
condition.write_before(config, out);
}
self.documentation.write(config, out);
write!(out, "{}", self.export_name);
if let Some(discriminant) = &self.discriminant {
if config.language == Language::Cython {
// For extern Cython declarations the enumerator value is ignored,
// but still useful as documentation, so we write it as a comment.
out.write(" #")
}
out.write(" = ");
discriminant.write(config, out);
}
out.write(",");
if config.language != Language::Cython {
condition.write_after(config, out);
}
}
}
#[derive(Debug, Clone)]
pub struct Enum {
pub path: Path,
pub export_name: String,
pub generic_params: GenericParams,
pub repr: Repr,
pub variants: Vec<EnumVariant>,
pub tag: Option<String>,
pub cfg: Option<Cfg>,
pub annotations: AnnotationSet,
pub documentation: Documentation,
}
impl Enum {
/// Name of the generated tag enum.
fn tag_name(&self) -> &str {
self.tag.as_deref().unwrap_or_else(|| self.export_name())
}
/// Enum with data turns into a union of structs with each struct having its own tag field.
fn inline_tag_field(repr: &Repr) -> bool {
repr.style != ReprStyle::C
}
pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
if self.generic_params.len() > 0 {
return;
}
for v in &self.variants {
v.add_monomorphs(library, out);
}
}
fn can_derive_eq(&self) -> bool {
if self.tag.is_none() {
return false;
}
self.variants.iter().all(|variant| match variant.body {
VariantBody::Empty(..) => true,
VariantBody::Body { ref body, .. } => body.can_derive_eq(),
})
}
pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
for variant in &mut self.variants {
variant.mangle_paths(monomorphs);
}
}
pub fn load(
item: &syn::ItemEnum,
mod_cfg: Option<&Cfg>,
config: &Config,
) -> Result<Enum, String> {
let repr = Repr::load(&item.attrs)?;
if repr.style == ReprStyle::Rust && repr.ty.is_none() {
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned());
}
// TODO: Implement translation of aligned enums.
if repr.align.is_some() {
return Err("Enum is marked with #[repr(align(...))] or #[repr(packed)].".to_owned());
}
let path = Path::new(item.ident.unraw().to_string());
let generic_params = GenericParams::load(&item.generics)?;
let mut variants = Vec::new();
let mut has_data = false;
let annotations = AnnotationSet::load(&item.attrs)?;
for variant in item.variants.iter() {
let variant = EnumVariant::load(
Self::inline_tag_field(&repr),
variant,
generic_params.clone(),
mod_cfg,
&path,
&annotations,
config,
)?;
has_data = has_data || !variant.body.is_empty();
variants.push(variant);
}
if let Some(names) = annotations.list("enum-trailing-values") {
for name in names {
variants.push(EnumVariant::new(
name,
None,
VariantBody::empty(),
None,
Documentation::none(),
));
}
}
if config.enumeration.add_sentinel(&annotations) {
variants.push(EnumVariant::new(
"Sentinel".to_owned(),
None,
VariantBody::empty(),
None,
Documentation::simple(" Must be last for serialization purposes"),
));
}
let tag = if has_data {
Some("Tag".to_string())
} else {
None
};
Ok(Enum::new(
path,
generic_params,
repr,
variants,
tag,
Cfg::append(mod_cfg, Cfg::load(&item.attrs)),
annotations,
Documentation::load(&item.attrs),
))
}
#[allow(clippy::too_many_arguments)]
pub fn new(
path: Path,
generic_params: GenericParams,
repr: Repr,
variants: Vec<EnumVariant>,
tag: Option<String>,
cfg: Option<Cfg>,
annotations: AnnotationSet,
documentation: Documentation,
) -> Self {
let export_name = path.name().to_owned();
Self {
path,
export_name,
generic_params,
repr,
variants,
tag,
cfg,
annotations,
documentation,
}
}
}
impl Item for Enum {
fn path(&self) -> &Path {
&self.path
}
fn export_name(&self) -> &str {
&self.export_name
}
fn cfg(&self) -> Option<&Cfg> {
self.cfg.as_ref()
}
fn annotations(&self) -> &AnnotationSet {
&self.annotations
}
fn annotations_mut(&mut self) -> &mut AnnotationSet {
&mut self.annotations
}
fn container(&self) -> ItemContainer {
ItemContainer::Enum(self.clone())
}
fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
if self.tag.is_some() {
if self.repr.style == ReprStyle::C {
resolver.add_struct(&self.path);
} else {
resolver.add_union(&self.path);
}
} else if self.repr.style == ReprStyle::C {
resolver.add_enum(&self.path);
} else {
// This is important to handle conflicting names with opaque items.
resolver.add_none(&self.path);
}
}
fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
for &mut ref mut var in &mut self.variants {
var.resolve_declaration_types(resolver);
}
}
fn rename_for_config(&mut self, config: &Config) {
config.export.rename(&mut self.export_name);
if config.language != Language::Cxx && self.tag.is_some() {
// it makes sense to always prefix Tag with type name in C
let new_tag = format!("{}_Tag", self.export_name);
if self.repr.style == ReprStyle::Rust {
for variant in &mut self.variants {
if let VariantBody::Body { ref mut body, .. } = variant.body {
let path = Path::new(new_tag.clone());
let generic_path = GenericPath::new(path, vec![]);
body.fields[0].ty = Type::Path(generic_path);
}
}
}
self.tag = Some(new_tag);
}
for variant in &mut self.variants {
reserved::escape(&mut variant.export_name);
if let Some(discriminant) = &mut variant.discriminant {
discriminant.rename_for_config(config);
}
if let VariantBody::Body {
ref mut name,
ref mut body,
..
} = variant.body
{
body.rename_for_config(config);
reserved::escape(name);
}
}
if config.enumeration.prefix_with_name
|| self.annotations.bool("prefix-with-name").unwrap_or(false)
{
let separator = if config.export.mangle.remove_underscores {
""
} else {
"_"
};
for variant in &mut self.variants {
variant.export_name =
format!("{}{}{}", self.export_name, separator, variant.export_name);
if let VariantBody::Body { ref mut body, .. } = variant.body {
body.export_name =
format!("{}{}{}", self.export_name, separator, body.export_name());
}
}
}
let rules = self
.annotations
.parse_atom::<RenameRule>("rename-all")
.unwrap_or(config.enumeration.rename_variants);
if let Some(r) = rules.not_none() {
self.variants = self
.variants
.iter()
.map(|variant| {
EnumVariant::new(
r.apply(
&variant.export_name,
IdentifierType::EnumVariant {
prefix: &self.export_name,
},
)
.into_owned(),
variant.discriminant.clone(),
match variant.body {
VariantBody::Empty(..) => variant.body.clone(),
VariantBody::Body {
ref name,
ref body,
inline,
inline_casts,
} => VariantBody::Body {
name: r.apply(name, IdentifierType::StructMember).into_owned(),
body: body.clone(),
inline,
inline_casts,
},
},
variant.cfg.clone(),
variant.documentation.clone(),
)
})
.collect();
}
}
fn instantiate_monomorph(
&self,
generic_values: &[GenericArgument],
library: &Library,
out: &mut Monomorphs,
) {
let mappings = self.generic_params.call(self.path.name(), generic_values);
for variant in &self.variants {
if let VariantBody::Body { ref body, .. } = variant.body {
body.instantiate_monomorph(generic_values, library, out);
}
}
let mangled_path = mangle::mangle_path(
&self.path,
generic_values,
&library.get_config().export.mangle,
);
let monomorph = Enum::new(
mangled_path,
GenericParams::default(),
self.repr,
self.variants
.iter()
.map(|v| v.specialize(generic_values, &mappings, library.get_config()))
.collect(),
self.tag.clone(),
self.cfg.clone(),
self.annotations.clone(),
self.documentation.clone(),
);
out.insert_enum(library, self, monomorph, generic_values.to_owned());
}
fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
for variant in &self.variants {
variant.add_dependencies(library, out);
}
}
}
impl Source for Enum {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
let size = self.repr.ty.map(|ty| ty.to_primitive().to_repr_c(config));
let has_data = self.tag.is_some();
let inline_tag_field = Self::inline_tag_field(&self.repr);
let tag_name = self.tag_name();
let condition = self.cfg.to_condition(config);
condition.write_before(config, out);
self.documentation.write(config, out);
self.generic_params.write(config, out);
// If the enum has data, we need to emit a struct or union for the data
// and enum for the tag. C++ supports nested type definitions, so we open
// the struct or union here and define the tag enum inside it (*).
if has_data && config.language == Language::Cxx {
self.open_struct_or_union(config, out, inline_tag_field);
}
// Emit the tag enum and everything related to it.
self.write_tag_enum(config, out, size, has_data, tag_name);
// If the enum has data, we need to emit structs for the variants and gather them together.
if has_data {
self.write_variant_defs(config, out);
out.new_line();
out.new_line();
// Open the struct or union for the data (**), gathering all the variants with data
// together, unless it's C++, then we have already opened that struct/union at (*) and
// are currently inside it.
if config.language != Language::Cxx {
self.open_struct_or_union(config, out, inline_tag_field);
}
// Emit tag field that is separate from all variants.
self.write_tag_field(config, out, size, inline_tag_field, tag_name);
out.new_line();
// Open union of all variants with data, only in the non-inline tag scenario.
// Cython extern declarations don't manage layouts, layouts are defined entierly by the
// corresponding C code. So we can inline the unnamed union into the struct and get the
// same observable result. Moreother we have to do it because Cython doesn't support
// unnamed unions.
if !inline_tag_field && config.language != Language::Cython {
out.write("union");
out.open_brace();
}
// Emit fields for all variants with data.
self.write_variant_fields(config, out, inline_tag_field);
// Close union of all variants with data, only in the non-inline tag scenario.
// See the comment about Cython on `open_brace`.
if !inline_tag_field && config.language != Language::Cython {
out.close_brace(true);
}
// Emit convenience methods for the struct or enum for the data.
self.write_derived_functions_data(config, out, tag_name);
// Emit the post_body section, if relevant.
if let Some(body) = config.export.post_body(&self.path) {
out.new_line();
out.write_raw_block(body);
}
// Close the struct or union opened either at (*) or at (**).
if config.language == Language::C && config.style.generate_typedef() {
out.close_brace(false);
write!(out, " {};", self.export_name);
} else {
out.close_brace(true);
}
}
condition.write_after(config, out);
}
}
impl Enum {
/// Emit the tag enum and convenience methods for it.
/// For enums with data this is only a part of the output,
/// but for enums without data it's the whole output (modulo doc comments etc.).
fn write_tag_enum<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
size: Option<&str>,
has_data: bool,
tag_name: &str,
) {
// Open the tag enum.
match config.language {
Language::C => {
if let Some(prim) = size {
// If we need to specify size, then we have no choice but to create a typedef,
// so `config.style` is not respected.
write!(out, "enum {}", tag_name);
if config.cpp_compatible_c() {
out.new_line();
out.write("#ifdef __cplusplus");
out.new_line();
write!(out, " : {}", prim);
out.new_line();
out.write("#endif // __cplusplus");
out.new_line();
}
} else {
if config.style.generate_typedef() {
out.write("typedef ");
}
out.write("enum");
if config.style.generate_tag() {
write!(out, " {}", tag_name);
}
}
}
Language::Cxx => {
if config.enumeration.enum_class(&self.annotations) {
out.write("enum class");
} else {
out.write("enum");
}
if self.annotations.must_use(config) {
if let Some(ref anno) = config.enumeration.must_use {
write!(out, " {}", anno)
}
}
write!(out, " {}", tag_name);
if let Some(prim) = size {
write!(out, " : {}", prim);
}
}
Language::Cython => {
if size.is_some() {
// If we need to specify size, then we have no choice but to create a typedef,
// so `config.style` is not respected.
write!(out, "cdef enum");
} else {
write!(out, "{}enum {}", config.style.cython_def(), tag_name);
}
}
}
out.open_brace();
// Emit enumerators for the tag enum.
for (i, variant) in self.variants.iter().enumerate() {
if i != 0 {
out.new_line()
}
variant.write(config, out);
}
// Close the tag enum.
if config.language == Language::C && size.is_none() && config.style.generate_typedef() {
out.close_brace(false);
write!(out, " {};", tag_name);
} else {
out.close_brace(true);
}
// Emit typedef specifying the tag enum's size if necessary.
// In C++ enums can "inherit" from numeric types (`enum E: uint8_t { ... }`),
// but in C `typedef uint8_t E` is the only way to give a fixed size to `E`.
if let Some(prim) = size {
if config.cpp_compatible_c() {
out.new_line_if_not_start();
out.write("#ifndef __cplusplus");
}
if config.language != Language::Cxx {
out.new_line();
write!(out, "{} {} {};", config.language.typedef(), prim, tag_name);
}
if config.cpp_compatible_c() {
out.new_line_if_not_start();
out.write("#endif // __cplusplus");
}
}
// Emit convenience methods for the tag enum.
self.write_derived_functions_enum(config, out, has_data, tag_name);
}
/// The code here mirrors the beginning of `Struct::write` and `Union::write`.
fn open_struct_or_union<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
inline_tag_field: bool,
) {
match config.language {
Language::C if config.style.generate_typedef() => out.write("typedef "),
Language::C | Language::Cxx => {}
Language::Cython => out.write(config.style.cython_def()),
}
out.write(if inline_tag_field { "union" } else { "struct" });
if self.annotations.must_use(config) {
if let Some(ref anno) = config.structure.must_use {
write!(out, " {}", anno);
}
}
if config.language != Language::C || config.style.generate_tag() {
write!(out, " {}", self.export_name());
}
out.open_brace();
// Emit the pre_body section, if relevant.
if let Some(body) = config.export.pre_body(&self.path) {
out.write_raw_block(body);
out.new_line();
}
}
/// Emit struct definitions for variants having data.
fn write_variant_defs<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
for variant in &self.variants {
if let VariantBody::Body {
ref body,
inline: false,
..
} = variant.body
{
out.new_line();
out.new_line();
let condition = variant.cfg.to_condition(config);
// Cython doesn't support conditional enum variants.
if config.language != Language::Cython {
condition.write_before(config, out);
}
body.write(config, out);
if config.language != Language::Cython {
condition.write_after(config, out);
}
}
}
}
/// Emit tag field that is separate from all variants.
/// For non-inline tag scenario this is *the* tag field, and it does not exist in the variants.
/// For the inline tag scenario this is just a convenience and another way
/// to refer to the same tag that exist in all the variants.
fn write_tag_field<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
size: Option<&str>,
inline_tag_field: bool,
tag_name: &str,
) {
// C++ allows accessing only common initial sequence of union
// fields so we have to wrap the tag field into an anonymous struct.
let wrap_tag = inline_tag_field && config.language == Language::Cxx;
if wrap_tag {
out.write("struct");
out.open_brace();
}
if config.language == Language::C && size.is_none() && !config.style.generate_typedef() {
out.write("enum ");
}
write!(out, "{} tag;", tag_name);
if wrap_tag {
out.close_brace(true);
}
}
/// Emit fields for all variants with data.
fn write_variant_fields<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
inline_tag_field: bool,
) {
let mut first = true;
for variant in &self.variants {
if let VariantBody::Body {
name, body, inline, ..
} = &variant.body
{
if !first {
out.new_line();
}
first = false;
let condition = variant.cfg.to_condition(config);
// Cython doesn't support conditional enum variants.
if config.language != Language::Cython {
condition.write_before(config, out);
}
if *inline {
// Write definition of an inlined variant with data.
// Cython extern declarations don't manage layouts, layouts are defined entierly
// by the corresponding C code. So we can inline the unnamed struct and get the
// same observable result. Moreother we have to do it because Cython doesn't
// support unnamed structs.
// For the same reason with Cython we can omit per-variant tags (the first
// field) to avoid extra noise, the main `tag` is enough in this case.
if config.language != Language::Cython {
out.write("struct");
out.open_brace();
}
let start_field =
usize::from(inline_tag_field && config.language == Language::Cython);
out.write_vertical_source_list(&body.fields[start_field..], ListType::Cap(";"));
if config.language != Language::Cython {
out.close_brace(true);
}
} else if config.style.generate_typedef() || config.language == Language::Cython {
write!(out, "{} {};", body.export_name(), name);
} else {
write!(out, "struct {} {};", body.export_name(), name);
}
if config.language != Language::Cython {
condition.write_after(config, out);
}
}
}
}
// Emit convenience methods for enums themselves.
fn write_derived_functions_enum<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
has_data: bool,
tag_name: &str,
) {
if config.language != Language::Cxx {
return;
}
// Emit an ostream function if required.
if config.enumeration.derive_ostream(&self.annotations) {
// For enums without data, this emits the serializer function for the
// enum. For enums with data, this emits the serializer function for
// the tag enum. In the latter case we need a couple of minor changes
// due to the function living inside the top-level struct or enum.
let stream = config
.function
.rename_args
.apply("stream", IdentifierType::FunctionArg);
let instance = config
.function
.rename_args
.apply("instance", IdentifierType::FunctionArg);
out.new_line();
out.new_line();
// For enums without data, we mark the function inline because the
// header might get included into multiple compilation units that
// get linked together, and not marking it inline would result in
// multiply-defined symbol errors. For enums with data we don't have
// the same problem, but mark it as a friend function of the
// containing union/struct.
// Note also that for enums with data, the case labels for switch
// statements apparently need to be qualified to the top-level
// generated struct or union. This is why the generated case labels
// below use the A::B::C format for enums with data, with A being
// self.export_name(). Failure to have that qualification results
// in a surprising compilation failure for the generated header.
write!(
out,
"{} std::ostream& operator<<(std::ostream& {}, const {}& {})",
if has_data { "friend" } else { "inline" },
stream,
tag_name,
instance,
);
out.open_brace();
if has_data {
// C++ name resolution rules are weird.
write!(
out,
"using {} = {}::{};",
tag_name,
self.export_name(),
tag_name
);
out.new_line();
}
write!(out, "switch ({})", instance);
out.open_brace();
let vec: Vec<_> = self
.variants
.iter()
.map(|x| {
format!(
"case {}::{}: {} << \"{}\"; break;",
tag_name, x.export_name, stream, x.export_name
)
})
.collect();
out.write_vertical_source_list(&vec[..], ListType::Join(""));
out.close_brace(false);
out.new_line();
write!(out, "return {};", stream);
out.close_brace(false);
if has_data {
// For enums with data, this emits the serializer function for
// the top-level union or struct.
out.new_line();
out.new_line();
write!(
out,
"friend std::ostream& operator<<(std::ostream& {}, const {}& {})",
stream,
self.export_name(),
instance,
);
out.open_brace();
// C++ name resolution rules are weird.
write!(
out,
"using {} = {}::{};",
tag_name,
self.export_name(),
tag_name
);
out.new_line();
write!(out, "switch ({}.tag)", instance);
out.open_brace();
let vec: Vec<_> = self
.variants
.iter()
.map(|x| {
let tag_str = format!("\"{}\"", x.export_name);
if let VariantBody::Body {
ref name, ref body, ..
} = x.body
{
format!(
"case {}::{}: {} << {}{}{}.{}; break;",
tag_name,
x.export_name,
stream,
if body.has_tag_field { "" } else { &tag_str },
if body.has_tag_field { "" } else { " << " },
instance,
name,
)
} else {
format!(
"case {}::{}: {} << {}; break;",
tag_name, x.export_name, stream, tag_str,
)
}
})
.collect();
out.write_vertical_source_list(&vec[..], ListType::Join(""));
out.close_brace(false);
out.new_line();
write!(out, "return {};", stream);
out.close_brace(false);
}
}
}
// Emit convenience methods for structs or unions produced for enums with data.
fn write_derived_functions_data<F: Write>(
&self,
config: &Config,
out: &mut SourceWriter<F>,
tag_name: &str,
) {
if config.language != Language::Cxx {
return;
}
if config.enumeration.derive_helper_methods(&self.annotations) {
for variant in &self.variants {
out.new_line();
out.new_line();
let condition = variant.cfg.to_condition(config);
condition.write_before(config, out);
let arg_renamer = |name: &str| {
config
.function
.rename_args
.apply(name, IdentifierType::FunctionArg)
.into_owned()
};
macro_rules! write_attrs {
($op:expr) => {{
if let Some(Some(attrs)) =
variant
.body
.annotations()
.atom(concat!("variant-", $op, "-attributes"))
{
write!(out, "{} ", attrs);
}
}};
}
write_attrs!("constructor");
write!(out, "static {} {}(", self.export_name, variant.export_name);
if let VariantBody::Body { ref body, .. } = variant.body {
let skip_fields = body.has_tag_field as usize;
let vec: Vec<_> = body
.fields
.iter()
.skip(skip_fields)
.map(|field| {
Field::from_name_and_type(
// const-ref args to constructor
arg_renamer(&field.name),
Type::const_ref_to(&field.ty),
)
})
.collect();
out.write_vertical_source_list(&vec[..], ListType::Join(","));
}
write!(out, ")");
out.open_brace();
write!(out, "{} result;", self.export_name);
if let VariantBody::Body {
name: ref variant_name,
ref body,
..
} = variant.body
{
let skip_fields = body.has_tag_field as usize;
for field in body.fields.iter().skip(skip_fields) {
out.new_line();
match field.ty {
Type::Array(ref ty, ref length) => {
// arrays are not assignable in C++ so we
// need to manually copy the elements
write!(out, "for (int i = 0; i < {}; i++)", length.as_str());
out.open_brace();
write!(out, "::new (&result.{}.{}[i]) (", variant_name, field.name);
ty.write(config, out);
write!(out, ")({}[i]);", arg_renamer(&field.name));
out.close_brace(false);
}
ref ty => {
write!(out, "::new (&result.{}.{}) (", variant_name, field.name);
ty.write(config, out);
write!(out, ")({});", arg_renamer(&field.name));
}
}
}
}
out.new_line();
write!(out, "result.tag = {}::{};", tag_name, variant.export_name);
out.new_line();
write!(out, "return result;");
out.close_brace(false);
out.new_line();
out.new_line();
write_attrs!("is");
// FIXME: create a config for method case
write!(out, "bool Is{}() const", variant.export_name);
out.open_brace();
write!(out, "return tag == {}::{};", tag_name, variant.export_name);
out.close_brace(false);
let assert_name = match config.enumeration.cast_assert_name {
Some(ref n) => &**n,
None => "assert",
};
let mut derive_casts = |const_casts: bool| {
let (member_name, body, inline_casts) = match variant.body {
VariantBody::Body {
ref name,
ref body,
inline_casts,
..
} => (name, body, inline_casts),
VariantBody::Empty(..) => return,
};
let skip_fields = body.has_tag_field as usize;
let field_count = body.fields.len() - skip_fields;
if field_count == 0 {
return;
}
out.new_line();
out.new_line();
if const_casts {
write_attrs!("const-cast");
} else {
write_attrs!("mut-cast");
}
if inline_casts {
let field = body.fields.last().unwrap();
let return_type = field.ty.clone();
let return_type = Type::Ptr {
ty: Box::new(return_type),
is_const: const_casts,
is_ref: true,
is_nullable: false,
};
return_type.write(config, out);
} else if const_casts {
write!(out, "const {}&", body.export_name());
} else {
write!(out, "{}&", body.export_name());
}
write!(out, " As{}()", variant.export_name);
if const_casts {
write!(out, " const");
}
out.open_brace();
write!(out, "{}(Is{}());", assert_name, variant.export_name);
out.new_line();
write!(out, "return {}", member_name);
if inline_casts {
write!(out, "._0");
}
write!(out, ";");
out.close_brace(false);
};
if config.enumeration.derive_const_casts(&self.annotations) {
derive_casts(true)
}
if config.enumeration.derive_mut_casts(&self.annotations) {
derive_casts(false)
}
condition.write_after(config, out);
}
}
let other = config
.function
.rename_args
.apply("other", IdentifierType::FunctionArg);
macro_rules! write_attrs {
($op:expr) => {{
if let Some(Some(attrs)) = self.annotations.atom(concat!($op, "-attributes")) {
write!(out, "{} ", attrs);
}
}};
}
if self.can_derive_eq() && config.structure.derive_eq(&self.annotations) {
out.new_line();
out.new_line();
write_attrs!("eq");
write!(
out,
"bool operator==(const {}& {}) const",
self.export_name, other
);
out.open_brace();
write!(out, "if (tag != {}.tag)", other);
out.open_brace();
write!(out, "return false;");
out.close_brace(false);
out.new_line();
write!(out, "switch (tag)");
out.open_brace();
let mut exhaustive = true;
for variant in &self.variants {
if let VariantBody::Body {
name: ref variant_name,
..
} = variant.body
{
let condition = variant.cfg.to_condition(config);
condition.write_before(config, out);
write!(
out,
"case {}::{}: return {} == {}.{};",
self.tag.as_ref().unwrap(),
variant.export_name,
variant_name,
other,
variant_name
);
condition.write_after(config, out);
out.new_line();
} else {
exhaustive = false;
}
}
if !exhaustive {
write!(out, "default: break;");
}
out.close_brace(false);
out.new_line();
write!(out, "return true;");
out.close_brace(false);
if config.structure.derive_neq(&self.annotations) {
out.new_line();
out.new_line();
write_attrs!("neq");
write!(
out,
"bool operator!=(const {}& {}) const",
self.export_name, other
);
out.open_brace();
write!(out, "return !(*this == {});", other);
out.close_brace(false);
}
}
if config
.enumeration
.private_default_tagged_enum_constructor(&self.annotations)
{
out.new_line();
out.new_line();
write!(out, "private:");
out.new_line();
write!(out, "{}()", self.export_name);
out.open_brace();
out.close_brace(false);
out.new_line();
write!(out, "public:");
out.new_line();
}
if config
.enumeration
.derive_tagged_enum_destructor(&self.annotations)
{
out.new_line();
out.new_line();
write_attrs!("destructor");
write!(out, "~{}()", self.export_name);
out.open_brace();
write!(out, "switch (tag)");
out.open_brace();
let mut exhaustive = true;
for variant in &self.variants {
if let VariantBody::Body {
ref name, ref body, ..
} = variant.body
{
let condition = variant.cfg.to_condition(config);
condition.write_before(config, out);
write!(
out,
"case {}::{}: {}.~{}(); break;",
self.tag.as_ref().unwrap(),
variant.export_name,
name,
body.export_name(),
);
condition.write_after(config, out);
out.new_line();
} else {
exhaustive = false;
}
}
if !exhaustive {
write!(out, "default: break;");
}
out.close_brace(false);
out.close_brace(false);
}
if config
.enumeration
.derive_tagged_enum_copy_constructor(&self.annotations)
{
out.new_line();
out.new_line();
write_attrs!("copy-constructor");
write!(
out,
"{}(const {}& {})",
self.export_name, self.export_name, other
);
out.new_line();
write!(out, " : tag({}.tag)", other);
out.open_brace();
write!(out, "switch (tag)");
out.open_brace();
let mut exhaustive = true;
for variant in &self.variants {
if let VariantBody::Body {
ref name, ref body, ..
} = variant.body
{
let condition = variant.cfg.to_condition(config);
condition.write_before(config, out);
write!(
out,
"case {}::{}: ::new (&{}) ({})({}.{}); break;",
self.tag.as_ref().unwrap(),
variant.export_name,
name,
body.export_name(),
other,
name,
);
condition.write_after(config, out);
out.new_line();
} else {
exhaustive = false;
}
}
if !exhaustive {
write!(out, "default: break;");
}
out.close_brace(false);
out.close_brace(false);
if config
.enumeration
.derive_tagged_enum_copy_assignment(&self.annotations)
{
out.new_line();
write_attrs!("copy-assignment");
write!(
out,
"{}& operator=(const {}& {})",
self.export_name, self.export_name, other
);
out.open_brace();
write!(out, "if (this != &{})", other);
out.open_brace();
write!(out, "this->~{}();", self.export_name);
out.new_line();
write!(out, "new (this) {}({});", self.export_name, other);
out.close_brace(false);
out.new_line();
write!(out, "return *this;");
out.close_brace(false);
}
}
}
pub fn simplify_standard_types(&mut self, config: &Config) {
for variant in &mut self.variants {
variant.simplify_standard_types(config);
}
}
}