use crate::discriminant::Discriminant;
use crate::err;
use crate::params::field::FieldParam;
use crate::params::{parse_attrs, Alignment, Size};
use merge::Merge;
use proc_macro2::{Ident, Span};
use std::convert::TryFrom;
use structmeta::StructMeta;
use syn::spanned::Spanned;
use syn::{Expr, ExprLit, Fields, Lit, LitInt, Result, Variant};
#[derive(Default, StructMeta, Merge)]
struct VariantAttrs {
#[merge(strategy = merge::option::overwrite_none)]
size: Option<Expr>,
#[merge(strategy = merge::option::overwrite_none)]
size_bits: Option<LitInt>,
#[merge(strategy = merge::bool::overwrite_false)]
align: bool,
#[merge(strategy = merge::option::overwrite_none)]
discriminant: Option<Lit>,
}
pub struct VariantParam {
pub span: Span,
pub variant_name: Ident,
pub body: VariantBody,
pub discriminant: Discriminant,
}
pub enum VariantBodyType {
Unit,
Unnamed,
Named,
}
pub enum VariantBody {
Unit,
Fields(Vec<FieldParam>),
}
impl VariantBody {
pub fn body_type(&self) -> VariantBodyType {
match self {
VariantBody::Unit => VariantBodyType::Unit,
VariantBody::Fields(fields) => {
let named = fields.iter().any(|f| f.field_name.is_some());
if named {
VariantBodyType::Named
} else {
VariantBodyType::Unnamed
}
}
}
}
}
impl VariantParam {
pub fn size_can_be_predicted(&self) -> bool {
match &self.body {
VariantBody::Fields(fields) => fields.iter().all(|field| field.size_can_be_predicted()),
VariantBody::Unit => true,
}
}
pub fn parse(input: &Variant) -> Result<VariantParam> {
let attrs: VariantAttrs = parse_attrs(&input.attrs)?;
let variant_name = input.ident.clone();
let align = attrs.align.into();
let size = Size::from_attrs(attrs.size, attrs.size_bits, input.span())?;
if attrs.discriminant.is_some() && input.discriminant.is_some() {
err(
"variant has both discriminant and discriminant attribute set",
input.span(),
)?;
}
let discriminant = attrs
.discriminant
.map(|lit| {
Expr::Lit(ExprLit {
attrs: Vec::new(),
lit,
})
})
.or_else(|| {
input
.discriminant
.clone()
.map(|(_, discriminant)| discriminant)
})
.map(Discriminant::try_from)
.transpose()?
.unwrap_or(Discriminant::Default);
let body = if matches!(input.fields, Fields::Unit) {
if align == Alignment::Auto {
err(
"'align' attribute is not allowed on unit variants",
input.span(),
)?;
}
if size.is_some() {
err(
"'size' attribute is not allowed on unit variants",
input.span(),
)?;
}
VariantBody::Unit
} else {
let mut fields = input
.fields
.iter()
.map(FieldParam::parse)
.collect::<Result<Vec<FieldParam>>>()?;
if let (Some(field), Alignment::Auto) = (fields.first_mut(), align) {
field.align = align;
}
if let (Some(field), Some(size)) = (fields.first_mut(), size) {
field.size = Some(size);
}
VariantBody::Fields(fields)
};
Ok(VariantParam {
span: input.span(),
variant_name,
discriminant,
body,
})
}
pub fn span(&self) -> Span {
self.span
}
}