use crate::ast::{PackedAttributes, PackedEnum, PackedStruct, PackedTuple, PackedUnit};
use syn::{
Result, Token,
parse::{Parse, ParseStream},
};
pub struct Container {
pub attributes: PackedAttributes,
pub data: Data,
pub _visibility: syn::Visibility,
}
pub enum Data {
Unit(PackedUnit),
Tuple(PackedTuple),
Struct(PackedStruct),
Enum(PackedEnum),
}
impl Data {
pub fn ident(&self) -> &syn::Ident {
match self {
Self::Unit(unit) => unit.ident(),
Self::Tuple(tuple) => tuple.ident(),
Self::Struct(structure) => structure.ident(),
Self::Enum(enumeration) => enumeration.ident(),
}
}
}
impl Data {
pub fn generics(&self) -> syn::Generics {
match self {
Self::Struct(structure) => structure.generics.clone(),
Self::Unit(_) | Self::Tuple(_) | Self::Enum(_) => syn::Generics::default(),
}
}
}
impl Container {
pub fn ident(&self) -> &syn::Ident {
self.data.ident()
}
pub fn generics(&self) -> syn::Generics {
self.data.generics()
}
}
impl Parse for Container {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
attributes: input.parse()?,
_visibility: input.parse()?,
data: input.parse()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accept_generic_named_struct() {
let data = syn::parse_str::<Data>("struct Wrapper<T> { inner: T }").unwrap();
assert!(matches!(data, Data::Struct(_)));
}
#[test]
fn reject_empty_generic_struct() {
let err = syn::parse_str::<Data>("struct Empty<T> {}")
.err()
.expect("empty generic struct must be rejected");
assert!(
err.to_string().contains("generic struct with no fields"),
"unexpected error: {err}"
);
}
#[test]
fn reject_generic_enum() {
let err = syn::parse_str::<Data>("enum Either<L, R> { Left(L), Right(R) }")
.err()
.expect("generic enum must be rejected");
assert!(
err.to_string().contains("generic"),
"unexpected error: {err}"
);
}
}
impl Parse for Data {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Token!(struct)) {
if input.peek3(Token!(;)) {
input.parse().map(Data::Unit)
} else if input.peek3(syn::token::Paren) {
let tuple: PackedTuple = input.parse()?;
if tuple.equivalent_to_packed_unit() {
Ok(Data::Unit(tuple.into_unit()))
} else {
Ok(Data::Tuple(tuple))
}
} else {
let structure: PackedStruct = input.parse()?;
if structure.equivalent_to_packed_unit() {
if !structure.generics.params.is_empty() {
return Err(syn::Error::new(
structure.ident.span(),
"packtool cannot derive `Packed` for a generic struct with no fields: \
the type parameters are unused and the packed layout cannot depend on them",
));
}
Ok(Data::Unit(structure.into_unit()))
} else {
Ok(Data::Struct(structure))
}
}
} else if input.peek(Token!(enum)) {
let enumeration: PackedEnum = input.parse()?;
if enumeration.equivalent_to_packed_unit() {
Err(syn::Error::new(
input.span(),
"zero-variant enums cannot be packed. This is because they cannot be instantiated.",
))
} else {
Ok(Data::Enum(enumeration))
}
} else if input.peek(Token!(union)) {
Err(syn::Error::new(
input.span(),
"packtool cannot derive `Packed` for unions",
))
} else {
Err(syn::Error::new(input.span(), "not handled by `packtool`"))
}
}
}