use super::{
attr_struct,
types::{Assert, CondEndian, EnumErrorMode, Imports, Magic, Map},
EnumVariant, FromInput, ParseResult, StructField, TrySet, UnitEnumField,
};
use crate::binrw::Options;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{spanned::Spanned, Ident};
pub(crate) enum Input {
Struct(Struct),
UnitStruct(Struct),
Enum(Enum),
UnitOnlyEnum(UnitOnlyEnum),
}
impl Input {
pub(crate) fn from_input(input: &syn::DeriveInput, options: Options) -> ParseResult<Self> {
let attrs = &input.attrs;
let ident = Some(&input.ident);
match &input.data {
syn::Data::Struct(st) => {
let read_struct = if options.write {
<Struct as FromInput<StructAttr<true>>>::from_input(
attrs,
st.fields.iter(),
options,
)
} else {
<Struct as FromInput<StructAttr<false>>>::from_input(
attrs,
st.fields.iter(),
options,
)
};
if matches!(st.fields, syn::Fields::Unit) {
read_struct.map(Self::UnitStruct)
} else {
read_struct.map(Self::Struct)
}
}
syn::Data::Enum(en) => {
let variants = &en.variants;
if variants.is_empty() {
ParseResult::Err(syn::Error::new(
input.span(),
"null enums are not supported",
))
} else if variants
.iter()
.all(|v| matches!(v.fields, syn::Fields::Unit))
{
if options.write {
<UnitOnlyEnum as FromInput<UnitEnumAttr<true>>>::from_input(
attrs,
variants.iter(),
options,
)
} else {
<UnitOnlyEnum as FromInput<UnitEnumAttr<false>>>::from_input(
attrs,
variants.iter(),
options,
)
}
.map(Self::UnitOnlyEnum)
} else {
if options.write {
<Enum as FromInput<EnumAttr<true>>>::from_input(
attrs,
variants.iter(),
options,
)
} else {
<Enum as FromInput<EnumAttr<false>>>::from_input(
attrs,
variants.iter(),
options,
)
}
.map(|mut e| {
e.ident = ident.cloned();
Self::Enum(e)
})
}
}
syn::Data::Union(_) => {
ParseResult::Err(syn::Error::new(input.span(), "unions are not supported"))
}
}
}
pub(crate) fn endian(&self) -> &CondEndian {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.endian,
Input::Enum(e) => &e.endian,
Input::UnitOnlyEnum(e) => &e.endian,
}
}
pub(crate) fn imports(&self) -> &Imports {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.imports,
Input::Enum(e) => &e.imports,
Input::UnitOnlyEnum(e) => &e.imports,
}
}
pub(crate) fn is_empty(&self) -> bool {
match self {
Input::Struct(s) => s.fields.is_empty() && s.magic.is_none(),
Input::UnitStruct(_) => true,
Input::Enum(e) => e.variants.is_empty() && e.magic.is_none(),
Input::UnitOnlyEnum(_) => false,
}
}
pub(crate) fn is_temp_field(&self, variant_index: usize, index: usize) -> bool {
match self {
Input::Struct(s) => s
.fields
.get(index)
.is_some_and(|field| field.is_temp(s.for_write)),
Input::Enum(e) => e.variants.get(variant_index).is_some_and(|variant| {
if let EnumVariant::Variant { options, .. } = variant {
options
.fields
.get(index)
.is_some_and(|field| field.is_temp(options.for_write))
} else {
false
}
}),
Input::UnitStruct(_) | Input::UnitOnlyEnum(_) => false,
}
}
pub(crate) fn map(&self) -> &Map {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.map,
Input::Enum(e) => &e.map,
Input::UnitOnlyEnum(e) => &e.map,
}
}
pub(crate) fn magic(&self) -> &Magic {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.magic,
Input::Enum(e) => &e.magic,
Input::UnitOnlyEnum(e) => &e.magic,
}
}
pub(crate) fn map_stream(&self) -> Option<&TokenStream> {
match self {
Input::Struct(s) | Input::UnitStruct(s) => s.map_stream.as_ref(),
Input::Enum(en) => en.map_stream.as_ref(),
Input::UnitOnlyEnum(en) => en.map_stream.as_ref(),
}
}
pub(crate) fn pre_assertions(&self) -> &[Assert] {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.pre_assertions,
Input::Enum(e) => &e.pre_assertions,
Input::UnitOnlyEnum(_) => &[],
}
}
pub(crate) fn stream_ident(&self) -> Option<&Ident> {
match self {
Input::Struct(s) | Input::UnitStruct(s) => s.stream_ident.as_ref(),
Input::Enum(en) => en.stream_ident.as_ref(),
Input::UnitOnlyEnum(en) => en.stream_ident.as_ref(),
}
}
pub(crate) fn stream_ident_or(&self, or: impl ToTokens) -> TokenStream {
self.stream_ident()
.map_or_else(|| or.to_token_stream(), ToTokens::to_token_stream)
}
pub(crate) fn assertions(&self) -> &[Assert] {
match self {
Input::Struct(s) | Input::UnitStruct(s) => &s.assertions,
Input::Enum(e) => &e.assertions,
Input::UnitOnlyEnum(_) => &[],
}
}
}
attr_struct! {
#[from(StructAttr)]
#[derive(Clone, Debug, Default)]
pub(crate) struct Struct {
#[from(RW:Stream)]
pub(crate) stream_ident: Option<Ident>,
#[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)]
pub(crate) endian: CondEndian,
#[from(RW:Map, RW:TryMap, RW:Repr)]
pub(crate) map: Map,
#[from(RW:MapStream)]
pub(crate) map_stream: Option<TokenStream>,
#[from(RW:Magic)]
pub(crate) magic: Magic,
#[from(RW:Import, RW:ImportRaw)]
pub(crate) imports: Imports,
#[from(RW:Assert)]
pub(crate) assertions: Vec<Assert>,
#[from(RO:PreAssert)]
pub(crate) pre_assertions: Vec<Assert>,
pub(crate) fields: Vec<StructField>,
pub(crate) for_write: bool,
}
}
impl Struct {
pub(crate) fn is_tuple(&self) -> bool {
self.fields
.first()
.is_some_and(|field| field.generated_ident)
}
pub(crate) fn iter_permanent_idents(&self) -> impl Iterator<Item = &syn::Ident> + '_ {
self.fields.iter().filter_map(move |field| {
if field.is_temp(self.for_write) {
None
} else {
Some(&field.ident)
}
})
}
pub(crate) fn has_no_attrs(&self) -> bool {
matches!(self.endian, CondEndian::Inherited)
&& matches!(self.map, Map::None)
&& self.magic.is_none()
&& matches!(self.imports, Imports::None)
&& self.fields.iter().all(StructField::has_no_attrs)
}
pub(crate) fn fields_pattern(&self) -> TokenStream {
let fields = self.iter_permanent_idents();
if self.is_tuple() {
quote::quote! {
(#(ref #fields),*)
}
} else {
quote::quote! {
{ #(ref #fields),* }
}
}
}
}
impl<const WRITE: bool> FromInput<StructAttr<WRITE>> for Struct {
type Field = StructField;
fn push_field(&mut self, field: Self::Field) -> syn::Result<()> {
self.fields.push(field);
Ok(())
}
fn set_options(&mut self, options: Options) {
self.for_write = options.write;
}
fn validate(&self, options: Options) -> syn::Result<()> {
if self.map.is_none() && !options.derive {
return Ok(());
}
for field in &self.fields {
if self.map.is_some() && !field.has_no_attrs() {
return Err(syn::Error::new(
field.field.span(),
"cannot use attributes on fields inside a struct with a struct-level `map`",
));
}
if options.derive && field.is_temp(options.write) {
return Err(syn::Error::new(
field.field.span(),
if options.write {
"`#[derive(BinWrite)]` cannot create temporary fields; use `#[binrw]` or `#[binwrite]` instead"
} else {
"`#[derive(BinRead)]` cannot create temporary fields; use `#[binrw]` or `#[binread]` instead"
},
));
}
}
Ok(())
}
}
attr_struct! {
#[from(EnumAttr)]
#[derive(Clone, Debug, Default)]
pub(crate) struct Enum {
pub(crate) ident: Option<syn::Ident>,
#[from(RW:Stream)]
pub(crate) stream_ident: Option<Ident>,
#[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)]
pub(crate) endian: CondEndian,
#[from(RW:Map, RW:TryMap, RW:Repr)]
pub(crate) map: Map,
#[from(RW:MapStream)]
pub(crate) map_stream: Option<TokenStream>,
#[from(RW:Magic)]
pub(crate) magic: Magic,
#[from(RW:Import, RW:ImportRaw)]
pub(crate) imports: Imports,
#[from(RW:Assert)]
pub(crate) assertions: Vec<Assert>,
#[from(RO:PreAssert)]
pub(crate) pre_assertions: Vec<Assert>,
#[from(RO:ReturnAllErrors, RO:ReturnUnexpectedError)]
pub(crate) error_mode: EnumErrorMode,
pub(crate) variants: Vec<EnumVariant>,
}
}
impl<const WRITE: bool> FromInput<EnumAttr<WRITE>> for Enum {
type Field = EnumVariant;
fn push_field(&mut self, field: Self::Field) -> syn::Result<()> {
self.variants.push(field);
Ok(())
}
fn validate(&self, _: Options) -> syn::Result<()> {
if self.map.is_some() {
if let Some(variant) = self.variants.iter().find(|variant| !variant.has_no_attrs()) {
return Err(syn::Error::new(
variant.ident().span(),
"cannot use attributes on variants inside an enum with an enum-level `map`",
));
}
}
Ok(())
}
}
attr_struct! {
#[from(UnitEnumAttr)]
#[derive(Clone, Debug, Default)]
pub(crate) struct UnitOnlyEnum {
#[from(RW:Stream)]
pub(crate) stream_ident: Option<Ident>,
#[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)]
pub(crate) endian: CondEndian,
#[from(RW:Map, RW:TryMap, RW:Repr)]
pub(crate) map: Map,
#[from(RW:MapStream)]
pub(crate) map_stream: Option<TokenStream>,
#[from(RW:Magic)]
pub(crate) magic: Magic,
#[from(RW:Import, RW:ImportRaw)]
pub(crate) imports: Imports,
pub(crate) fields: Vec<UnitEnumField>,
pub(crate) is_magic_enum: bool,
}
}
impl UnitOnlyEnum {
pub(crate) fn is_magic_enum(&self) -> bool {
self.is_magic_enum
}
}
impl<const WRITE: bool> FromInput<UnitEnumAttr<WRITE>> for UnitOnlyEnum {
type Field = UnitEnumField;
fn push_field(&mut self, field: Self::Field) -> syn::Result<()> {
if let (Some(repr), Some(magic)) = (self.map.as_repr(), field.magic.as_ref()) {
let magic_span = magic.span();
let span = magic_span.join(repr.span()).unwrap_or(magic_span);
Err(syn::Error::new(
span,
"`repr` and `magic` are mutually exclusive",
))
} else {
self.is_magic_enum |= field.magic.is_some();
self.fields.push(field);
Ok(())
}
}
fn validate(&self, options: Options) -> syn::Result<()> {
if self.map.as_repr().is_some() || self.is_magic_enum() {
Ok(())
} else if options.write {
Err(syn::Error::new(proc_macro2::Span::call_site(), "BinWrite on unit-like enums requires either `#[bw(repr = ...)]` on the enum or `#[bw(magic = ...)]` on at least one variant"))
} else {
Err(syn::Error::new(proc_macro2::Span::call_site(), "BinRead on unit-like enums requires either `#[br(repr = ...)]` on the enum or `#[br(magic = ...)]` on at least one variant"))
}
}
}